Code minus docs
This commit is contained in:
parent
c675a9537c
commit
177d396c6d
16 changed files with 558 additions and 45 deletions
|
@ -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.
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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++;
|
||||||
|
|
359
privet/ailabs/controller/midjourney.php
Normal file
359
privet/ailabs/controller/midjourney.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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++;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) . '...'; //'…';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -15,4 +15,5 @@ class resultSubmit
|
||||||
{
|
{
|
||||||
public string $response;
|
public string $response;
|
||||||
public $responseCodes;
|
public $responseCodes;
|
||||||
|
public bool $ignore = false;
|
||||||
};
|
};
|
|
@ -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'
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -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'
|
||||||
]);
|
]);
|
||||||
|
|
52
privet/ailabs/migrations/v1x/release_1_0_5_schema.php
Normal file
52
privet/ailabs/migrations/v1x/release_1_0_5_schema.php
Normal 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
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue