Code minus docs

This commit is contained in:
privet.fun 2023-10-01 14:08:48 -07:00
parent c675a9537c
commit 177d396c6d
16 changed files with 558 additions and 45 deletions

View file

@ -1,4 +1,4 @@
# AI Labs v 1.0.4 RC # AI Labs v 1.0.5
##### [Changelog](#changelog_link) ##### [Changelog](#changelog_link)
Incorporate AI into your phpBB board and get ready for an exciting experience. Incorporate AI into your phpBB board and get ready for an exciting experience.

View file

@ -3,8 +3,8 @@
"type": "phpbb-extension", "type": "phpbb-extension",
"description": "AI Labs", "description": "AI Labs",
"homepage": "https://privet.fun", "homepage": "https://privet.fun",
"version": "1.0.4", "version": "1.0.5",
"time": "2023-06-04", "time": "2023-10-01",
"keywords": [ "keywords": [
"phpbb", "phpbb",
"extension", "extension",

View file

@ -10,6 +10,21 @@ privet_stablediffusion_page:
path: /ailabs/stablediffusion path: /ailabs/stablediffusion
defaults: { _controller: privet.ailabs.controller_stablediffusion:execute } defaults: { _controller: privet.ailabs.controller_stablediffusion:execute }
privet_midjourney_page:
path: /ailabs/midjourney
defaults: { _controller: privet.ailabs.controller_midjourney:execute }
privet_midjourney_callback:
path: /ailabs/midjourney/callback/{job_id}/{ref}/{action}
methods: [POST]
defaults:
_controller: privet.ailabs.controller_midjourney:callback
mode: 'post'
requirements:
job_id: \d+
ref: "[a-zA-Z0-9]+"
action: posted|reply
privet_scriptexecute_page: privet_scriptexecute_page:
path: /ailabs/scriptexecute path: /ailabs/scriptexecute
defaults: { _controller: privet.ailabs.controller_scriptexecute:execute } defaults: { _controller: privet.ailabs.controller_scriptexecute:execute }

View file

@ -85,6 +85,23 @@ services:
- '%privet.ailabs.tables.users%' - '%privet.ailabs.tables.users%'
- '%privet.ailabs.tables.jobs%' - '%privet.ailabs.tables.jobs%'
privet.ailabs.controller_midjourney:
class: privet\ailabs\controller\midjourney
arguments:
- '@auth'
- '@config'
- '@dbal.conn'
- '@controller.helper'
- '@language'
- '@request'
- '@template'
- '@user'
- '@service_container'
- '%core.php_ext%'
- '%core.root_path%'
- '%privet.ailabs.tables.users%'
- '%privet.ailabs.tables.jobs%'
privet.ailabs.controller_scriptexecute: privet.ailabs.controller_scriptexecute:
class: privet\ailabs\controller\scriptexecute class: privet\ailabs\controller\scriptexecute
arguments: arguments:

View file

@ -38,6 +38,7 @@ class acp_controller //implements acp_interface
'/ailabs/chatgpt', '/ailabs/chatgpt',
'/ailabs/dalle', '/ailabs/dalle',
'/ailabs/stablediffusion', '/ailabs/stablediffusion',
'/ailabs/midjourney',
'/ailabs/scriptexecute' '/ailabs/scriptexecute'
]; ];

View file

@ -18,7 +18,8 @@ use privet\ailabs\includes\resultParse;
/* /*
config config (example)
{ {
"api_key": "<api-key>", "api_key": "<api-key>",
"url_chat": "https://api.openai.com/v1/chat/completions", "url_chat": "https://api.openai.com/v1/chat/completions",
@ -31,10 +32,13 @@ config
"presence_penalty": 0.6, "presence_penalty": 0.6,
"prefix": "This is optional field you can remove it or populate with something like this -> Pretend your are Bender from Futurma", "prefix": "This is optional field you can remove it or populate with something like this -> Pretend your are Bender from Futurma",
"prefix_tokens": 16 "prefix_tokens": 16
"max_quote_length": 10
} }
template template
{info}[quote={poster_name} post_id={post_id} user_id={poster_id}]{request}[/quote]{response} {info}[quote={poster_name} post_id={post_id} user_id={poster_id}]{request}[/quote]{response}
*/ */
class chatgpt extends AIController class chatgpt extends AIController
@ -92,7 +96,7 @@ class chatgpt extends AIController
$post_first_discarded = null; $post_first_discarded = null;
$mode = $this->job['post_mode']; $mode = $this->job['post_mode'];
$history = ['post_text' => $this->job['post_text']]; $history = ['post_text' => $this->job['post_text']];
$pattern = '/<QUOTE\sauthor="' . $this->job['ailabs_username'] . '"\spost_id="(.*)"\stime="(.*)"\suser_id="' . $this->job['ailabs_user_id'] . '">/'; $pattern = '/<QUOTE\sauthor="' . $this->job['ailabs_username'] . '"\spost_id="(.*)"\stime="(.*)"\suser_id="' . $this->job['ailabs_user_id'] . '">/';
@ -116,7 +120,7 @@ class chatgpt extends AIController
if ($matches != null && !empty($matches) && !empty($matches[1][0])) { if ($matches != null && !empty($matches) && !empty($matches[1][0])) {
$postid = (int) $matches[1][0]; $postid = (int) $matches[1][0];
$sql = 'SELECT j.job_id, j.post_id, j.response_post_id, j.request, j.response, p.post_text, j.request_tokens, j.response_tokens ' . $sql = 'SELECT j.job_id, j.post_id, j.response_post_id, j.request, j.response, p.post_text, p.post_time, j.request_tokens, j.response_tokens ' .
'FROM ' . $this->jobs_table . ' j ' . 'FROM ' . $this->jobs_table . ' j ' .
'JOIN ' . POSTS_TABLE . ' p ON p.post_id = j.post_id ' . 'JOIN ' . POSTS_TABLE . ' p ON p.post_id = j.post_id ' .
'WHERE ' . $this->db->sql_build_array('SELECT', ['response_post_id' => $postid]); 'WHERE ' . $this->db->sql_build_array('SELECT', ['response_post_id' => $postid]);
@ -145,11 +149,34 @@ class chatgpt extends AIController
$post_first_taken = $postid; $post_first_taken = $postid;
$history_tokens += $count_tokens; $history_tokens += $count_tokens;
$history_decoded_request = utf8_decode_ncr($history['request']);
$history_decoded_response = utf8_decode_ncr($history['response']);
array_unshift( array_unshift(
$messages, $messages,
['role' => 'user', 'content' => trim($history['request'])], ['role' => 'user', 'content' => trim($history_decoded_request)],
['role' => 'assistant', 'content' => trim($history['response'])] ['role' => 'assistant', 'content' => trim($history_decoded_response)]
); );
if ($round == 0) {
// Remove quoted content from the quoted post
$post_text = sprintf(
'<r><QUOTE author="%1$s" post_id="%2$s" time="%3$s" user_id="%4$s"><s>[quote=%1$s post_id=%2$s time=%3$s user_id=%4$s]</s>%6$s<e>[/quote]</e></QUOTE>%5$s</r>',
$this->job['ailabs_username'],
(string) $postid,
(string) $this->job['post_time'],
(string) $this->job['ailabs_user_id'],
$this->job['request'],
property_exists($this->cfg, 'max_quote_length') ?
$this->trim_words($history_decoded_response, (int) $this->cfg->max_quote_length) : $history_decoded_response,
);
$sql = 'UPDATE ' . POSTS_TABLE .
' SET ' . $this->db->sql_build_array('UPDATE', ['post_text' => utf8_encode_ucr($post_text)]) .
' WHERE post_id = ' . (int) $this->job['post_id'];
$result = $this->db->sql_query($sql);
$this->db->sql_freeresult($result);
}
} }
} }
} while (!empty($history)); } while (!empty($history));
@ -269,7 +296,7 @@ class chatgpt extends AIController
'status' => $this->job['status'], 'status' => $this->job['status'],
'attempts' => $this->job['attempts'] + 1, 'attempts' => $this->job['attempts'] + 1,
'response_time' => $this->job['response_time'], 'response_time' => $this->job['response_time'],
'response' => $api_response, 'response' => utf8_encode_ucr($api_response),
'request_tokens' => $request_tokens - $history_tokens - $prefix_tokens, 'request_tokens' => $request_tokens - $history_tokens - $prefix_tokens,
'response_post_id' => $this->job['response_post_id'], 'response_post_id' => $this->job['response_post_id'],
'response_tokens' => $response_tokens, 'response_tokens' => $response_tokens,

View file

@ -159,7 +159,7 @@ class dalle extends GenericController
array_push($images, $item->url); array_push($images, $item->url);
} else { } else {
$filename = $this->save_base64_to_temp_file($item->b64_json, $ind); $filename = $this->save_base64_to_temp_file($item->b64_json, $ind);
$item->b64_json = '<reducted>'; $item->b64_json = '<redacted>';
array_push($images, $filename); array_push($images, $filename);
} }
$ind++; $ind++;

View file

@ -0,0 +1,359 @@
<?php
/**
*
* AI Labs extension
*
* @copyright (c) 2023, privet.fun, https://privet.fun
* @license GNU General Public License, version 2 (GPL-2.0)
*
*/
namespace privet\ailabs\controller;
use privet\ailabs\includes\GenericCurl;
use privet\ailabs\includes\GenericController;
use privet\ailabs\includes\resultSubmit;
use privet\ailabs\includes\resultParse;
use Symfony\Component\HttpFoundation\JsonResponse;
/*
// How to get api token and configure Discord
// https://useapi.net/docs/start-here
config:
{
"api_key": "<useapi.net api token>",
"url_imagine": "https://api.useapi.net/v1/jobs/imagine",
"url_button": "https://api.useapi.net/v1/jobs/button",
"discord": "<Discord token, required>",
"server": "<Discord server id, required>",
"channel": "<Discord channel id, required>",
"maxJobs": "<Midjourney subscription plan Maximum Concurrent Jobs, optional, default 3>",
"retryCount": "<Maximum attempts to submit request, optional, default 80>",
"timeoutBeforeRetrySec": "<Time to wait before next retry, optional, default 15>",
}
template:
[quote={poster_name} post_id={post_id} user_id={poster_id}]{request}[/quote]
{response}
{images}
{info}
*/
class midjourney extends GenericController
{
/**
* @return \Symfony\Component\HttpFoundation\Response A Symfony Response object
*/
public function callback($job_id, $ref, $action)
{
$this->job_id = $job_id;
$this->load_job();
if (empty($this->job))
return new JsonResponse('job_id ' . $job_id . ' not found in the database');
if ($this->job['ref'] !== $ref)
return new JsonResponse('wrong reference ' . $ref);
if (in_array($this->job['status'], ['ok', 'failed']))
return new JsonResponse('job_id ' . $job_id . ' already has final status ' . $this->job['status']);
$this->log = json_decode($this->job['log'], true);
// POST body as json
$data = json_decode(file_get_contents('php://input'), true);
$json = null;
switch ($action) {
case 'posted':
$response_codes = null;
// Store entire posted response into log
foreach ($data as $key => $value) {
$this->log[$key] = $value;
if ($key === 'response.json')
$json = $value;
if ($key === 'response.codes') {
$response_codes = $value;
// We may get no response body at all in some cases
if (!in_array(200, $response_codes))
$this->job['status'] = 'failed';
}
}
$response_message_id = $this->process_response_message_id($json);
// https://useapi.net/docs/api-v1/jobs-button
// HTTP 409 Conflict
// Button <U1 | U2 | U3 | U4> already executed by job <jobid>
if (!empty($response_message_id) && !empty($response_codes) && in_array(409, $response_codes)) {
$sql = 'SELECT j.response_post_id FROM ' . $this->jobs_table . ' j WHERE ' .
$this->db->sql_build_array('SELECT', ['response_message_id' => $response_message_id]);
$result = $this->db->sql_query($sql);
$row = $this->db->sql_fetchrow($result);
$this->db->sql_freeresult($result);
if (!empty($row)) {
$viewtopic = "{$this->root_path}viewtopic.{$this->php_ext}";
$json['response'] = $this->language->lang('AILABS_MJ_BUTTON_ALREADY_USED', $json['button'], $viewtopic, $row['response_post_id']);
}
}
break;
case 'reply':
// Raw response from useapi.net /imagine or /button API endpoints
$json = $data;
// Upscale buttons U1..U4 may create race condition, let's rely on .../posted to process response
if (!empty($json) && !empty($json['code']) && $json['code'] === 409)
return new JsonResponse('Skipping 409');
$this->process_response_message_id($json);
$this->log['response.json'] = $json;
$this->log['response.time'] = date('Y-m-d H:i:s');
break;
}
// Assume the worst
$this->job['status'] = 'failed';
$this->job['response'] = $this->language->lang('AILABS_ERROR_CHECK_LOGS');
if (!empty($json)) {
if (!empty($json['status']))
switch ($json['status']) {
case 'created':
case 'started':
case 'progress':
$this->job['status'] = 'exec';
break;
case 'completed':
$this->job['status'] = 'ok';
break;
}
if (!empty($json['code']))
switch ($json['code']) {
case 200: // HTTP OK
$this->job['response'] = preg_replace('/<@(\d+)>/', '', $json['content']);
break;
case 422: // HTTP 422 Unprocessable Content - Moderated
$this->job['response'] = $this->language->lang('AILABS_MJ_MODERATED');
break;
}
}
if (!empty($json) && in_array($this->job['status'], ['ok', 'failed'])) {
$resultParse = new resultParse();
$resultParse->message = $this->job['response'];
// Only attach successfully generated images, seems like all other images will be deleted from Discord CDN
if (($this->job['status'] == 'ok') && !empty($json['attachments'])) {
$url_adjusted = (string) $json['attachments'][0]['url'];
$url_adjusted = preg_replace('/\?.*$/', '', $url_adjusted);
$resultParse->images = array($url_adjusted);
}
if (!empty($json['buttons']))
$resultParse->info = $this->language->lang('AILABS_MJ_BUTTONS') . implode("", $json['buttons']);
$response = $this->replace_vars($this->job, $resultParse);
$data = $this->post_response($this->job, $response);
$this->job['response_post_id'] = $data['post_id'];
}
$set = [
'status' => $this->job['status'],
'response' => utf8_encode_ucr($this->job['response']),
'response_time' => time(),
'response_post_id' => $this->job['response_post_id'],
'log' => json_encode($this->log)
];
$this->job_update($set);
$this->post_update($this->job);
return new JsonResponse($this->log);
}
protected function prepare($opts)
{
$pattern = '/<QUOTE\sauthor="' . $this->job['ailabs_username'] . '"\spost_id="(.*)"\stime="(.*)"\suser_id="' . $this->job['ailabs_user_id'] . '">/';
$parent_job = null;
$matches = null;
preg_match_all(
$pattern,
$this->job['post_text'],
$matches
);
if (!empty($matches) && !empty($matches[1][0])) {
$response_post_id = (int) $matches[1][0];
$sql = 'SELECT j.job_id, j.response_post_id, j.log, j.response ' .
'FROM ' . $this->jobs_table . ' j ' .
'WHERE ' . $this->db->sql_build_array('SELECT', ['response_post_id' => $response_post_id]);
$result = $this->db->sql_query($sql);
$parent_job = $this->db->sql_fetchrow($result);
$this->db->sql_freeresult($result);
// Remove quoted content from the quoted post
$post_text = sprintf(
'<r><QUOTE author="%1$s" post_id="%2$s" time="%3$s" user_id="%4$s"><s>[quote=%1$s post_id=%2$s time=%3$s user_id=%4$s]</s>%6$s<e>[/quote]</e></QUOTE>%5$s</r>',
$this->job['ailabs_username'],
(string) $response_post_id,
(string) $this->job['post_time'],
(string) $this->job['ailabs_user_id'],
$this->job['request'],
$parent_job ? utf8_decode_ncr($parent_job['response']) : '...'
);
$sql = 'UPDATE ' . POSTS_TABLE .
' SET ' . $this->db->sql_build_array('UPDATE', ['post_text' => utf8_encode_ucr($post_text)]) .
' WHERE post_id = ' . (int) $this->job['post_id'];
$result = $this->db->sql_query($sql);
$this->db->sql_freeresult($result);
}
$maxJobs = empty($this->cfg->maxJobs) ? 3 : $this->cfg->maxJobs;
$url_callback = generate_board_url(true) .
$this->helper->route(
'privet_midjourney_callback',
[
'job_id' => $this->job_id,
'ref' => $this->job['ref'],
'action' => 'reply'
]
);
$request = $this->job['request'];
$payload = null;
if (!empty($parent_job)) {
$log = json_decode($parent_job['log'], true);
// https://useapi.net/docs/api-v1/jobs-button
if (
!empty($log) &&
!empty($log['response.json']) &&
!empty($log['response.json']['jobid']) &&
!empty($log['response.json']['buttons']) &&
in_array($request, $log['response.json']['buttons'], true)
) {
$payload = [
'jobid' => $log['response.json']['jobid'],
'button' => $request,
'discord' => $this->cfg->discord,
'maxJobs' => $maxJobs,
'replyUrl' => $url_callback,
'replyRef' => $this->job_id,
];
}
}
// https://useapi.net/docs/api-v1/jobs-imagine
if (empty($payload)) {
$payload = [
'prompt' => $request,
'discord' => $this->cfg->discord,
'server' => $this->cfg->server,
'channel' => $this->cfg->channel,
'maxJobs' => $maxJobs,
'replyUrl' => $url_callback,
'replyRef' => $this->job_id,
];
}
array_push($this->redactOpts, 'discord');
return $payload;
}
protected function submit($opts): resultSubmit
{
$this->job['status'] = 'query';
$this->job_update(['status' => $this->job['status']]);
$this->post_update($this->job);
$api = new GenericCurl($this->cfg->api_key, 0);
$this->cfg->api_key = null;
$retryCount = empty($this->cfg->retryCount) ? 80 : $this->cfg->retryCount;
$timeoutBeforeRetrySec = empty($this->cfg->timeoutBeforeRetrySec) ? 15 : $this->cfg->timeoutBeforeRetrySec;
$count = 0;
$response = null;
// https://useapi.net/docs/api-v1/jobs-imagine
// https://useapi.net/docs/api-v1/jobs-button
$url = empty($opts['jobid']) ? $this->cfg->url_imagine : $this->cfg->url_button;
// Attempt to submit request for (retryCount * timeoutBeforeRetrySec) seconds.
// Required for cases where multiple users simultaneously submitting requests or Midjourney query is full.
do {
$count++;
$response = $api->sendRequest($url, 'POST', $opts);
} while (
// 429: Maximum of xx jobs executing in parallel supported
// 504: Unable to lock Discord after xx attempts
(in_array(429, $api->responseCodes) || in_array(504, $api->responseCodes)) &&
$count < $retryCount &&
sleep($timeoutBeforeRetrySec) !== false
);
$data = [
'request.time' => date('Y-m-d H:i:s'),
'request.config.retryCount' => $retryCount,
'request.config.timeoutBeforeRetrySec' => $timeoutBeforeRetrySec,
'request.attempts' => $count,
'response.codes' => $api->responseCodes,
'response.length' => strlen($response),
'response.json' => json_decode($response)
];
$url_callback = generate_board_url(true) .
$this->helper->route(
'privet_midjourney_callback',
[
'job_id' => $this->job_id,
'ref' => $this->job['ref'],
'action' => 'posted'
]
);
$api->sendRequest($url_callback, 'POST', $data);
$result = new resultSubmit();
$result->ignore = true;
return $result;
}
protected function process_response_message_id($json)
{
$response_message_id = null;
if (!empty($json) && !empty($json['jobid']))
$response_message_id = $json['jobid'];
if (!empty($response_message_id) && empty($this->job['response_message_id']))
$this->job_update(['response_message_id' => $response_message_id]);
return $response_message_id;
}
}

View file

@ -128,11 +128,11 @@ class stablediffusion extends GenericController
if ($item->finishReason !== 'SUCCESS') { if ($item->finishReason !== 'SUCCESS') {
$message = $item->finishReason; $message = $item->finishReason;
if (!empty($item->base64)) if (!empty($item->base64))
$item->base64 = '<reducted>'; $item->base64 = '<redacted>';
} else { } else {
$filename = $this->save_base64_to_temp_file($item->base64, $ind); $filename = $this->save_base64_to_temp_file($item->base64, $ind);
array_push($images, $filename); array_push($images, $filename);
$item->base64 = '<reducted>'; $item->base64 = '<redacted>';
} }
$ind++; $ind++;
} }

View file

@ -203,6 +203,7 @@ class listener implements EventSubscriberInterface
'poster_id' => $this->user->data['user_id'], 'poster_id' => $this->user->data['user_id'],
'poster_name' => $this->user->data['username'], 'poster_name' => $this->user->data['username'],
'request' => utf8_encode_ucr($request), 'request' => utf8_encode_ucr($request),
'ref' => bin2hex(random_bytes(21))
]; ];
$sql = 'INSERT INTO ' . $this->jobs_table . ' ' . $this->db->sql_build_array('INSERT', $data); $sql = 'INSERT INTO ' . $this->jobs_table . ' ' . $this->db->sql_build_array('INSERT', $data);
$result = $this->db->sql_query($sql); $result = $this->db->sql_query($sql);
@ -303,6 +304,8 @@ class listener implements EventSubscriberInterface
return $this->language->lang('AILABS_REPLIED'); return $this->language->lang('AILABS_REPLIED');
case 'fail': case 'fail':
return $this->language->lang('AILABS_UNABLE_TO_REPLY'); return $this->language->lang('AILABS_UNABLE_TO_REPLY');
case 'query':
return $this->language->lang('AILABS_QUERY');
} }
return $status; return $status;

View file

@ -91,20 +91,7 @@ class AIController
return new JsonResponse('job_id not provided'); return new JsonResponse('job_id not provided');
} }
$where = [ $this->load_job();
'job_id' => $this->job_id
];
$sql = 'SELECT j.job_id, j.ailabs_user_id, j.status, j.attempts, j.post_mode, j.post_id, j.forum_id, j.poster_id, j.poster_name, j.request, c.config, c.template, u.username as ailabs_username, p.topic_id, p.post_subject, p.post_text, f.forum_name ' .
'FROM ' . $this->jobs_table . ' j ' .
'JOIN ' . $this->users_table . ' c ON c.user_id = j.ailabs_user_id ' .
'JOIN ' . USERS_TABLE . ' u ON u.user_id = j.ailabs_user_id ' .
'JOIN ' . POSTS_TABLE . ' p ON p.post_id = j.post_id ' .
'JOIN ' . FORUMS_TABLE . ' f ON f.forum_id = j.forum_id ' .
'WHERE ' . $this->db->sql_build_array('SELECT', $where);
$result = $this->db->sql_query($sql);
$this->job = $this->db->sql_fetchrow($result);
$this->db->sql_freeresult($result);
if (empty($this->job)) { if (empty($this->job)) {
return new JsonResponse('job_id not found in the database'); return new JsonResponse('job_id not found in the database');
@ -116,8 +103,6 @@ class AIController
$this->log = array('start' => $this->start); $this->log = array('start' => $this->start);
$this->job['request'] = utf8_decode_ncr($this->job['request']);
try { try {
$this->cfg = json_decode($this->job['config']); $this->cfg = json_decode($this->job['config']);
} catch (\Exception $e) { } catch (\Exception $e) {
@ -143,6 +128,27 @@ class AIController
return $this->process(); return $this->process();
} }
protected function load_job()
{
$where = [
'job_id' => $this->job_id
];
$sql = 'SELECT j.job_id, j.ailabs_user_id, j.status, j.attempts, j.post_mode, j.post_id, j.forum_id, j.poster_id, j.poster_name, j.request, j.response, j.log, j.ref, j.response_message_id, c.config, c.template, u.username as ailabs_username, p.topic_id, p.post_subject, p.post_text, p.post_time, f.forum_name ' .
'FROM ' . $this->jobs_table . ' j ' .
'JOIN ' . $this->users_table . ' c ON c.user_id = j.ailabs_user_id ' .
'JOIN ' . USERS_TABLE . ' u ON u.user_id = j.ailabs_user_id ' .
'JOIN ' . POSTS_TABLE . ' p ON p.post_id = j.post_id ' .
'JOIN ' . FORUMS_TABLE . ' f ON f.forum_id = j.forum_id ' .
'WHERE ' . $this->db->sql_build_array('SELECT', $where);
$result = $this->db->sql_query($sql);
$this->job = $this->db->sql_fetchrow($result);
$this->db->sql_freeresult($result);
if (!empty($this->job) && !empty($this->job['request']))
$this->job['request'] = utf8_decode_ncr($this->job['request']);
}
protected function process() protected function process()
{ {
return new JsonResponse($this->log); return new JsonResponse($this->log);
@ -453,4 +459,17 @@ class AIController
return $filename; return $filename;
} }
protected function trim_words($inputString, $numWords)
{
$words = explode(' ', $inputString);
if (count($words) <= $numWords) {
return $inputString;
}
$trimmedWords = array_slice($words, 0, $numWords);
return implode(' ', $trimmedWords) . '...'; //'…';
}
} }

View file

@ -18,6 +18,8 @@ use privet\ailabs\includes\resultSubmit;
class GenericController extends AIController class GenericController extends AIController
{ {
public $redactOpts = [];
// Cleansing and setting back $this->job['request'] // Cleansing and setting back $this->job['request']
// Return $opts or empty array // Return $opts or empty array
protected function init() protected function init()
@ -73,7 +75,13 @@ class GenericController extends AIController
$opts = $this->prepare($opts); $opts = $this->prepare($opts);
$this->log['request.json'] = $opts; $optsCloned = json_decode(json_encode($opts), true);
foreach ($this->redactOpts as $key) {
unset($optsCloned[$key]);
}
$this->log['request.json'] = $optsCloned;
$this->log_flush(); $this->log_flush();
$this->job['status'] = 'fail'; $this->job['status'] = 'fail';
@ -84,6 +92,9 @@ class GenericController extends AIController
try { try {
$resultSubmit = $this->submit($opts); $resultSubmit = $this->submit($opts);
if ($resultSubmit->ignore)
return new JsonResponse('waiting for callback');
$this->log['response.length'] = strlen($resultSubmit->response); $this->log['response.length'] = strlen($resultSubmit->response);
$this->log['response.codes'] = $resultSubmit->responseCodes; $this->log['response.codes'] = $resultSubmit->responseCodes;
$this->log_flush(); $this->log_flush();

View file

@ -15,4 +15,5 @@ class resultSubmit
{ {
public string $response; public string $response;
public $responseCodes; public $responseCodes;
public bool $ignore = false;
}; };

View file

@ -18,12 +18,16 @@ if (empty($lang) || !is_array($lang)) {
} }
$lang = array_merge($lang, [ $lang = array_merge($lang, [
'AILABS_ERROR_CHECK_LOGS' => '[color=#FF0000]Error. Pelase check logs.[/color]', 'AILABS_MJ_BUTTONS' => 'Reply by quoting one of the supported actions [size=70][url=https://docs.midjourney.com/docs/quick-start#8-upscale-or-create-variations]1[/url] [url=https://docs.midjourney.com/docs/quick-start#9-enhance-or-modify-your-image]2[/url][/size]: ',
'AILABS_POSTS_DISCARDED' => ', posts starting from [url=%1$s?p=%2$d#p%2$d]this post[/url] were discarded', 'AILABS_MJ_BUTTON_ALREADY_USED' => 'Action %1s was already [url=%2$s?p=%3$d#p%3$d]executed[/url]',
'AILABS_DISCARDED_INFO' => '[size=75][url=%1$s?p=%2$d#p%2$d]Beginning[/url] of a conversation containing %3$d posts%4$s (%5$d tokens of %6$d were used)[/size]', 'AILABS_ERROR_CHECK_LOGS' => '[color=#FF0000]Error. Please check logs.[/color]',
'AILABS_THINKING' => 'thinking', 'AILABS_MJ_MODERATED' => '[color=#FF0000]Moderated or invalid prompt.[/color]',
'AILABS_REPLYING' => 'replying…', 'AILABS_POSTS_DISCARDED' => ', posts starting from [url=%1$s?p=%2$d#p%2$d]this post[/url] were discarded',
'AILABS_REPLIED' => 'replied ↓', 'AILABS_DISCARDED_INFO' => '[size=75][url=%1$s?p=%2$d#p%2$d]Beginning[/url] of a conversation containing %3$d posts%4$s (%5$d tokens of %6$d were used)[/size]',
'AILABS_UNABLE_TO_REPLY' => 'unable to reply', 'AILABS_THINKING' => 'thinking',
'L_AILABS_AI' => 'AI' 'AILABS_REPLYING' => 'replying…',
'AILABS_REPLIED' => 'replied ↓',
'AILABS_UNABLE_TO_REPLY' => 'unable to reply',
'AILABS_QUERY' => 'querying',
'L_AILABS_AI' => 'AI'
]); ]);

View file

@ -18,12 +18,16 @@ if (empty($lang) || !is_array($lang)) {
} }
$lang = array_merge($lang, [ $lang = array_merge($lang, [
'AILABS_ERROR_CHECK_LOGS' => '[color=#FF0000]Ошибка. Лог содержит детальную информацию.[/color]', 'AILABS_MJ_BUTTONS' => 'Ответьте, процитировав одну из поддерживаемых команд [size=70][url=https://docs.midjourney.com/docs/quick-start#8-upscale-or-create-variations]1[/url] [url=https://docs.midjourney.com/docs/quick-start#9-enhance-or-modify-your-image]2[/url][/size]: ',
'AILABS_POSTS_DISCARDED' => ', сообщения начиная с [url=%1$s?p=%2$d#p%2$d]этого[/url] не включены', 'AILABS_MJ_BUTTON_ALREADY_USED' => 'Команда %1s уже была [url=%2$s?p=%3$d#p%3$d]выполнена[/url]',
'AILABS_DISCARDED_INFO' => '[size=75][url=%1$s?p=%2$d#p%2$d]Начало[/url] беседы из %3$d сообщений%4$s (%5$d токенов из %6$d использовано)[/size]', 'AILABS_ERROR_CHECK_LOGS' => '[color=#FF0000]Ошибка. Лог содержит детальную информацию.[/color]',
'AILABS_THINKING' => 'думает', 'AILABS_MJ_MODERATED' => '[color=#FF0000]Запрос составлен неверно или подвергся модерации Midjourney.[/color]',
'AILABS_REPLYING' => 'отвечает…', 'AILABS_POSTS_DISCARDED' => ', сообщения начиная с [url=%1$s?p=%2$d#p%2$d]этого[/url] не включены',
'AILABS_REPLIED' => 'ответил ↓', 'AILABS_DISCARDED_INFO' => '[size=75][url=%1$s?p=%2$d#p%2$d]Начало[/url] беседы из %3$d сообщений%4$s (%5$d токенов из %6$d использовано)[/size]',
'AILABS_UNABLE_TO_REPLY' => 'ответить не смог', 'AILABS_THINKING' => 'думает',
'L_AILABS_AI' => 'AI' 'AILABS_REPLYING' => 'отвечает…',
'AILABS_REPLIED' => 'ответил ↓',
'AILABS_UNABLE_TO_REPLY' => 'ответить не смог',
'AILABS_QUERY' => 'в очереди',
'L_AILABS_AI' => 'AI'
]); ]);

View file

@ -0,0 +1,52 @@
<?php
/**
*
* AI Labs extension
*
* @copyright (c) 2023, privet.fun, https://privet.fun
* @license GNU General Public License, version 2 (GPL-2.0)
*
*/
namespace privet\ailabs\migrations\v1x;
class release_1_0_5_schema extends \phpbb\db\migration\migration
{
static public function depends_on()
{
return array('\privet\ailabs\migrations\v1x\release_1_0_4_schema');
}
public function effectively_installed()
{
return isset($this->config['privet_ailabs_version']) && version_compare($this->config['privet_ailabs_version'], '1.0.5', '>=');
}
public function update_data()
{
return array(
array('config.update', array('privet_ailabs_version', '1.0.5')),
);
}
public function revert_data()
{
return array(
array('config.remove', array('privet_ailabs_version')),
);
}
public function update_schema()
{
return [
'add_columns' => [
$this->table_prefix . 'ailabs_jobs' => [
'ref' => ['VCHAR', ''], // uniquer alpha-numeric value
'response_message_id' => ['VCHAR', ''], // uniquer alpha-numeric value
],
],
];
}
}