Initial commit
This commit is contained in:
commit
8d9504d2ad
34 changed files with 3508 additions and 0 deletions
11
privet/ailabs/README.md
Normal file
11
privet/ailabs/README.md
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
# AI Labs
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Copy the extension to phpBB/ext/privet/ailabs
|
||||||
|
|
||||||
|
Go to "ACP" > "Customise" > "Extensions" and enable the "AI Labs" extension.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[GPLv2](license.txt)
|
30
privet/ailabs/acp/main_info.php
Normal file
30
privet/ailabs/acp/main_info.php
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<?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\acp;
|
||||||
|
|
||||||
|
class main_info
|
||||||
|
{
|
||||||
|
public function module()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'filename' => '\privet\ailabs\acp\main_module',
|
||||||
|
'title' => 'ACP_AILABS_TITLE',
|
||||||
|
'modes' => [
|
||||||
|
'settings' => [
|
||||||
|
'title' => 'ACP_AILABS_SETTINGS',
|
||||||
|
'auth' => 'ext_privet/ailabs && acl_a_board',
|
||||||
|
'cat' => ['ACP_AILABS_TITLE']
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
82
privet/ailabs/acp/main_module.php
Normal file
82
privet/ailabs/acp/main_module.php
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
<?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\acp;
|
||||||
|
|
||||||
|
class main_module
|
||||||
|
{
|
||||||
|
protected $phpbb_container;
|
||||||
|
protected $request;
|
||||||
|
protected $template;
|
||||||
|
|
||||||
|
public $u_action;
|
||||||
|
public $tpl_name;
|
||||||
|
public $page_title;
|
||||||
|
|
||||||
|
public function main($id, $mode)
|
||||||
|
{
|
||||||
|
global $phpbb_container, $request, $template;
|
||||||
|
|
||||||
|
$this->phpbb_container = $phpbb_container;
|
||||||
|
$this->request = $request;
|
||||||
|
$this->template = $template;
|
||||||
|
|
||||||
|
$this->tpl_name = 'acp_ailabs_body';
|
||||||
|
|
||||||
|
$action = $request->variable('action', '');
|
||||||
|
$submit = $request->is_set_post('submit');
|
||||||
|
$user_id = $request->variable('user_id', 0);
|
||||||
|
$username = utf8_normalize_nfc($request->variable('username', '', true));
|
||||||
|
|
||||||
|
$language = $phpbb_container->get('language');
|
||||||
|
$language->add_lang('info_acp_ailabs', 'privet/ailabs');
|
||||||
|
|
||||||
|
$acp_controller = $this->phpbb_container->get('privet.ailabs.acp_controller');
|
||||||
|
|
||||||
|
add_form_key('privet_ailabs_settings');
|
||||||
|
if ($submit && !check_form_key('privet_ailabs_settings')) {
|
||||||
|
trigger_error('FORM_INVALID' . adm_back_link($this->u_action), E_USER_WARNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
$acp_controller->get_acp_data($id, $mode, $action, $submit, $this->u_action);
|
||||||
|
|
||||||
|
switch ($mode) {
|
||||||
|
case 'settings':
|
||||||
|
switch ($action) {
|
||||||
|
case 'add':
|
||||||
|
case 'edit':
|
||||||
|
|
||||||
|
$this->page_title = ($action == 'add') ? 'ACP_AILABS_TITLE_ADD' : 'ACP_AILABS_TITLE_EDIT';
|
||||||
|
$acp_controller->edit_add();
|
||||||
|
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'delete':
|
||||||
|
if (confirm_box(true)) {
|
||||||
|
$acp_controller->delete($user_id);
|
||||||
|
} else {
|
||||||
|
confirm_box(false, $language->lang('ACP_AILABS_DELETED_CONFIRM', $username), build_hidden_fields([
|
||||||
|
'user_id' => $user_id,
|
||||||
|
'mode' => $mode,
|
||||||
|
'action' => $action,
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->page_title = 'ACP_AILABS_TITLE_VIEW';
|
||||||
|
$acp_controller->acp_ailabs_main();
|
||||||
|
break;
|
||||||
|
default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
267
privet/ailabs/adm/style/acp_ailabs_body.html
Normal file
267
privet/ailabs/adm/style/acp_ailabs_body.html
Normal file
|
@ -0,0 +1,267 @@
|
||||||
|
{% INCLUDE 'overall_header.html' %}
|
||||||
|
|
||||||
|
{% INCLUDEJS '@privet_ailabs/js/chosen.jquery.min.js' %}
|
||||||
|
|
||||||
|
{% INCLUDECSS '@privet_ailabs/chosen.min.css' %}
|
||||||
|
|
||||||
|
<a id="maincontent"></a>
|
||||||
|
|
||||||
|
<h1>{{ lang('ACP_AILABS_TITLE') }}</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{{ lang('LBL_AILABS_SETTINGS_DESC') }}
|
||||||
|
|
||||||
|
{% if U_AILABS_VEIW %}
|
||||||
|
<a href="{{ U_ADD }}" class="button2" style="float: {{ S_CONTENT_FLOW_END }};">{{ lang('ACP_AILABS_ADD') }}</a>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if U_AILABS_ADD_EDIT %}
|
||||||
|
<a href="{{ U_BACK }}" style="float: {{ S_CONTENT_FLOW_END }};">« {{ lang('BACK') }}</a>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{% if S_ERROR %}
|
||||||
|
<div class="errorbox">
|
||||||
|
<h3>{{ lang('WARNING') }}</h3>
|
||||||
|
<p>{{ S_ERROR }}</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if U_AILABS_ADD_EDIT %}
|
||||||
|
|
||||||
|
<form id="ailabs_configuration" method="POST" action="{{ U_ACTION }}">
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
const defaultConfigs = {
|
||||||
|
"chatgpt": {
|
||||||
|
"api_key": "your-openai-api-key-goes-here",
|
||||||
|
"url_chat": "https://api.openai.com/v1/chat/completions",
|
||||||
|
"model": "gpt-3.5-turbo",
|
||||||
|
"temperature": 0.9,
|
||||||
|
"max_tokens": 4096,
|
||||||
|
"message_tokens": 1024,
|
||||||
|
"top_p": 1,
|
||||||
|
"frequency_penalty": 0,
|
||||||
|
"presence_penalty": 0.6,
|
||||||
|
"prefix": "",
|
||||||
|
"prefix_tokens": 0
|
||||||
|
},
|
||||||
|
"dalle": {
|
||||||
|
"api_key": "your-openai-api-key-goes-here",
|
||||||
|
"url_generations": "https://api.openai.com/v1/images/generations",
|
||||||
|
"url_variations": "https://api.openai.com/v1/images/variations",
|
||||||
|
"n": 3,
|
||||||
|
"size": "512x512",
|
||||||
|
"response_format": "b64_json"
|
||||||
|
},
|
||||||
|
"stablediffusion": {
|
||||||
|
"api_key": "your-stablityai-api-key-goes-here",
|
||||||
|
"url_texttoimage": "https://api.stability.ai/v1/generation/stable-diffusion-xl-beta-v2-2-2/text-to-image",
|
||||||
|
"cfg_scale": 7.5,
|
||||||
|
"clip_guidance_preset": "FAST_BLUE",
|
||||||
|
"height": 512,
|
||||||
|
"width": 512,
|
||||||
|
"samples": 3,
|
||||||
|
"steps": 30
|
||||||
|
},
|
||||||
|
"scriptexecute": {
|
||||||
|
"config": {
|
||||||
|
"script": "app-to-execute",
|
||||||
|
"logs": "/var/logs/app-folder",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultTemplate = {
|
||||||
|
"chatgpt": "\{info\}[quote=\{poster_name\} post_id=\{post_id\} user_id=\{poster_id\}]\{request\}[/quote]\{response\}",
|
||||||
|
"dalle": "[quote=\{poster_name\} post_id=\{post_id\} user_id=\{poster_id\}]\{request\}[/quote]\{response\}\{attachments\}",
|
||||||
|
"stablediffusion": "[quote=\{poster_name\} post_id=\{post_id\} user_id=\{poster_id\}]\{request\}[/quote]\{response\}\{attachments\}",
|
||||||
|
"scriptexecute": "[quote=\{poster_name\} post_id=\{post_id\} user_id=\{poster_id\}]\{request\}[/quote]\{response\}\{attachments\}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function doDefault(id, config, isJSON) {
|
||||||
|
const controller = $('#ailabs_controller option:selected').text().trim();
|
||||||
|
if (config[controller]) {
|
||||||
|
const selectElement = $('#' + id);
|
||||||
|
selectElement.val(isJSON ? JSON.stringify(config[controller], null, 2) : config[controller]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeValue(value) {
|
||||||
|
if (value && Array.isArray(value) && value.length > 0)
|
||||||
|
return JSON.stringify(value);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupSelect(id, value) {
|
||||||
|
const hiddenInputName = '#' + id;
|
||||||
|
const selectName = hiddenInputName + '_select';
|
||||||
|
const selectElement = $(selectName);
|
||||||
|
|
||||||
|
selectElement.chosen().val(value);
|
||||||
|
selectElement.chosen().trigger("chosen:updated");
|
||||||
|
|
||||||
|
$(hiddenInputName).val(sanitizeValue(selectElement.val()));
|
||||||
|
|
||||||
|
selectElement.on('change', function (evt, params) {
|
||||||
|
$(hiddenInputName).val(sanitizeValue(selectElement.val()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetValue(id, value) {
|
||||||
|
const selectName = '#' + id + '_select';
|
||||||
|
const selectElement = $(selectName);
|
||||||
|
selectElement.chosen().val(value);
|
||||||
|
selectElement.chosen().trigger("chosen:updated");
|
||||||
|
}
|
||||||
|
|
||||||
|
function doReset() {
|
||||||
|
setTimeout(function () {
|
||||||
|
resetValue('ailabs_forums_post', {{ ailabs_forums_post }});
|
||||||
|
resetValue('ailabs_forums_mention', {{ ailabs_forums_mention }});
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("DOMContentLoaded", function () {
|
||||||
|
$(".chosen-select").chosen();
|
||||||
|
setupSelect('ailabs_forums_post', {{ ailabs_forums_post }});
|
||||||
|
setupSelect('ailabs_forums_mention', {{ ailabs_forums_mention }});
|
||||||
|
});
|
||||||
|
|
||||||
|
function find_username(url) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
popup(url, 760, 570, '_usersearch');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<dl>
|
||||||
|
<dt>
|
||||||
|
<label for="ailabs_enabled">{{ lang('LBL_AILABS_ENABLED') ~ lang('COLON') }}</label>
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
<label><input type="radio" id="ailabs_enabled" name="ailabs_enabled" value="1" {% if ailabs_enabled %}
|
||||||
|
checked="checked" {% endif %}> {{ lang('YES') }}</label>
|
||||||
|
<label><input type="radio" name="ailabs_enabled" value="0" {% if not ailabs_enabled %} checked="checked"
|
||||||
|
{% endif %}> {{ lang('NO') }}</label>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
<dl>
|
||||||
|
<dt><label for="ailabs_username">{L_ENTER_USERNAME}{L_COLON}</label></dt>
|
||||||
|
<dd><input required class="text medium" type="text" id="ailabs_username" name="ailabs_username"
|
||||||
|
value="{{ ailabs_username }}" /></dd>
|
||||||
|
<dd>[ <a href="{U_FIND_USERNAME}" onclick="find_username(this.href); return false;">{L_FIND_USERNAME}</a> ]
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
<dl>
|
||||||
|
<dt>
|
||||||
|
<label for="ailabs_controller">{{ lang('LBL_AILABS_CONTROLLER') ~ lang('COLON') }}</label>
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
<select required id="ailabs_controller" name="ailabs_controller">
|
||||||
|
{% for CONTROLLER in AILABS_CONTROLLER_DESC %}
|
||||||
|
<option value="{{ CONTROLLER.VALUE }}" {% if CONTROLLER.SELECTED %} selected="selected" {% endif %}>
|
||||||
|
{{ CONTROLLER.NAME }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
<dl>
|
||||||
|
<dt>
|
||||||
|
<label for="ailabs_config">{{ lang('LBL_AILABS_CONFIG') ~ lang('COLON') }}</label>
|
||||||
|
<br><span>{{ lang('LBL_AILABS_CONFIG_EXPLAIN') }}</span>
|
||||||
|
<br><br><input type="button" class="button2" value="{{ lang('LBL_AILABS_CONFIG_DEFAULT') }}"
|
||||||
|
onclick="doDefault('ailabs_config', defaultConfigs, true)">
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
<textarea required id="ailabs_config" name="ailabs_config" rows="15">{{ ailabs_config }}</textarea>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
<dl>
|
||||||
|
<dt>
|
||||||
|
<label for="ailabs_template">{{ lang('LBL_AILABS_TEMPLATE') ~ lang('COLON') }}</label>
|
||||||
|
<br><span>{{ lang('LBL_AILABS_TEMPLATE_EXPLAIN') }}</span>
|
||||||
|
<br><br><input type="button" class="button2" value="{{ lang('LBL_AILABS_TEMPLATE_DEFAULT') }}"
|
||||||
|
onclick="doDefault('ailabs_template', defaultTemplate, false)">
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
<textarea required id="ailabs_template" name="ailabs_template" rows="5">{{ ailabs_template }}</textarea>
|
||||||
|
</dd>
|
||||||
|
</dl>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>{{ lang('LBL_AILABS_REPLY_POST_FORUMS') }}</legend>
|
||||||
|
<span>{{ lang('LBL_AILABS_REPLY_POST_FORUMS_EXPLAIN') }}</span>
|
||||||
|
<select id="ailabs_forums_post_select" class="chosen-select" multiple data-placeholder="{{ lang('LBL_AILABS_SELECT_FORUMS') }}"
|
||||||
|
style="width: 100%;">
|
||||||
|
{% for FORUM in AILABS_FORUMS_LIST %}
|
||||||
|
<option value="{{ FORUM.VALUE }}">{{ FORUM.NAME }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>{{ lang('LBL_AILABS_REPLY_QUOTE_FORUMS') }}</legend>
|
||||||
|
<span>{{ lang('LBL_AILABS_REPLY_QUOTE_FORUMS_EXPLAIN') }}</span>
|
||||||
|
<select id="ailabs_forums_mention_select" class="chosen-select" multiple data-placeholder="{{ lang('LBL_AILABS_SELECT_FORUMS') }}"
|
||||||
|
style="width: 100%;">
|
||||||
|
{% for FORUM in AILABS_FORUMS_LIST %}
|
||||||
|
<option value="{{ FORUM.VALUE }}">{{ FORUM.NAME }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset>
|
||||||
|
<legend>{{ lang('ACP_SUBMIT_CHANGES') }}</legend>
|
||||||
|
<p class="submit-buttons">
|
||||||
|
<input type="hidden" id="ailabs_forums_post" name="ailabs_forums_post">
|
||||||
|
<input type="hidden" id="ailabs_forums_mention" name="ailabs_forums_mention">
|
||||||
|
|
||||||
|
<input type="reset" class="button2" value="{{ lang('RESET') }}" onclick="doReset()">
|
||||||
|
<input type="submit" name="submit" class="button1" value="{{ lang('SUBMIT') }}">
|
||||||
|
{S_FORM_TOKEN}
|
||||||
|
</p>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if U_AILABS_VEIW %}
|
||||||
|
|
||||||
|
<table class="tableUsers zebra-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{ lang('LBL_AILABS_USERNAME') }}</th>
|
||||||
|
<th>{{ lang('LBL_AILABS_CONTROLLER') }}</th>
|
||||||
|
<th>{{ lang('LBL_AILABS_REPLY_POST_FORUMS') }}</th>
|
||||||
|
<th>{{ lang('LBL_AILABS_REPLY_QUOTE_FORUMS') }}</th>
|
||||||
|
<th class="centered-text">{{ lang('LBL_AILABS_ENABLED') }}</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for user in U_AILABS_USERS %}
|
||||||
|
<tr>
|
||||||
|
<td><a href="/memberlist.php?mode=viewprofile&u={{ user.user_id }}">{{ user.username }}</a></td>
|
||||||
|
<td>{{ user.controller }}</td>
|
||||||
|
<td>{{ user.forums_post_names }}</td>
|
||||||
|
<td>{{ user.forums_mention_names }}</td>
|
||||||
|
<td class="centered-text"><input type="checkbox" onclick="return false" {{ user.enabled ? 'checked' : '' }}>
|
||||||
|
</td>
|
||||||
|
<td class="centered-text">
|
||||||
|
<a href="{{ user.U_EDIT }}">{{ ICON_EDIT }}</a>
|
||||||
|
<a href="{{ user.U_DELETE }}">{{ ICON_DELETE }}</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% INCLUDE 'overall_footer.html' %}
|
33
privet/ailabs/composer.json
Normal file
33
privet/ailabs/composer.json
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
"name": "privet/ailabs",
|
||||||
|
"type": "phpbb-extension",
|
||||||
|
"description": "AI Labs",
|
||||||
|
"homepage": "https://privet.fun",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"time": "2023-02-12",
|
||||||
|
"keywords": [
|
||||||
|
"phpbb",
|
||||||
|
"extension",
|
||||||
|
"ailabs",
|
||||||
|
"chatgpt",
|
||||||
|
"ai"
|
||||||
|
],
|
||||||
|
"license": "GPL-2.0-only",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "privet.fun",
|
||||||
|
"homepage": "https://privet.fun"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.1",
|
||||||
|
"phpbb/phpbb": ">=3.2",
|
||||||
|
"composer/installers": "~1.0.0"
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"display-name": "AI Labs",
|
||||||
|
"soft-require": {
|
||||||
|
"phpbb/phpbb": ">=3.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
privet/ailabs/config/routing.yml
Normal file
21
privet/ailabs/config/routing.yml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
privet_chatgpt_page:
|
||||||
|
path: /ailabs/chatgpt
|
||||||
|
defaults: { _controller: privet.ailabs.controller_chatgpt:execute }
|
||||||
|
|
||||||
|
privet_dalle_page:
|
||||||
|
path: /ailabs/dalle
|
||||||
|
defaults: { _controller: privet.ailabs.controller_dalle:execute }
|
||||||
|
|
||||||
|
privet_stablediffusion_page:
|
||||||
|
path: /ailabs/stablediffusion
|
||||||
|
defaults: { _controller: privet.ailabs.controller_stablediffusion:execute }
|
||||||
|
|
||||||
|
privet_scriptexecute_page:
|
||||||
|
path: /ailabs/scriptexecute
|
||||||
|
defaults: { _controller: privet.ailabs.controller_scriptexecute:execute }
|
||||||
|
|
||||||
|
privet_ailabs_view_log_controller_page:
|
||||||
|
path: /ailabs/log/{post_id}
|
||||||
|
defaults: { _controller: privet.ailabs.controller_log:view_log }
|
||||||
|
requirements:
|
||||||
|
post_id: \d+
|
119
privet/ailabs/config/services.yml
Normal file
119
privet/ailabs/config/services.yml
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
imports:
|
||||||
|
- { resource: tables.yml }
|
||||||
|
|
||||||
|
services:
|
||||||
|
privet.ailabs.acp_controller:
|
||||||
|
class: privet\ailabs\controller\acp_controller
|
||||||
|
arguments:
|
||||||
|
- '@config'
|
||||||
|
- '@dbal.conn'
|
||||||
|
- '@language'
|
||||||
|
- '@log'
|
||||||
|
- '@notification_manager'
|
||||||
|
- '@pagination'
|
||||||
|
- '@request'
|
||||||
|
- '@template'
|
||||||
|
- '@user'
|
||||||
|
- '%core.root_path%'
|
||||||
|
- '%privet.ailabs.tables.users%'
|
||||||
|
|
||||||
|
privet.ailabs.listener:
|
||||||
|
class: privet\ailabs\event\listener
|
||||||
|
arguments:
|
||||||
|
- '@user'
|
||||||
|
- '@auth'
|
||||||
|
- '@dbal.conn'
|
||||||
|
- '@controller.helper'
|
||||||
|
- '@language'
|
||||||
|
- '@request'
|
||||||
|
- '%core.root_path%'
|
||||||
|
- '%core.php_ext%'
|
||||||
|
- '%privet.ailabs.tables.users%'
|
||||||
|
- '%privet.ailabs.tables.jobs%'
|
||||||
|
tags:
|
||||||
|
- { name: event.listener }
|
||||||
|
|
||||||
|
privet.ailabs.controller_chatgpt:
|
||||||
|
class: privet\ailabs\controller\chatgpt
|
||||||
|
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_dalle:
|
||||||
|
class: privet\ailabs\controller\dalle
|
||||||
|
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_stablediffusion:
|
||||||
|
class: privet\ailabs\controller\stablediffusion
|
||||||
|
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:
|
||||||
|
class: privet\ailabs\controller\scriptexecute
|
||||||
|
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_log:
|
||||||
|
class: privet\ailabs\controller\log
|
||||||
|
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%'
|
3
privet/ailabs/config/tables.yml
Normal file
3
privet/ailabs/config/tables.yml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
parameters:
|
||||||
|
privet.ailabs.tables.users: '%core.table_prefix%ailabs_users'
|
||||||
|
privet.ailabs.tables.jobs: '%core.table_prefix%ailabs_jobs'
|
305
privet/ailabs/controller/acp_controller.php
Normal file
305
privet/ailabs/controller/acp_controller.php
Normal file
|
@ -0,0 +1,305 @@
|
||||||
|
<?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;
|
||||||
|
|
||||||
|
class acp_controller //implements acp_interface
|
||||||
|
{
|
||||||
|
protected $config;
|
||||||
|
protected $db;
|
||||||
|
protected $language;
|
||||||
|
protected $log;
|
||||||
|
protected $notification_manager;
|
||||||
|
protected $pagination;
|
||||||
|
protected $request;
|
||||||
|
protected $template;
|
||||||
|
protected $user;
|
||||||
|
protected $root_path;
|
||||||
|
protected $ailabs_users_table;
|
||||||
|
|
||||||
|
protected $id;
|
||||||
|
protected $mode;
|
||||||
|
protected $action;
|
||||||
|
protected $submit;
|
||||||
|
protected $u_action;
|
||||||
|
protected $user_id;
|
||||||
|
protected $ailabs_enabled;
|
||||||
|
protected $tpr_ailabs;
|
||||||
|
|
||||||
|
protected $desc_contollers = [
|
||||||
|
'/ailabs/chatgpt',
|
||||||
|
'/ailabs/dalle',
|
||||||
|
'/ailabs/stablediffusion',
|
||||||
|
'/ailabs/scriptexecute'
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
\phpbb\config\config $config,
|
||||||
|
\phpbb\db\driver\driver_interface $db,
|
||||||
|
\phpbb\language\language $language,
|
||||||
|
\phpbb\log\log $log,
|
||||||
|
\phpbb\notification\manager $notification_manager,
|
||||||
|
\phpbb\pagination $pagination,
|
||||||
|
\phpbb\request\request $request,
|
||||||
|
\phpbb\template\template $template,
|
||||||
|
\phpbb\user $user,
|
||||||
|
$root_path,
|
||||||
|
$ailabs_users_table
|
||||||
|
) {
|
||||||
|
$this->config = $config;
|
||||||
|
$this->db = $db;
|
||||||
|
$this->language = $language;
|
||||||
|
$this->log = $log;
|
||||||
|
$this->notification_manager = $notification_manager;
|
||||||
|
$this->pagination = $pagination;
|
||||||
|
$this->request = $request;
|
||||||
|
$this->template = $template;
|
||||||
|
$this->user = $user;
|
||||||
|
$this->root_path = $root_path;
|
||||||
|
$this->ailabs_users_table = $ailabs_users_table;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get_acp_data($id, $mode, $action, $submit, $u_action)
|
||||||
|
{
|
||||||
|
$this->id = $id;
|
||||||
|
$this->mode = $mode;
|
||||||
|
$this->action = $action;
|
||||||
|
$this->submit = $submit;
|
||||||
|
$this->u_action = $u_action;
|
||||||
|
$this->user_id = $this->request->variable('user_id', 0);
|
||||||
|
$this->ailabs_enabled = !empty($this->config['ailabs_enabled']) ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit_add()
|
||||||
|
{
|
||||||
|
$username = utf8_normalize_nfc($this->request->variable('ailabs_username', '', true));
|
||||||
|
$new_user_id = $this->find_user_id($username);
|
||||||
|
|
||||||
|
if ($this->action == 'edit' && empty($this->user_id)) {
|
||||||
|
trigger_error($this->language->lang('AILABS_USER_EMPTY') . adm_back_link($this->u_action), E_USER_WARNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
$edit = [];
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'user_id' => $new_user_id,
|
||||||
|
'controller' => $this->request->variable('ailabs_controller', ''),
|
||||||
|
'config' => utf8_normalize_nfc($this->request->variable('ailabs_config', '', true)),
|
||||||
|
'template' => utf8_normalize_nfc($this->request->variable('ailabs_template', '', true)),
|
||||||
|
'forums_post' => $this->request->variable('ailabs_forums_post', ''),
|
||||||
|
'forums_mention' => $this->request->variable('ailabs_forums_mention', ''),
|
||||||
|
'enabled' => $this->request->variable('ailabs_enabled', true),
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($this->submit) {
|
||||||
|
|
||||||
|
if (empty($new_user_id)) {
|
||||||
|
trigger_error($this->language->lang('AILABS_USER_NOT_FOUND', $username) . adm_back_link($this->u_action), E_USER_WARNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
$configs_count = $this->count_configs($new_user_id);
|
||||||
|
|
||||||
|
if (($this->action == 'add' && $configs_count > 0) ||
|
||||||
|
($this->action == 'edit' && $new_user_id != $this->user_id && $configs_count > 0)
|
||||||
|
) {
|
||||||
|
trigger_error($this->language->lang('AILABS_USER_ALREADY_CONFIGURED', $username) . adm_back_link($this->u_action), E_USER_WARNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($data['forums_post']) && empty($data['forums_mention'])) {
|
||||||
|
trigger_error($this->language->lang('AILABS_SPECIFY_POST_OR_MENTION') . adm_back_link($this->u_action), E_USER_WARNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($error)) {
|
||||||
|
$sql_ary = [
|
||||||
|
'user_id' => (int) $data['user_id'],
|
||||||
|
'controller' => (string) $data['controller'],
|
||||||
|
'config' => (string) html_entity_decode($data['config']),
|
||||||
|
'template' => (string) html_entity_decode($data['template']),
|
||||||
|
'forums_post' => (string) html_entity_decode($data['forums_post']),
|
||||||
|
'forums_mention' => (string) html_entity_decode($data['forums_mention']),
|
||||||
|
'enabled' => (bool) $data['enabled']
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($this->action == 'add') {
|
||||||
|
$sql = 'INSERT INTO ' . $this->ailabs_users_table . ' ' . $this->db->sql_build_array('INSERT', $sql_ary);
|
||||||
|
$this->db->sql_query($sql);
|
||||||
|
|
||||||
|
$log_lang = 'LOG_ACP_AILABS_ADDED';
|
||||||
|
$this->log->add('admin', $this->user->data['user_id'], $this->user->ip, $log_lang, false, [$data['user_id']]);
|
||||||
|
|
||||||
|
trigger_error($this->language->lang('ACP_AILABS_ADDED') . adm_back_link($this->u_action), E_USER_NOTICE);
|
||||||
|
} else if ($this->action == 'edit') {
|
||||||
|
$sql = 'UPDATE ' . $this->ailabs_users_table . '
|
||||||
|
SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . '
|
||||||
|
WHERE user_id = ' . (int) $this->user_id;
|
||||||
|
$this->db->sql_query($sql);
|
||||||
|
|
||||||
|
$log_lang = 'LOG_ACP_AILABS_EDITED';
|
||||||
|
$this->log->add('admin', $this->user->data['user_id'], $this->user->ip, $log_lang, false, [$data['user_id']]);
|
||||||
|
|
||||||
|
trigger_error($this->language->lang('ACP_AILABS_UPDATED') . adm_back_link($this->u_action), E_USER_NOTICE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($this->action == 'edit') {
|
||||||
|
$sql = 'SELECT * FROM ' . $this->ailabs_users_table . ' WHERE user_id = ' . (int) $this->user_id;
|
||||||
|
$result = $this->db->sql_query($sql);
|
||||||
|
$row = $this->db->sql_fetchrow($result);
|
||||||
|
|
||||||
|
$edit = [
|
||||||
|
'ailabs_user_id' => (int) $row['user_id'],
|
||||||
|
'ailabs_username' => (string) $this->find_user_name((int) $this->user_id),
|
||||||
|
'ailabs_controller' => (string) $row['controller'],
|
||||||
|
'ailabs_config' => (string) $row['config'],
|
||||||
|
'ailabs_template' => (string) $row['template'],
|
||||||
|
'ailabs_forums_post' => (string) $row['forums_post'],
|
||||||
|
'ailabs_forums_mention' => (string) $row['forums_mention'],
|
||||||
|
'ailabs_enabled' => (bool) $row['enabled']
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->db->sql_freeresult($result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = 'SELECT forum_id, forum_name FROM ' . FORUMS_TABLE . ' ORDER BY left_id';
|
||||||
|
$result = $this->db->sql_query($sql);
|
||||||
|
while ($row = $this->db->sql_fetchrow($result)) {
|
||||||
|
$this->template->assign_block_vars('AILABS_FORUMS_LIST', [
|
||||||
|
'VALUE' => $row['forum_id'],
|
||||||
|
'NAME' => $row['forum_name']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$this->db->sql_freeresult($result);
|
||||||
|
|
||||||
|
foreach ($this->desc_contollers as $key => $value) {
|
||||||
|
$controller = explode("/", $value);
|
||||||
|
$name = end($controller);
|
||||||
|
$this->template->assign_block_vars('AILABS_CONTROLLER_DESC', [
|
||||||
|
'NAME' => $name,
|
||||||
|
'VALUE' => $value,
|
||||||
|
'SELECTED' => (!empty($edit) && $value === $edit['ailabs_controller'])
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
global $phpbb_root_path, $phpEx;
|
||||||
|
|
||||||
|
$this->template->assign_vars(
|
||||||
|
array_merge(
|
||||||
|
$edit,
|
||||||
|
[
|
||||||
|
'S_ERROR' => isset($error) ? $error : '',
|
||||||
|
'U_AILABS_ADD_EDIT' => true,
|
||||||
|
'U_ACTION' => $this->action == 'add' ? $this->u_action . '&action=add' : $this->u_action . '&action=edit&user_id=' . $this->user_id,
|
||||||
|
'U_BACK' => $this->u_action,
|
||||||
|
'U_FIND_USERNAME' => append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=searchuser&form=ailabs_configuration&field=ailabs_username&select_single=true'),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete($user_id)
|
||||||
|
{
|
||||||
|
if (empty($user_id)) {
|
||||||
|
trigger_error('AILABS_USER_EMPTY' . adm_back_link($this->u_action), E_USER_WARNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = 'DELETE FROM ' . $this->ailabs_users_table . ' WHERE user_id = ' . (int) $user_id;
|
||||||
|
$result = $this->db->sql_query($sql);
|
||||||
|
$this->db->sql_freeresult($result);
|
||||||
|
|
||||||
|
$this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_ACP_AILABS_DELETED');
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function acp_ailabs_main()
|
||||||
|
{
|
||||||
|
$sql = 'SELECT a.*, u.username, ' .
|
||||||
|
'(SELECT GROUP_CONCAT(f.forum_name SEPARATOR ", ") FROM phpbblt_forums f WHERE INSTR(a.forums_post, CONCAT(\'"\',f.forum_id,\'"\')) > 0) as forums_post_names, ' .
|
||||||
|
'(SELECT GROUP_CONCAT(f.forum_name SEPARATOR ", ") FROM phpbblt_forums f WHERE INSTR(a.forums_mention, CONCAT(\'"\',f.forum_id,\'"\')) > 0) as forums_mention_names ' .
|
||||||
|
'FROM ' . $this->ailabs_users_table . ' a ' .
|
||||||
|
'LEFT JOIN ' . USERS_TABLE . ' u ON u.user_id = a.user_id ' .
|
||||||
|
'ORDER BY u.username';
|
||||||
|
|
||||||
|
$result = $this->db->sql_query($sql);
|
||||||
|
|
||||||
|
$ailabs_users = [];
|
||||||
|
|
||||||
|
while ($row = $this->db->sql_fetchrow($result)) {
|
||||||
|
if (empty($row)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$controller = explode("/", $row['controller']);
|
||||||
|
$row['controller'] = end($controller);
|
||||||
|
$row['U_EDIT'] = $this->u_action . '&action=edit&user_id=' . $row['user_id'] . '&hash=' . generate_link_hash('acp_ailabs');
|
||||||
|
$row['U_DELETE'] = $this->u_action . '&action=delete&user_id=' . $row['user_id'] . '&username=' . $row['username'] . '&hash=' . generate_link_hash('acp_ailabs');
|
||||||
|
|
||||||
|
$ailabs_users[] = (array) $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->db->sql_freeresult($result);
|
||||||
|
|
||||||
|
$template_vars = [
|
||||||
|
'U_AILABS_USERS' => $ailabs_users,
|
||||||
|
'U_ADD' => $this->u_action . '&action=add',
|
||||||
|
'U_ACTION' => $this->u_action,
|
||||||
|
'U_AILABS_VEIW' => true
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this->template->assign_vars($template_vars);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function find_user_id($username)
|
||||||
|
{
|
||||||
|
$user_id = null;
|
||||||
|
if (!empty($username)) {
|
||||||
|
$where = ['username' => $username];
|
||||||
|
$sql = 'SELECT user_id FROM ' . USERS_TABLE . ' WHERE ' . $this->db->sql_build_array('SELECT', $where);
|
||||||
|
$result = $this->db->sql_query($sql);
|
||||||
|
$row = $this->db->sql_fetchrow($result);
|
||||||
|
if (!empty($row) && !empty($row['user_id']))
|
||||||
|
$user_id = $row['user_id'];
|
||||||
|
$this->db->sql_freeresult($result);
|
||||||
|
}
|
||||||
|
return $user_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function find_user_name($user_id)
|
||||||
|
{
|
||||||
|
$username = null;
|
||||||
|
if (!empty($user_id)) {
|
||||||
|
$where = ['user_id' => $user_id];
|
||||||
|
$sql = 'SELECT username FROM ' . USERS_TABLE . ' WHERE ' . $this->db->sql_build_array('SELECT', $where);
|
||||||
|
$result = $this->db->sql_query($sql);
|
||||||
|
$row = $this->db->sql_fetchrow($result);
|
||||||
|
if (!empty($row) && !empty($row['username']))
|
||||||
|
$username = $row['username'];
|
||||||
|
$this->db->sql_freeresult($result);
|
||||||
|
}
|
||||||
|
return $username;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function count_configs($user_id)
|
||||||
|
{
|
||||||
|
$count = 0;
|
||||||
|
if (!empty($user_id)) {
|
||||||
|
$where = ['user_id' => $user_id];
|
||||||
|
$sql = 'SELECT count(*) as cnt FROM ' . $this->ailabs_users_table . ' WHERE ' . $this->db->sql_build_array('SELECT', $where);
|
||||||
|
$result = $this->db->sql_query($sql);
|
||||||
|
$row = $this->db->sql_fetchrow($result);
|
||||||
|
if (!empty($row) && !empty($row['cnt']))
|
||||||
|
$count = $row['cnt'];
|
||||||
|
$this->db->sql_freeresult($result);
|
||||||
|
}
|
||||||
|
return $count;
|
||||||
|
}
|
||||||
|
}
|
291
privet/ailabs/controller/chatgpt.php
Normal file
291
privet/ailabs/controller/chatgpt.php
Normal file
|
@ -0,0 +1,291 @@
|
||||||
|
<?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 Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use privet\ailabs\includes\AIController;
|
||||||
|
use privet\ailabs\includes\resultParse;
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
config
|
||||||
|
{
|
||||||
|
"api_key": "<api-key>",
|
||||||
|
"url_chat": "https://api.openai.com/v1/chat/completions",
|
||||||
|
"model": "gpt-3.5-turbo",
|
||||||
|
"temperature": 0.9,
|
||||||
|
"max_tokens": 4096,
|
||||||
|
"message_tokens": 1024,
|
||||||
|
"top_p": 1,
|
||||||
|
"frequency_penalty": 0,
|
||||||
|
"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_tokens": 16
|
||||||
|
}
|
||||||
|
|
||||||
|
template
|
||||||
|
{info}[quote={poster_name} post_id={post_id} user_id={poster_id}]{request}[/quote]{response}
|
||||||
|
*/
|
||||||
|
|
||||||
|
class chatgpt extends AIController
|
||||||
|
{
|
||||||
|
// https://platform.openai.com/docs/api-reference/chat/create#chat/create-max_tokens
|
||||||
|
// By default, the number of tokens the model can return will be (4096 - prompt tokens).
|
||||||
|
protected $max_tokens = 4096;
|
||||||
|
protected $message_tokens = 2048;
|
||||||
|
|
||||||
|
protected function process()
|
||||||
|
{
|
||||||
|
$this->language->add_lang('common', 'privet/ailabs');
|
||||||
|
|
||||||
|
$this->job['status'] = 'exec';
|
||||||
|
|
||||||
|
$set = [
|
||||||
|
'status' => $this->job['status'],
|
||||||
|
'log' => json_encode($this->log)
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->job_update($set);
|
||||||
|
$this->post_update($this->job);
|
||||||
|
|
||||||
|
if (!empty($this->cfg->message_tokens)) {
|
||||||
|
$this->message_tokens = $this->cfg->message_tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($this->cfg->max_tokens)) {
|
||||||
|
$this->max_tokens = (int)$this->cfg->max_tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
$prefix_tokens = empty($this->cfg->prefix_tokens) ? 0 : $this->cfg->prefix_tokens;
|
||||||
|
|
||||||
|
$api_key = $this->cfg->api_key;
|
||||||
|
$this->cfg->api_key = null;
|
||||||
|
$api = new GenericCurl($api_key);
|
||||||
|
|
||||||
|
$this->job['status'] = 'fail';
|
||||||
|
$response = $this->language->lang('AILABS_ERROR_CHECK_LOGS');
|
||||||
|
$api_response = null;
|
||||||
|
$request_tokens = null;
|
||||||
|
$response_tokens = null;
|
||||||
|
|
||||||
|
$total_replaced = 0;
|
||||||
|
$original_request = $this->job['request'];
|
||||||
|
|
||||||
|
if ($total_replaced > 0) {
|
||||||
|
$this->job['request'] = trim(str_replace(' ', ' ', $this->job['request']));
|
||||||
|
$this->log['request.original'] = $original_request;
|
||||||
|
$this->log['request.adjusted'] = $this->job['request'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$messages = [];
|
||||||
|
$info = null;
|
||||||
|
$posts = [];
|
||||||
|
$post_first_taken = null;
|
||||||
|
$post_first_discarded = null;
|
||||||
|
|
||||||
|
if ($this->job['post_mode'] == 'quote') {
|
||||||
|
$history = ['post_text' => $this->job['post_text']];
|
||||||
|
|
||||||
|
$pattern = '/<QUOTE\sauthor="' . $this->job['ailabs_username'] . '"\spost_id="(.*)"\stime="(.*)"\suser_id="' . $this->job['ailabs_user_id'] . '">/';
|
||||||
|
|
||||||
|
$this->log['history.pattern'] = $pattern;
|
||||||
|
$this->log_flush();
|
||||||
|
|
||||||
|
$history_tokens = 0;
|
||||||
|
$round = -1;
|
||||||
|
do {
|
||||||
|
$round++;
|
||||||
|
$matches = null;
|
||||||
|
preg_match_all(
|
||||||
|
$pattern,
|
||||||
|
$history['post_text'],
|
||||||
|
$matches
|
||||||
|
);
|
||||||
|
|
||||||
|
$history = null;
|
||||||
|
|
||||||
|
if ($matches != null && !empty($matches) && !empty($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 ' .
|
||||||
|
'FROM ' . $this->jobs_table . ' j ' .
|
||||||
|
'JOIN ' . POSTS_TABLE . ' p ON p.post_id = j.post_id ' .
|
||||||
|
'WHERE ' . $this->db->sql_build_array('SELECT', ['response_post_id' => $postid]);
|
||||||
|
$result = $this->db->sql_query($sql);
|
||||||
|
$history = $this->db->sql_fetchrow($result);
|
||||||
|
$this->db->sql_freeresult($result);
|
||||||
|
|
||||||
|
if (!empty($history)) {
|
||||||
|
$count_tokens = $history['request_tokens'] + $history['response_tokens'];
|
||||||
|
|
||||||
|
$discard = $this->max_tokens < ($this->message_tokens + $history_tokens + $count_tokens);
|
||||||
|
|
||||||
|
$posts[] = [
|
||||||
|
'postid' => $postid,
|
||||||
|
'request_tokens' => $history['request_tokens'],
|
||||||
|
'response_tokens' => $history['response_tokens'],
|
||||||
|
'runnig_total_tokens' => $history_tokens + $count_tokens,
|
||||||
|
'discard' => $discard
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($discard) {
|
||||||
|
$post_first_discarded = $postid;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$post_first_taken = $postid;
|
||||||
|
$history_tokens += $count_tokens;
|
||||||
|
|
||||||
|
$post_messages = [
|
||||||
|
['role' => 'user', 'content' => trim($history['request'])],
|
||||||
|
['role' => 'assistant', 'content' => trim($history['response'])],
|
||||||
|
];
|
||||||
|
|
||||||
|
$messages = [
|
||||||
|
...$post_messages,
|
||||||
|
...$messages
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (!empty($history));
|
||||||
|
|
||||||
|
if (!empty($posts)) {
|
||||||
|
$this->log['history.posts'] = $posts;
|
||||||
|
$this->log_flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($this->cfg->prefix)) {
|
||||||
|
$messages = [
|
||||||
|
['role' => 'system', 'content' => $this->cfg->prefix],
|
||||||
|
...$messages
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$messages = [
|
||||||
|
...$messages,
|
||||||
|
['role' => 'user', 'content' => trim($this->job['request'])]
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->log['request.messages'] = $messages;
|
||||||
|
$this->log_flush();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// https://api.openai.com/v1/chat/completions
|
||||||
|
$api_result = $api->sendRequest($this->cfg->url_chat, 'POST', [
|
||||||
|
'model' => $this->cfg->model,
|
||||||
|
'messages' => $messages,
|
||||||
|
'temperature' => (float) $this->cfg->temperature,
|
||||||
|
// https://platform.openai.com/docs/api-reference/chat/create#chat/create-max_tokens
|
||||||
|
// By default, the number of tokens the model can return will be (4096 - prompt tokens).
|
||||||
|
// 'max_tokens' => (int) $this->cfg->max_tokens,
|
||||||
|
'frequency_penalty' => (float) $this->cfg->frequency_penalty,
|
||||||
|
'presence_penalty' => (float)$this->cfg->presence_penalty,
|
||||||
|
]);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Response example:
|
||||||
|
{
|
||||||
|
'id': 'chatcmpl-1p2RTPYSDSRi0xRviKjjilqrWU5Vr',
|
||||||
|
'object': 'chat.completion',
|
||||||
|
'created': 1677649420,
|
||||||
|
'model': 'gpt-3.5-turbo',
|
||||||
|
'usage': {'prompt_tokens': 56, 'completion_tokens': 31, 'total_tokens': 87},
|
||||||
|
'choices': [
|
||||||
|
{
|
||||||
|
'message': {
|
||||||
|
'role': 'assistant',
|
||||||
|
'content': 'The 2020 World Series was played in Arlington, Texas at the Globe Life Field, which was the new home stadium for the Texas Rangers.'
|
||||||
|
},
|
||||||
|
'finish_reason': 'stop',
|
||||||
|
'index': 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
$json = json_decode($api_result);
|
||||||
|
$this->log['response'] = $json;
|
||||||
|
$this->log['response.codes'] = $api->responseCodes;
|
||||||
|
|
||||||
|
$this->log_flush();
|
||||||
|
|
||||||
|
if (
|
||||||
|
empty($json->object) ||
|
||||||
|
empty($json->choices) ||
|
||||||
|
$json->object != 'chat.completion' ||
|
||||||
|
!in_array(200, $api->responseCodes)
|
||||||
|
) {
|
||||||
|
} else {
|
||||||
|
$this->job['status'] = 'ok';
|
||||||
|
$api_response = $json->choices[0]->message->content;
|
||||||
|
$response = $api_response;
|
||||||
|
$request_tokens = $json->usage->prompt_tokens;
|
||||||
|
$response_tokens = $json->usage->completion_tokens;
|
||||||
|
if ($history_tokens > 0 || $prefix_tokens > 0) {
|
||||||
|
$this->log['request.tokens.raw'] = $request_tokens;
|
||||||
|
$this->log['request.tokens.adjusted'] = $request_tokens - $history_tokens - $prefix_tokens;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->log['exception'] = $e->getMessage();
|
||||||
|
$this->log_flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->log['finish'] = date('Y-m-d H:i:s');
|
||||||
|
|
||||||
|
if (!empty($posts)) {
|
||||||
|
$discarded = '';
|
||||||
|
if ($post_first_discarded != null) {
|
||||||
|
$discarded = $this->language->lang('AILABS_POSTS_DISCARDED', $post_first_discarded);
|
||||||
|
}
|
||||||
|
$total_posts_count = count($posts) * 2 + 2;
|
||||||
|
$total_tokens_used_count = $request_tokens + $response_tokens;
|
||||||
|
$info = $this->language->lang(
|
||||||
|
'AILABS_DISCARDED_INFO',
|
||||||
|
$post_first_taken,
|
||||||
|
$total_posts_count,
|
||||||
|
$discarded,
|
||||||
|
$total_tokens_used_count,
|
||||||
|
$this->max_tokens
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$resultParse = new resultParse();
|
||||||
|
$resultParse->message = $response;
|
||||||
|
$resultParse->info = $info;
|
||||||
|
|
||||||
|
$response = $this->replace_vars($this->job, $resultParse);
|
||||||
|
|
||||||
|
$data = $this->post_response($this->job, $response);
|
||||||
|
|
||||||
|
$this->job['response_time'] = time();
|
||||||
|
$this->job['response_post_id'] = $data['post_id'];
|
||||||
|
|
||||||
|
$set = [
|
||||||
|
'status' => $this->job['status'],
|
||||||
|
'attempts' => $this->job['attempts'] + 1,
|
||||||
|
'response_time' => $this->job['response_time'],
|
||||||
|
'response' => $api_response,
|
||||||
|
'request_tokens' => $request_tokens - $history_tokens - $prefix_tokens,
|
||||||
|
'response_post_id' => $this->job['response_post_id'],
|
||||||
|
'response_tokens' => $response_tokens,
|
||||||
|
'log' => json_encode($this->log)
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->job_update($set);
|
||||||
|
$this->post_update($this->job);
|
||||||
|
|
||||||
|
return new JsonResponse($this->log);
|
||||||
|
}
|
||||||
|
}
|
176
privet/ailabs/controller/dalle.php
Normal file
176
privet/ailabs/controller/dalle.php
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
<?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;
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
config
|
||||||
|
{
|
||||||
|
"api_key": "<api-key>",
|
||||||
|
"url_generations": "https://api.openai.com/v1/images/generations",
|
||||||
|
"url_variations": "https://api.openai.com/v1/images/variations",
|
||||||
|
"n": 1,
|
||||||
|
"size": "1024x1024",
|
||||||
|
"response_format": "url"
|
||||||
|
}
|
||||||
|
|
||||||
|
template
|
||||||
|
[quote={poster_name} post_id={post_id} user_id={poster_id}]{request}[/quote]
|
||||||
|
{response}{attachments}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
class dalle extends GenericController
|
||||||
|
{
|
||||||
|
protected function init()
|
||||||
|
{
|
||||||
|
$opts = parent::init();
|
||||||
|
|
||||||
|
$opts += ['size' => $this->cfg->size];
|
||||||
|
|
||||||
|
$count_replaced = 0;
|
||||||
|
|
||||||
|
// User can explicitly override image size.
|
||||||
|
// Comment out code below to disable this feature.
|
||||||
|
foreach (['256x256', '512x512', '1024x1024'] as $known_size) {
|
||||||
|
$count_replaced = 0;
|
||||||
|
$this->job['request'] = trim(str_replace($known_size, '', $this->job['request'], $count_replaced));
|
||||||
|
if ($count_replaced > 0) {
|
||||||
|
if ($opts['size'] != $known_size) {
|
||||||
|
$this->log['size.adjusted'] = $known_size;
|
||||||
|
$opts['size'] = $known_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($count_replaced > 0) {
|
||||||
|
$this->log['request.adjusted'] = $this->job['request'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $opts;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function prepare($opts)
|
||||||
|
{
|
||||||
|
if (filter_var($this->job['request'], FILTER_VALIDATE_URL)) {
|
||||||
|
// https://platform.openai.com/docs/api-reference/images/create-variation
|
||||||
|
// The image to use as the basis for the variation(s). Must be a valid PNG file, less than 4MB, and square.
|
||||||
|
$image = curl_file_create($this->job['request'], 'image/png');
|
||||||
|
$opts += [
|
||||||
|
'image' => $image,
|
||||||
|
'n' => $this->cfg->n,
|
||||||
|
'response_format' => $this->cfg->response_format,
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
$opts += [
|
||||||
|
'prompt' => trim($this->job['request']),
|
||||||
|
'n' => $this->cfg->n,
|
||||||
|
'response_format' => $this->cfg->response_format,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $opts;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function submit($opts): resultSubmit
|
||||||
|
{
|
||||||
|
$api = new GenericCurl($this->cfg->api_key);
|
||||||
|
$this->cfg->api_key = null;
|
||||||
|
|
||||||
|
$result = new resultSubmit();
|
||||||
|
|
||||||
|
if (empty($opts['image'])) {
|
||||||
|
// https://api.openai.com/v1/images/generations
|
||||||
|
$result->response = $api->sendRequest($this->cfg->url_generations, 'POST', $opts);
|
||||||
|
} else {
|
||||||
|
// https://api.openai.com/v1/images/variations
|
||||||
|
$result->response = $api->sendRequest($this->cfg->url_variations, 'POST', $opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result->responseCodes = $api->responseCodes;
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function parse(resultSubmit $resultSubmit): resultParse
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
Response example for response_format="url":
|
||||||
|
{
|
||||||
|
"created": 1589478378,
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"url": "https://..."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://..."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Response example for response_format="b64_json":
|
||||||
|
{
|
||||||
|
"created": 1589478378,
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"b64_json": "..."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"b64_json": "..."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
$json = json_decode($resultSubmit->response);
|
||||||
|
$images = null;
|
||||||
|
$message = null;
|
||||||
|
|
||||||
|
if (
|
||||||
|
empty($json->data) ||
|
||||||
|
!empty($json->error) ||
|
||||||
|
!in_array(200, $resultSubmit->responseCodes)
|
||||||
|
) {
|
||||||
|
if (!empty($json->error)) {
|
||||||
|
$message = $json->error->message;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->job['status'] = 'ok';
|
||||||
|
$images = [];
|
||||||
|
$ind = 0;
|
||||||
|
foreach ($json->data as $item) {
|
||||||
|
// Image name returned back by Open AI API in url is not always can be parsed by internal phpBB routines.
|
||||||
|
// Use b64_json instead
|
||||||
|
if ($this->cfg->response_format == 'url') {
|
||||||
|
array_push($images, $item->url);
|
||||||
|
} else {
|
||||||
|
$filename = $this->save_base64_to_temp_file($item->b64_json, $ind);
|
||||||
|
$item->b64_json = '<reducted>';
|
||||||
|
array_push($images, $filename);
|
||||||
|
}
|
||||||
|
$ind++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = new resultParse();
|
||||||
|
$result->json = $json;
|
||||||
|
$result->images = $images;
|
||||||
|
$result->message = $message;
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
51
privet/ailabs/controller/log.php
Normal file
51
privet/ailabs/controller/log.php
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
<?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 phpbb\exception\http_exception;
|
||||||
|
use privet\ailabs\includes\AIController;
|
||||||
|
|
||||||
|
class log extends AIController
|
||||||
|
{
|
||||||
|
public function view_log($post_id)
|
||||||
|
{
|
||||||
|
if ($this->user->data['user_id'] == ANONYMOUS || $this->user->data['is_bot']) {
|
||||||
|
throw new http_exception(401);
|
||||||
|
}
|
||||||
|
|
||||||
|
$where = [
|
||||||
|
'post_id' => $post_id
|
||||||
|
];
|
||||||
|
|
||||||
|
$sql = 'SELECT * ' . 'FROM ' . $this->jobs_table . ' WHERE ' . $this->db->sql_build_array('SELECT', $where);
|
||||||
|
$result = $this->db->sql_query($sql);
|
||||||
|
$data = $this->db->sql_fetchrowset($result);
|
||||||
|
$this->db->sql_freeresult($result);
|
||||||
|
|
||||||
|
if (!empty($data)) {
|
||||||
|
foreach($data as &$row)
|
||||||
|
{
|
||||||
|
$row['poster_user_url'] = '/' . append_sid("memberlist.$this->php_ext", 'mode=viewprofile&u=' . $row['poster_id'], true, '');
|
||||||
|
$row['ailabs_user_url'] = '/' . append_sid("memberlist.$this->php_ext", 'mode=viewprofile&u=' . $row['ailabs_user_id'], true, '');
|
||||||
|
if (!empty($row['response_post_id'])) {
|
||||||
|
$row['response_url'] = '/viewtopic.php?p=' . $row['response_post_id'] . '#p' . $row['response_post_id'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->template->assign_block_vars('ailabs_log', [
|
||||||
|
'LOGS' => $data
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->helper->render('post_ailabs_log.html', 'AI Labs Log');
|
||||||
|
}
|
||||||
|
}
|
125
privet/ailabs/controller/scriptexecute.php
Normal file
125
privet/ailabs/controller/scriptexecute.php
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
<?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\GenericController;
|
||||||
|
use privet\ailabs\includes\resultSubmit;
|
||||||
|
use privet\ailabs\includes\resultParse;
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
config
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"script": "<script name to execute>",
|
||||||
|
"logs": "<folder where job config and responses go>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template
|
||||||
|
[quote={poster_name} post_id={post_id} user_id={poster_id}]{request}[/quote]
|
||||||
|
{response}{attachments}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
class scriptexecute extends GenericController
|
||||||
|
{
|
||||||
|
protected function submit($opts): resultSubmit
|
||||||
|
{
|
||||||
|
$opts = (array)(clone $this->cfg);
|
||||||
|
$opts['prompt'] = trim($this->job['request']);
|
||||||
|
|
||||||
|
if (array_key_exists('model', $opts) && property_exists($opts['model'], 'prompt') && empty($opts['model']->prompt))
|
||||||
|
$opts['model']->prompt = $opts['prompt'];
|
||||||
|
|
||||||
|
if (array_key_exists('config', $opts) && property_exists($opts['config'], 'prompt') && empty($opts['config']->prompt))
|
||||||
|
$opts['config']->prompt = $opts['prompt'];
|
||||||
|
|
||||||
|
$result = new resultSubmit();
|
||||||
|
|
||||||
|
$fileConfig = $this->cfg->config->logs . '/' . $this->job_id . '.json';
|
||||||
|
$fileLog = $this->cfg->config->logs . '/' . $this->job_id . '.json.log';
|
||||||
|
$fileResponse = $this->cfg->config->logs . '/' . $this->job_id . '.json.response';
|
||||||
|
$execute = $this->cfg->config->script . ' ' . $this->job_id . ' ' . $fileConfig . ' > ' . $fileLog;
|
||||||
|
|
||||||
|
$jsonConfig = json_encode($opts, JSON_PRETTY_PRINT);
|
||||||
|
|
||||||
|
$result_put = file_put_contents($fileConfig, $jsonConfig);
|
||||||
|
|
||||||
|
if (empty($result_put)) {
|
||||||
|
$result->responseCodes[] = $result_put;
|
||||||
|
$result->response = '{ "error" : "Unable to save .json config" }';
|
||||||
|
} else {
|
||||||
|
$result_code = null;
|
||||||
|
try {
|
||||||
|
// Make sure that exe is enabled in /etc/php/8.2/fpm/pool.d/phpbb_pool.conf file
|
||||||
|
// See line php_admin_value[disable_functions] =
|
||||||
|
unset($output);
|
||||||
|
exec($execute, $output, $result_code);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$result->response = '{ "error" : "' . $e . '" }';
|
||||||
|
}
|
||||||
|
|
||||||
|
$result->responseCodes[] = $result_code;
|
||||||
|
$result->response = file_get_contents($fileResponse);
|
||||||
|
|
||||||
|
if ($result_code == 0) {
|
||||||
|
unlink($fileLog);
|
||||||
|
unlink($fileResponse);
|
||||||
|
unlink($fileConfig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function parse(resultSubmit $resultSubmit): resultParse
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
Response
|
||||||
|
{
|
||||||
|
images: ['','',''],
|
||||||
|
agentName: '0'..'j',
|
||||||
|
subscriptionTokens: tokensLeft,
|
||||||
|
error: null | 'error message'
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
$json = empty($resultSubmit->response) ? false : json_decode($resultSubmit->response);
|
||||||
|
$images = [];
|
||||||
|
$message = null;
|
||||||
|
|
||||||
|
if (
|
||||||
|
empty($json) ||
|
||||||
|
empty($json->images) ||
|
||||||
|
!empty($json->error)
|
||||||
|
) {
|
||||||
|
if (!empty($json->error)) {
|
||||||
|
$message = $json->error;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->job['status'] = 'ok';
|
||||||
|
$images = [];
|
||||||
|
foreach ($json->images as $item) {
|
||||||
|
array_push($images, $item);
|
||||||
|
}
|
||||||
|
$json->images = $images;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = new resultParse();
|
||||||
|
$result->json = $json;
|
||||||
|
$result->images = $images;
|
||||||
|
$result->message = $message;
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
148
privet/ailabs/controller/stablediffusion.php
Normal file
148
privet/ailabs/controller/stablediffusion.php
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
<?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;
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
config
|
||||||
|
{
|
||||||
|
"api_key": "<api-key>",
|
||||||
|
"url_texttoimage": "https://api.stability.ai/v1/generation/stable-diffusion-xl-beta-v2-2-2/text-to-image",
|
||||||
|
"cfg_scale": 7.5,
|
||||||
|
"clip_guidance_preset": "FAST_BLUE",
|
||||||
|
"height": 512,
|
||||||
|
"width": 512,
|
||||||
|
"samples": 1,
|
||||||
|
"steps": 30
|
||||||
|
}
|
||||||
|
|
||||||
|
template
|
||||||
|
[quote={poster_name} post_id={post_id} user_id={poster_id}]{request}[/quote]
|
||||||
|
{response}{attachments}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
class stablediffusion extends GenericController
|
||||||
|
{
|
||||||
|
|
||||||
|
protected function prepare($opts)
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'text_prompts' => [
|
||||||
|
['text' => trim($this->job['request'])],
|
||||||
|
],
|
||||||
|
'cfg_scale' => $this->cfg->cfg_scale,
|
||||||
|
'clip_guidance_preset' => $this->cfg->clip_guidance_preset,
|
||||||
|
'height' => $this->cfg->height,
|
||||||
|
'width' => $this->cfg->width,
|
||||||
|
'samples' => $this->cfg->samples,
|
||||||
|
'steps' => $this->cfg->steps,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function submit($opts): resultSubmit
|
||||||
|
{
|
||||||
|
$api = new GenericCurl($this->cfg->api_key);
|
||||||
|
$this->cfg->api_key = null;
|
||||||
|
|
||||||
|
$result = new resultSubmit();
|
||||||
|
// https://api.stability.ai/docs#tag/v1generation/operation/textToImage
|
||||||
|
// https://api.stability.ai/v1/generation/stable-diffusion-xl-beta-v2-2-2/text-to-image
|
||||||
|
$result->response = $api->sendRequest($this->cfg->url_texttoimage, 'POST', $opts);
|
||||||
|
$result->responseCodes = $api->responseCodes;
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function parse(resultSubmit $resultSubmit): resultParse
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
Response headers:
|
||||||
|
Content-Type required string
|
||||||
|
Enum: "application/json" | "image/png"
|
||||||
|
Finish-Reason string (FinishReason)
|
||||||
|
Enum: "SUCCESS" | "ERROR" | "CONTENT_FILTERED"
|
||||||
|
The result of the generation process.
|
||||||
|
|
||||||
|
SUCCESS indicates success
|
||||||
|
ERROR indicates an error
|
||||||
|
CONTENT_FILTERED indicates the result affected by the content filter and may be blurred.
|
||||||
|
This header is only present when the Accept is set to image/png. Otherwise it is returned in the response body.
|
||||||
|
|
||||||
|
Seed integer
|
||||||
|
Example: 3817857576
|
||||||
|
The seed used to generate the image. This header is only present when the Accept is set to image/png. Otherwise it is returned in the response body.
|
||||||
|
|
||||||
|
Response HTTP 200:
|
||||||
|
{
|
||||||
|
"artifacts":[
|
||||||
|
{
|
||||||
|
"base64":"<encoded>",
|
||||||
|
"seed":4188843142,
|
||||||
|
"finishReason":"SUCCESS"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Response HTTP 400, 401, 404, 500:
|
||||||
|
{
|
||||||
|
"id": "A unique identifier for this particular occurrence of the problem.",
|
||||||
|
"name": "The short-name of this class of errors e.g. bad_request.",
|
||||||
|
"message": "A human-readable explanation specific to this occurrence of the problem."
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
$json = json_decode($resultSubmit->response);
|
||||||
|
$images = null;
|
||||||
|
$message = null;
|
||||||
|
|
||||||
|
if (
|
||||||
|
empty($json->artifacts) ||
|
||||||
|
!empty($json->name) ||
|
||||||
|
!empty($json->message) ||
|
||||||
|
!in_array(200, $resultSubmit->responseCodes)
|
||||||
|
) {
|
||||||
|
if (!empty($json->message)) {
|
||||||
|
$message = $json->message;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$this->job['status'] = 'ok';
|
||||||
|
|
||||||
|
$images = [];
|
||||||
|
|
||||||
|
$ind = 0;
|
||||||
|
foreach ($json->artifacts as $item) {
|
||||||
|
if ($item->finishReason !== 'SUCCESS') {
|
||||||
|
$message = $item->finishReason;
|
||||||
|
if (!empty($item->base64))
|
||||||
|
$item->base64 = '<reducted>';
|
||||||
|
} else {
|
||||||
|
$filename = $this->save_base64_to_temp_file($item->base64, $ind);
|
||||||
|
array_push($images, $filename);
|
||||||
|
$item->base64 = '<reducted>';
|
||||||
|
}
|
||||||
|
$ind++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = new resultParse();
|
||||||
|
$result->json = $json;
|
||||||
|
$result->images = $images;
|
||||||
|
$result->message = $message;
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
312
privet/ailabs/event/listener.php
Normal file
312
privet/ailabs/event/listener.php
Normal file
|
@ -0,0 +1,312 @@
|
||||||
|
<?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\event;
|
||||||
|
|
||||||
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
|
|
||||||
|
class listener implements EventSubscriberInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
protected $user;
|
||||||
|
protected $auth;
|
||||||
|
protected $db;
|
||||||
|
protected $helper;
|
||||||
|
protected $language;
|
||||||
|
protected $request;
|
||||||
|
|
||||||
|
/** @var string phpBB root path */
|
||||||
|
protected $root_path;
|
||||||
|
|
||||||
|
/** @var string PHP extension */
|
||||||
|
protected $php_ext;
|
||||||
|
|
||||||
|
protected $users_table;
|
||||||
|
protected $jobs_table;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
\phpbb\user $user,
|
||||||
|
\phpbb\auth\auth $auth,
|
||||||
|
\phpbb\db\driver\driver_interface $db,
|
||||||
|
\phpbb\controller\helper $helper,
|
||||||
|
\phpbb\language\language $language,
|
||||||
|
\phpbb\request\request_interface $request,
|
||||||
|
string $root_path,
|
||||||
|
string $php_ext,
|
||||||
|
string $users_table,
|
||||||
|
string $jobs_table
|
||||||
|
) {
|
||||||
|
$this->user = $user;
|
||||||
|
$this->auth = $auth;
|
||||||
|
$this->db = $db;
|
||||||
|
$this->helper = $helper;
|
||||||
|
$this->language = $language;
|
||||||
|
$this->request = $request;
|
||||||
|
$this->root_path = $root_path;
|
||||||
|
$this->php_ext = $php_ext;
|
||||||
|
$this->users_table = $users_table;
|
||||||
|
$this->jobs_table = $jobs_table;
|
||||||
|
}
|
||||||
|
|
||||||
|
static public function getSubscribedEvents()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
'core.posting_modify_submit_post_after' => 'post_ailabs_message',
|
||||||
|
'core.viewtopic_post_rowset_data' => 'viewtopic_post_rowset_data',
|
||||||
|
'core.viewtopic_modify_post_row' => 'viewtopic_modify_post_row',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post a message
|
||||||
|
*
|
||||||
|
* @param \phpbb\event\data $event Event object
|
||||||
|
*/
|
||||||
|
public function post_ailabs_message($event, $forum_data)
|
||||||
|
{
|
||||||
|
$mode = $event['mode'];
|
||||||
|
|
||||||
|
// Only for new topics and posts with mention (quote/reply/@mention) to AI Labs user posts
|
||||||
|
if (!in_array($mode, ['post', 'reply', 'quote'])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$forum_id = $event['forum_id'];
|
||||||
|
|
||||||
|
$ailabs_users_forum = $this->ailabs_users_forum($forum_id);
|
||||||
|
|
||||||
|
if (empty($ailabs_users_forum)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$post_id = $event['data']['post_id'];
|
||||||
|
|
||||||
|
$ailabs_users_notified = $this->ailabs_users_notified($post_id);
|
||||||
|
|
||||||
|
$ailabs_users = array();
|
||||||
|
|
||||||
|
foreach ($ailabs_users_forum as $user) {
|
||||||
|
if ($mode == 'post' && $user['post'] == 1) {
|
||||||
|
array_push($ailabs_users, $user);
|
||||||
|
} else {
|
||||||
|
if ($user['mention'] == 1 && in_array($user['user_id'], $ailabs_users_notified))
|
||||||
|
array_push($ailabs_users, $user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($ailabs_users)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$message_parser = new \parse_message($event['data']['message']);
|
||||||
|
$message_parser->remove_nested_quotes(0);
|
||||||
|
$message_parser->decode_message();
|
||||||
|
|
||||||
|
$request = $message_parser->message;
|
||||||
|
|
||||||
|
// Remove all mentioned AI user names from request
|
||||||
|
foreach ($ailabs_users as $user) {
|
||||||
|
$count = 0;
|
||||||
|
$updated = preg_replace('/\[mention\][\s\t]?' . $user['username'] . '[\s\t]?\[\/mention\]/', '', $request, -1, $count);
|
||||||
|
if ($count > 0) {
|
||||||
|
$request = $updated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace mention tags
|
||||||
|
$request = preg_replace(array('/\[mention\]/', '/\[\/mention\]/'), array('', ''), $request);
|
||||||
|
|
||||||
|
// Replace size tags
|
||||||
|
$request = preg_replace(array('/\[size=[0-9]+\]/', '/\[\/size\]/'), array('', ''), $request);
|
||||||
|
|
||||||
|
// Remove leading and trailing spaces as well as all doublespaces
|
||||||
|
$request = trim(str_replace(' ', ' ', $request));
|
||||||
|
|
||||||
|
// https://area51.phpbb.com/docs/dev/master/db/dbal.html
|
||||||
|
foreach ($ailabs_users as $user) {
|
||||||
|
$data = [
|
||||||
|
'ailabs_user_id' => $user['user_id'],
|
||||||
|
'ailabs_username' => $user['username'],
|
||||||
|
'request_time' => time(),
|
||||||
|
'post_mode' => $mode,
|
||||||
|
'post_id' => $post_id,
|
||||||
|
'forum_id' => $forum_id,
|
||||||
|
'poster_id' => $this->user->data['user_id'],
|
||||||
|
'poster_name' => $this->user->data['username'],
|
||||||
|
'request' => utf8_encode_ucr($request),
|
||||||
|
];
|
||||||
|
$sql = 'INSERT INTO ' . $this->jobs_table . ' ' . $this->db->sql_build_array('INSERT', $data);
|
||||||
|
$result = $this->db->sql_query($sql);
|
||||||
|
$this->db->sql_freeresult($result);
|
||||||
|
$data['job_id'] = $this->db->sql_nextid();
|
||||||
|
|
||||||
|
$this->update_post($data);
|
||||||
|
|
||||||
|
$url = generate_board_url() . $user['controller'] . '?job_id=' . $data['job_id'];
|
||||||
|
get_headers($url);
|
||||||
|
unset($data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function update_post($data)
|
||||||
|
{
|
||||||
|
$where = [
|
||||||
|
'post_id' => $data['post_id']
|
||||||
|
];
|
||||||
|
|
||||||
|
$set = [
|
||||||
|
'post_ailabs_data' => json_encode(array(
|
||||||
|
'job_id' => $data['job_id'],
|
||||||
|
'ailabs_user_id' => $data['ailabs_user_id'],
|
||||||
|
'ailabs_username' => $data['ailabs_username'],
|
||||||
|
)) . ','
|
||||||
|
];
|
||||||
|
|
||||||
|
$sql = 'UPDATE ' . POSTS_TABLE .
|
||||||
|
' SET ' . $this->db->sql_build_array('UPDATE', $set) .
|
||||||
|
' WHERE ' . $this->db->sql_build_array('SELECT', $where);
|
||||||
|
$result = $this->db->sql_query($sql);
|
||||||
|
$this->db->sql_freeresult($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if forum enabled for ailabs users
|
||||||
|
* @param int $id
|
||||||
|
* @return array of found ailabs user_ids along with allowed actions for each [user_id, post, mention]
|
||||||
|
*/
|
||||||
|
private function ailabs_users_forum($id)
|
||||||
|
{
|
||||||
|
$return = array();
|
||||||
|
$sql = 'SELECT c.user_id, ' .
|
||||||
|
'c.forums_post LIKE \'%"' . $id . '"%\' as post, ' .
|
||||||
|
'c.forums_mention LIKE \'%"' . $id . '"%\' as mention, ' .
|
||||||
|
'c.controller, ' .
|
||||||
|
'u.username ' .
|
||||||
|
'FROM ' . $this->users_table . ' c ' .
|
||||||
|
'JOIN ' . USERS_TABLE . ' u ON c.user_id = u.user_id ' .
|
||||||
|
'WHERE c.enabled = 1';
|
||||||
|
$result = $this->db->sql_query($sql);
|
||||||
|
while ($row = $this->db->sql_fetchrow($result)) {
|
||||||
|
array_push($return, array(
|
||||||
|
'user_id' => $row['user_id'],
|
||||||
|
'username' => $row['username'],
|
||||||
|
'post' => $row['post'],
|
||||||
|
'mention' => $row['mention'],
|
||||||
|
'controller' => $row['controller']
|
||||||
|
));
|
||||||
|
}
|
||||||
|
$this->db->sql_freeresult($result);
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if any of ailabs user notified in this post
|
||||||
|
* @param int $post_id
|
||||||
|
* @return array of notified ailabs users
|
||||||
|
*/
|
||||||
|
private function ailabs_users_notified($post_id)
|
||||||
|
{
|
||||||
|
$return = array();
|
||||||
|
$sql = 'SELECT c.user_id FROM ' . $this->users_table . ' c ' .
|
||||||
|
'JOIN ' . NOTIFICATIONS_TABLE . ' n ON n.user_id = c.user_id ' .
|
||||||
|
'WHERE c.enabled = 1 AND n.item_id = ' . (int) $post_id;
|
||||||
|
$result = $this->db->sql_query($sql);
|
||||||
|
while ($row = $this->db->sql_fetchrow($result)) {
|
||||||
|
array_push($return, $row['user_id']);
|
||||||
|
}
|
||||||
|
$this->db->sql_freeresult($result);
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function get_status($status)
|
||||||
|
{
|
||||||
|
$this->language->add_lang('common', 'privet/ailabs');
|
||||||
|
|
||||||
|
switch ($status) {
|
||||||
|
case null:
|
||||||
|
return $this->language->lang('AILABS_THINKING');
|
||||||
|
case 'exec':
|
||||||
|
return $this->language->lang('AILABS_REPLYING');
|
||||||
|
case 'ok':
|
||||||
|
return $this->language->lang('AILABS_REPLIED');
|
||||||
|
case 'fail':
|
||||||
|
return $this->language->lang('AILABS_UNABLE_TO_REPLY');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $status;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function viewtopic_post_rowset_data($event)
|
||||||
|
{
|
||||||
|
$rowset_data = $event['rowset_data'];
|
||||||
|
$rowset_data = array_merge($rowset_data, [
|
||||||
|
'post_ailabs_data' => $event['row']['post_ailabs_data'],
|
||||||
|
]);
|
||||||
|
$event['rowset_data'] = $rowset_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function viewtopic_modify_post_row($event)
|
||||||
|
{
|
||||||
|
$post_ailabs_data = $event['row']['post_ailabs_data'];
|
||||||
|
$post_id = $event['row']['post_id'];
|
||||||
|
|
||||||
|
$jobs = array();
|
||||||
|
|
||||||
|
if (!empty($post_ailabs_data)) {
|
||||||
|
$json_data = json_decode('[' . rtrim($post_ailabs_data, ',') . ']');
|
||||||
|
if (!empty($json_data)) {
|
||||||
|
/*
|
||||||
|
[
|
||||||
|
job_id: <int>,
|
||||||
|
ailabs_user_id: <int>,
|
||||||
|
ailabs_username: <string>,
|
||||||
|
response_time: <int>,
|
||||||
|
status: <string>,
|
||||||
|
response_post_id: <int>
|
||||||
|
]
|
||||||
|
*/
|
||||||
|
foreach ($json_data as $job) {
|
||||||
|
$ailabs_user_id = (string) $job->ailabs_user_id;
|
||||||
|
$response_time = empty($job->response_time) ? 0 : $job->response_time;
|
||||||
|
if (
|
||||||
|
!in_array($ailabs_user_id, $jobs) ||
|
||||||
|
$jobs[$ailabs_user_id]->$response_time < $response_time
|
||||||
|
) {
|
||||||
|
$jobs[$ailabs_user_id] = $job;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unset($json_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
$ailabs = array();
|
||||||
|
|
||||||
|
foreach ($jobs as $key => $value) {
|
||||||
|
$value->user_url = '/' . append_sid("memberlist.$this->php_ext", 'mode=viewprofile&u=' . $value->ailabs_user_id, true, '');
|
||||||
|
if (!empty($value->response_post_id)) {
|
||||||
|
$value->response_url = '/viewtopic.php?p=' . $value->response_post_id . '#p' . $value->response_post_id;
|
||||||
|
}
|
||||||
|
$value->status = $this->get_status(empty($value->status) ? null : $value->status);
|
||||||
|
array_push($ailabs, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($ailabs)) {
|
||||||
|
$event['post_row'] = array_merge($event['post_row'], [
|
||||||
|
'U_AILABS' => $ailabs,
|
||||||
|
]);
|
||||||
|
if ($this->auth->acl_get('a_', 'm_')) {
|
||||||
|
$event['post_row'] = array_merge($event['post_row'], [
|
||||||
|
'U_AILABS_VIEW_LOG' => $this->helper->route('privet_ailabs_view_log_controller_page', ['post_id' => $post_id]),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
451
privet/ailabs/includes/AIController.php
Normal file
451
privet/ailabs/includes/AIController.php
Normal file
|
@ -0,0 +1,451 @@
|
||||||
|
<?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\includes;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
|
use privet\ailabs\includes\resultParse;
|
||||||
|
|
||||||
|
class AIController
|
||||||
|
{
|
||||||
|
protected $auth;
|
||||||
|
protected $config;
|
||||||
|
protected $db;
|
||||||
|
protected $helper;
|
||||||
|
protected $language;
|
||||||
|
protected $request;
|
||||||
|
protected $template;
|
||||||
|
protected $user;
|
||||||
|
protected $phpbb_container;
|
||||||
|
protected $php_ext;
|
||||||
|
protected $root_path;
|
||||||
|
protected $users_table;
|
||||||
|
protected $jobs_table;
|
||||||
|
|
||||||
|
protected $job_id;
|
||||||
|
protected $start;
|
||||||
|
protected $log;
|
||||||
|
protected $job;
|
||||||
|
protected $cfg;
|
||||||
|
protected $lang;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
\phpbb\auth\auth $auth,
|
||||||
|
\phpbb\config\config $config,
|
||||||
|
\phpbb\db\driver\driver_interface $db,
|
||||||
|
\phpbb\controller\helper $helper,
|
||||||
|
\phpbb\language\language $language,
|
||||||
|
\phpbb\request\request $request,
|
||||||
|
\phpbb\template\template $template,
|
||||||
|
\phpbb\user $user,
|
||||||
|
ContainerInterface $phpbb_container,
|
||||||
|
$php_ext,
|
||||||
|
$root_path,
|
||||||
|
$users_table,
|
||||||
|
$jobs_table
|
||||||
|
) {
|
||||||
|
$this->auth = $auth;
|
||||||
|
$this->config = $config;
|
||||||
|
$this->db = $db;
|
||||||
|
$this->helper = $helper;
|
||||||
|
$this->language = $language;
|
||||||
|
$this->request = $request;
|
||||||
|
$this->template = $template;
|
||||||
|
$this->user = $user;
|
||||||
|
$this->phpbb_container = $phpbb_container;
|
||||||
|
$this->php_ext = $php_ext;
|
||||||
|
$this->root_path = $root_path;
|
||||||
|
$this->users_table = $users_table;
|
||||||
|
$this->jobs_table = $jobs_table;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Symfony\Component\HttpFoundation\Response A Symfony Response object
|
||||||
|
*/
|
||||||
|
public function execute()
|
||||||
|
{
|
||||||
|
$this->start = date('Y-m-d H:i:s');
|
||||||
|
|
||||||
|
// https://symfony.com/doc/current/components/http_foundation.html#streaming-a-response
|
||||||
|
$streamedResponse = new StreamedResponse();
|
||||||
|
$streamedResponse->headers->set('X-Accel-Buffering', 'no');
|
||||||
|
$streamedResponse->setCallback(function () {
|
||||||
|
var_dump('Processing');
|
||||||
|
flush();
|
||||||
|
});
|
||||||
|
$streamedResponse->send();
|
||||||
|
|
||||||
|
$this->job_id = utf8_clean_string($this->request->variable('job_id', '', true));
|
||||||
|
|
||||||
|
if (empty($this->job_id)) {
|
||||||
|
return new JsonResponse('job_id not provided');
|
||||||
|
}
|
||||||
|
|
||||||
|
$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, 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)) {
|
||||||
|
return new JsonResponse('job_id not found in the database');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($this->job['status'])) {
|
||||||
|
return new JsonResponse('job_id already completed with ' . $this->job['status']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->log = array('start' => $this->start);
|
||||||
|
|
||||||
|
$this->job['request'] = utf8_decode_ncr($this->job['request']);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->cfg = json_decode($this->job['config']);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->cfg = null;
|
||||||
|
$this->log['exception'] = $e->getMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($this->cfg)) {
|
||||||
|
$this->job['status'] = 'fail';
|
||||||
|
$this->log['error'] = 'config not provided';
|
||||||
|
|
||||||
|
$set = [
|
||||||
|
'status' => $this->job['status'],
|
||||||
|
'log' => json_encode($this->log)
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->job_update($set);
|
||||||
|
$this->post_update($this->job);
|
||||||
|
|
||||||
|
return new JsonResponse($this->log);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->process();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function process()
|
||||||
|
{
|
||||||
|
return new JsonResponse($this->log);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse message template
|
||||||
|
* @param string $template
|
||||||
|
*/
|
||||||
|
protected function replace_vars($job, resultParse $resultParse)
|
||||||
|
{
|
||||||
|
$images = null;
|
||||||
|
$attachments = null;
|
||||||
|
if (!empty($resultParse->images)) {
|
||||||
|
$images = [];
|
||||||
|
$attachments = [];
|
||||||
|
$ind = 0;
|
||||||
|
foreach ($resultParse->images as $item) {
|
||||||
|
array_push($images, '[img]' . $item . '[/img]' . PHP_EOL . PHP_EOL);
|
||||||
|
array_push($attachments, '[attachment=' . $ind . '][/attachment]' . PHP_EOL);
|
||||||
|
$ind = $ind + 1;
|
||||||
|
}
|
||||||
|
$images = implode("", $images);
|
||||||
|
$attachments = implode("", $attachments);
|
||||||
|
}
|
||||||
|
|
||||||
|
$tokens = array(
|
||||||
|
'{post_id}' => $job['post_id'],
|
||||||
|
'{request}' => $job['request'],
|
||||||
|
'{info}' => $resultParse->info,
|
||||||
|
'{response}' => $resultParse->message,
|
||||||
|
'{images}' => $images,
|
||||||
|
'{attachments}' => $attachments,
|
||||||
|
'{poster_id}' => $job['poster_id'],
|
||||||
|
'{poster_name}' => $job['poster_name'],
|
||||||
|
'{ailabs_username}' => $job['ailabs_username'],
|
||||||
|
);
|
||||||
|
|
||||||
|
return str_ireplace(array_keys($tokens), array_values($tokens), $job['template']);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function job_update($set)
|
||||||
|
{
|
||||||
|
$where = ['job_id' => $this->job_id];
|
||||||
|
|
||||||
|
$sql = 'UPDATE ' . $this->jobs_table .
|
||||||
|
' SET ' . $this->db->sql_build_array('UPDATE', $set) .
|
||||||
|
' WHERE ' . $this->db->sql_build_array('SELECT', $where);
|
||||||
|
|
||||||
|
$result = $this->db->sql_query($sql);
|
||||||
|
|
||||||
|
$this->db->sql_freeresult($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function log_flush()
|
||||||
|
{
|
||||||
|
$set = ['log' => json_encode($this->log)];
|
||||||
|
|
||||||
|
$this->job_update($set);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function post_update($job)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
[
|
||||||
|
job_id: <int>,
|
||||||
|
ailabs_user_id: <int>,
|
||||||
|
ailabs_username: <string>,
|
||||||
|
response_time: <int>,
|
||||||
|
status: <string>,
|
||||||
|
response_post_id: <int>
|
||||||
|
]
|
||||||
|
*/
|
||||||
|
$data = array(
|
||||||
|
'job_id' => $job['job_id'],
|
||||||
|
'ailabs_user_id' => $job['ailabs_user_id'],
|
||||||
|
'ailabs_username' => $job['ailabs_username'],
|
||||||
|
'status' => $job['status'],
|
||||||
|
'response_time' => empty($job['response_time']) ? time() : $job['response_time'],
|
||||||
|
);
|
||||||
|
if (!empty($job['response_post_id'])) {
|
||||||
|
$data['response_post_id'] = $job['response_post_id'];
|
||||||
|
}
|
||||||
|
$where = [
|
||||||
|
'post_id' => $job['post_id']
|
||||||
|
];
|
||||||
|
$set = '\'' . json_encode($data) . ',\'';
|
||||||
|
$sql = 'UPDATE ' . POSTS_TABLE .
|
||||||
|
' SET post_ailabs_data = CONCAT(post_ailabs_data, ' . $set . ')' .
|
||||||
|
' WHERE ' . $this->db->sql_build_array('SELECT', $where);
|
||||||
|
$result = $this->db->sql_query($sql);
|
||||||
|
$this->db->sql_freeresult($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function post_response($job, $response)
|
||||||
|
{
|
||||||
|
// Prep posting
|
||||||
|
$poll = $uid = $bitfield = $options = '';
|
||||||
|
$allow_bbcode = $allow_urls = $allow_smilies = true;
|
||||||
|
generate_text_for_storage($response, $uid, $bitfield, $options, $allow_bbcode, $allow_urls, $allow_smilies);
|
||||||
|
|
||||||
|
$data = array(
|
||||||
|
'poster_id' => $job['ailabs_user_id'],
|
||||||
|
// General Posting Settings
|
||||||
|
'forum_id' => $job['forum_id'], // The forum ID in which the post will be placed. (int)
|
||||||
|
'topic_id' => $job['topic_id'], // Post a new topic or in an existing one? Set to 0 to create a new one, if not, specify your topic ID here instead.
|
||||||
|
'icon_id' => false, // The Icon ID in which the post will be displayed with on the viewforum, set to false for icon_id. (int)
|
||||||
|
// Defining Post Options
|
||||||
|
'enable_bbcode' => true, // Enable BBcode in this post. (bool)
|
||||||
|
'enable_smilies' => true, // Enabe smilies in this post. (bool)
|
||||||
|
'enable_urls' => true, // Enable self-parsing URL links in this post. (bool)
|
||||||
|
'enable_sig' => true, // Enable the signature of the poster to be displayed in the post. (bool)
|
||||||
|
// Message Body
|
||||||
|
'message' => $response, // Your text you wish to have submitted. It should pass through generate_text_for_storage() before this. (string)
|
||||||
|
'message_md5' => md5($response), // The md5 hash of your message
|
||||||
|
'post_checksum' => md5($response), // The md5 hash of your message
|
||||||
|
// Values from generate_text_for_storage()
|
||||||
|
'bbcode_bitfield' => $bitfield, // Value created from the generate_text_for_storage() function.
|
||||||
|
'bbcode_uid' => $uid, // Value created from the generate_text_for_storage() function.
|
||||||
|
// Other Options
|
||||||
|
'post_edit_locked' => 0, // Disallow post editing? 1 = Yes, 0 = No
|
||||||
|
'topic_title' => $job['post_subject'],
|
||||||
|
'notify_set' => true, // (bool)
|
||||||
|
'notify' => true, // (bool)
|
||||||
|
'post_time' => 0, // Set a specific time, use 0 to let submit_post() take care of getting the proper time (int)
|
||||||
|
'forum_name' => $job['forum_name'], // For identifying the name of the forum in a notification email. (string) // Indexing
|
||||||
|
'enable_indexing' => true, // Allow indexing the post? (bool) // 3.0.6
|
||||||
|
);
|
||||||
|
|
||||||
|
// Post as designated user and then switch back to original one
|
||||||
|
$actual_user_id = $this->user->data['user_id'];
|
||||||
|
$this->switch_user($job['ailabs_user_id']);
|
||||||
|
$post_subject = ((strpos($job['post_subject'], 'Re: ') !== 0) ? 'Re: ' : '') . censor_text($job['post_subject']);
|
||||||
|
|
||||||
|
include($this->root_path . 'includes/functions_posting.' . $this->php_ext);
|
||||||
|
submit_post('reply', $post_subject, $job['ailabs_username'], POST_NORMAL, $poll, $data);
|
||||||
|
|
||||||
|
$this->switch_user($actual_user_id);
|
||||||
|
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switch to the AI Labs user
|
||||||
|
* @param int $new_user_id
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function switch_user($new_user_id)
|
||||||
|
{
|
||||||
|
if ($this->user->data['user_id'] == $new_user_id) {
|
||||||
|
// Nothing to do
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = 'SELECT * FROM ' . USERS_TABLE . ' WHERE user_id = ' . (int) $new_user_id;
|
||||||
|
$result = $this->db->sql_query($sql);
|
||||||
|
$row = $this->db->sql_fetchrow($result);
|
||||||
|
$this->db->sql_freeresult($result);
|
||||||
|
|
||||||
|
$row['is_registered'] = true;
|
||||||
|
$this->user->data = array_merge($this->user->data, $row);
|
||||||
|
$this->auth->acl($this->user->data);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Inspired by https://www.phpbb.com/community/viewtopic.php?t=2556226
|
||||||
|
*/
|
||||||
|
protected function attach_to_post($isUrl, $urlOrFilename, $postId, $topicId = 0, $forumId = 0, $userId = 0, $realFileName = '', $comment = '', $dispatchEvent = true)
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
getType($isUrl) != 'boolean' ||
|
||||||
|
getType($urlOrFilename) != 'string' ||
|
||||||
|
getType($postId) != 'integer' ||
|
||||||
|
getType($comment) != 'string' ||
|
||||||
|
getType($dispatchEvent) != 'boolean' ||
|
||||||
|
getType($topicId) != 'integer' ||
|
||||||
|
getType($forumId) != 'integer' ||
|
||||||
|
getType($userId) != 'integer'
|
||||||
|
)
|
||||||
|
throw 'Type Missmatch';
|
||||||
|
|
||||||
|
if ($postId <= 0)
|
||||||
|
throw 'Post ID cannot be zero!';
|
||||||
|
|
||||||
|
// if not given, get missing IDs
|
||||||
|
if ($topicId == 0 || $forumId == 0 || $userId == 0) {
|
||||||
|
$idRow = $this->db->sql_fetchrow($this->db->sql_query('SELECT post_id, topic_id, forum_id, poster_id FROM ' . POSTS_TABLE . ' WHERE post_id = ' . $postId));
|
||||||
|
if (!$idRow)
|
||||||
|
return ['POST_NOT_EXISTS'];
|
||||||
|
$topicId = intval($idRow['topic_id']);
|
||||||
|
$forumId = intval($idRow['topic_id']);
|
||||||
|
$userId = intval($idRow['poster_id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get required classes
|
||||||
|
$upload = $this->phpbb_container->get('files.upload');
|
||||||
|
$cache = $this->phpbb_container->get('cache');
|
||||||
|
$attach = $this->phpbb_container->get('attachment.upload');
|
||||||
|
|
||||||
|
// load file from remote location to local server
|
||||||
|
$upload->set_disallowed_content([]);
|
||||||
|
$extensions = $cache->obtain_attach_extensions($forumId);
|
||||||
|
$extensionArr = array_keys($extensions['_allowed_']);
|
||||||
|
$upload->set_allowed_extensions($extensionArr);
|
||||||
|
|
||||||
|
$tempFile = null;
|
||||||
|
if ($isUrl) {
|
||||||
|
$upload->set_allowed_extensions($extensionArr);
|
||||||
|
$tempFile = $upload->handle_upload('files.types.remote', $urlOrFilename);
|
||||||
|
} else {
|
||||||
|
// TODO: There seems to be some kind of bug where phpBB unable to get extension for local file
|
||||||
|
array_push($extensionArr, '');
|
||||||
|
$upload->set_allowed_extensions($extensionArr);
|
||||||
|
$local_filedata = array();
|
||||||
|
$tempFile = $upload->handle_upload('files.types.local', $urlOrFilename, $local_filedata);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count($tempFile->error) > 0) {
|
||||||
|
$ext = '.';
|
||||||
|
if (strrpos($urlOrFilename, '.') !== false)
|
||||||
|
$ext = substr($urlOrFilename, strrpos($urlOrFilename, '.') + 1);
|
||||||
|
if ($tempFile->error[0] == 'URL_INVALID' && !in_array($ext, $extensionArr))
|
||||||
|
return ['FILE_EXTENSION_NOT_ALLOWED', 'EXTENSION=.' . htmlspecialchars($ext)];
|
||||||
|
return $tempFile->error;
|
||||||
|
}
|
||||||
|
|
||||||
|
$realFileNameExt = $isUrl ? '.' . $tempFile->get('extension') : '';
|
||||||
|
|
||||||
|
$realFileName = $realFileName == '' ? $tempFile->get('realname') : htmlspecialchars($realFileName) . $realFileNameExt;
|
||||||
|
|
||||||
|
$tempFileData = [
|
||||||
|
'realname' => $realFileName,
|
||||||
|
'size' => $tempFile->get('filesize'),
|
||||||
|
'type' => $tempFile->get('mimetype'),
|
||||||
|
];
|
||||||
|
|
||||||
|
// create attachment from temp file
|
||||||
|
if (!function_exists('create_thumbnail'))
|
||||||
|
require($this->root_path . 'includes/functions_posting.php');
|
||||||
|
|
||||||
|
$attachFileName = $isUrl ? $tempFile->get('filename') : $urlOrFilename;
|
||||||
|
|
||||||
|
$attachmentFileData = $attach->upload('', $forumId, true, $attachFileName, false, $tempFileData);
|
||||||
|
|
||||||
|
if (!$attachmentFileData['post_attach'])
|
||||||
|
return ['FILE_ATTACH_ERROR', $realFileName, $attachFileName, $tempFileData];
|
||||||
|
|
||||||
|
if (count($attachmentFileData['error']) > 0)
|
||||||
|
return $attachmentFileData['error'];
|
||||||
|
|
||||||
|
$sql_ary = array(
|
||||||
|
'physical_filename' => $attachmentFileData['physical_filename'],
|
||||||
|
'attach_comment' => $comment,
|
||||||
|
'real_filename' => $attachmentFileData['real_filename'],
|
||||||
|
'extension' => $attachmentFileData['extension'],
|
||||||
|
'mimetype' => $attachmentFileData['mimetype'],
|
||||||
|
'filesize' => $attachmentFileData['filesize'],
|
||||||
|
'filetime' => $attachmentFileData['filetime'],
|
||||||
|
'thumbnail' => $attachmentFileData['thumbnail'],
|
||||||
|
'is_orphan' => 0,
|
||||||
|
'in_message' => 0,
|
||||||
|
'poster_id' => $userId,
|
||||||
|
'post_msg_id' => $postId,
|
||||||
|
'topic_id' => $topicId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($dispatchEvent) {
|
||||||
|
$dispatcher = $this->phpbb_container->get('dispatcher');
|
||||||
|
$vars = array('sql_ary');
|
||||||
|
extract($dispatcher->trigger_event('core.modify_attachment_sql_ary_on_submit', compact($vars)));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->db->sql_query('INSERT INTO ' . ATTACHMENTS_TABLE . ' ' . $this->db->sql_build_array('INSERT', $sql_ary));
|
||||||
|
$newAttachmentID = intval($this->db->sql_nextid());
|
||||||
|
|
||||||
|
if ($newAttachmentID == 0)
|
||||||
|
return ['SQL_ATTACHMENT_INSERT_ERROR'];
|
||||||
|
|
||||||
|
$this->db->sql_query('UPDATE ' . POSTS_TABLE . ' SET post_attachment = 1 WHERE post_id = ' . $postId);
|
||||||
|
|
||||||
|
return ['SUCCESS', $newAttachmentID, $postId];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function image_filename($ind)
|
||||||
|
{
|
||||||
|
return 'ailabs_' . $this->job['ailabs_user_id'] . '_' . $this->job_id . '_' . $ind;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function save_base64_to_temp_file($base64, $ind, $ext = '.png')
|
||||||
|
{
|
||||||
|
$temp_dir_path = sys_get_temp_dir();
|
||||||
|
|
||||||
|
if (substr($temp_dir_path, -1) != DIRECTORY_SEPARATOR)
|
||||||
|
$temp_dir_path .= DIRECTORY_SEPARATOR;
|
||||||
|
|
||||||
|
$filename = $temp_dir_path . $this->image_filename($ind) . $ext;
|
||||||
|
|
||||||
|
$handle = fopen($filename, 'wb');
|
||||||
|
fwrite($handle, base64_decode($base64));
|
||||||
|
fclose($handle);
|
||||||
|
|
||||||
|
return $filename;
|
||||||
|
}
|
||||||
|
}
|
159
privet/ailabs/includes/GenericController.php
Normal file
159
privet/ailabs/includes/GenericController.php
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
<?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\includes;
|
||||||
|
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use privet\ailabs\includes\AIController;
|
||||||
|
use privet\ailabs\includes\resultParse;
|
||||||
|
use privet\ailabs\includes\resultSubmit;
|
||||||
|
|
||||||
|
class GenericController extends AIController
|
||||||
|
{
|
||||||
|
// Cleansing and setting back $this->job['request']
|
||||||
|
// Return $opts or empty array
|
||||||
|
protected function init()
|
||||||
|
{
|
||||||
|
$this->job['status'] = 'exec';
|
||||||
|
|
||||||
|
$set = [
|
||||||
|
'status' => $this->job['status'],
|
||||||
|
'log' => json_encode($this->log)
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->job_update($set);
|
||||||
|
$this->post_update($this->job);
|
||||||
|
|
||||||
|
$total_replaced = 0;
|
||||||
|
$original_request = $this->job['request'];
|
||||||
|
$this->job['request'] = str_replace('@' . $this->job['ailabs_username'], '', $this->job['request'], $total_replaced);
|
||||||
|
|
||||||
|
if ($total_replaced > 0) {
|
||||||
|
$this->job['request'] = str_replace(' ', ' ', $this->job['request']);
|
||||||
|
$this->log['request.original'] = $original_request;
|
||||||
|
$this->log['request.adjusted'] = $this->job['request'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function prepare($opts)
|
||||||
|
{
|
||||||
|
return $opts;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function submit($opts): resultSubmit
|
||||||
|
{
|
||||||
|
$result = new resultSubmit();
|
||||||
|
$result->response = '';
|
||||||
|
$result->responseCodes = [];
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override this method to extract response image(s)/message(s) and set job status
|
||||||
|
protected function parse(resultSubmit $resultSubmit): resultParse
|
||||||
|
{
|
||||||
|
$result = new resultParse();
|
||||||
|
$result->json = json_decode($resultSubmit->response);
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function process()
|
||||||
|
{
|
||||||
|
$opts = $this->init();
|
||||||
|
$this->log_flush();
|
||||||
|
|
||||||
|
$opts = $this->prepare($opts);
|
||||||
|
|
||||||
|
$this->log['request.json'] = $opts;
|
||||||
|
$this->log_flush();
|
||||||
|
|
||||||
|
$this->job['status'] = 'fail';
|
||||||
|
|
||||||
|
$resultSubmit = null;
|
||||||
|
$resultParse = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$resultSubmit = $this->submit($opts);
|
||||||
|
|
||||||
|
$this->log['response.length'] = strlen($resultSubmit->response);
|
||||||
|
$this->log['response.codes'] = $resultSubmit->responseCodes;
|
||||||
|
$this->log_flush();
|
||||||
|
|
||||||
|
$resultParse = $this->parse($resultSubmit);
|
||||||
|
|
||||||
|
$this->log['response.json'] = $resultParse->json;
|
||||||
|
$this->log_flush();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->log['exception'] = $e->getMessage();
|
||||||
|
$this->log_flush();
|
||||||
|
|
||||||
|
$this->log['response.raw'] = $resultSubmit->response;
|
||||||
|
$this->log_flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->log['finish'] = date('Y-m-d H:i:s');
|
||||||
|
|
||||||
|
$response = $this->replace_vars($this->job, $resultParse);
|
||||||
|
|
||||||
|
$data = $this->post_response($this->job, $response);
|
||||||
|
|
||||||
|
$this->job['response_time'] = time();
|
||||||
|
$this->job['response_post_id'] = $data['post_id'];
|
||||||
|
|
||||||
|
if (!empty($resultParse->images)) {
|
||||||
|
$this->log['attachments_start_time'] = date('Y-m-d H:i:s');
|
||||||
|
$ind = 0;
|
||||||
|
foreach ($resultParse->images as $url_or_filename) {
|
||||||
|
$is_url = filter_var($url_or_filename, FILTER_VALIDATE_URL) !== false;
|
||||||
|
$attachemnt_name = $is_url ? $this->image_filename($ind) : basename($url_or_filename);
|
||||||
|
$attachment = $this->attach_to_post(
|
||||||
|
$is_url,
|
||||||
|
$url_or_filename,
|
||||||
|
+$data['post_id'],
|
||||||
|
+$this->job['topic_id'],
|
||||||
|
+$this->job['forum_id'],
|
||||||
|
+$this->job['ailabs_user_id'],
|
||||||
|
$attachemnt_name
|
||||||
|
);
|
||||||
|
|
||||||
|
// If you getting error REMOTE_UPLOAD_TIMEOUT try to adjust php.ini as described at https://stackoverflow.com/questions/52069439/upload-large-files-and-time-out-php-uploads
|
||||||
|
// Edit /etc/php/8.2/fpm/php.ini, eg
|
||||||
|
// max_execution_time = 120
|
||||||
|
// max_input_time = 120
|
||||||
|
// upload_max_filesize = 10M
|
||||||
|
|
||||||
|
$this->log['attachment_' . $attachemnt_name] = [
|
||||||
|
'is_url' => $is_url,
|
||||||
|
'url_or_filename' => $url_or_filename,
|
||||||
|
'result' => $attachment
|
||||||
|
];
|
||||||
|
|
||||||
|
$ind++;
|
||||||
|
}
|
||||||
|
$this->log['attachments_finish_time'] = date('Y-m-d H:i:s');
|
||||||
|
}
|
||||||
|
|
||||||
|
$set = [
|
||||||
|
'status' => $this->job['status'],
|
||||||
|
'attempts' => $this->job['attempts'] + 1,
|
||||||
|
'response_time' => $this->job['response_time'],
|
||||||
|
'response' => empty($resultParse->images) ? $resultParse->message : implode(PHP_EOL, $resultParse->images),
|
||||||
|
'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);
|
||||||
|
}
|
||||||
|
}
|
151
privet/ailabs/includes/GenericCurl.php
Normal file
151
privet/ailabs/includes/GenericCurl.php
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
<?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\includes;
|
||||||
|
|
||||||
|
class GenericCurl
|
||||||
|
{
|
||||||
|
private array $headers;
|
||||||
|
private array $contentTypes;
|
||||||
|
private int $timeout = 0;
|
||||||
|
private object $stream_method;
|
||||||
|
private string $proxy = "";
|
||||||
|
private array $curlInfo = [];
|
||||||
|
public int $retryCount;
|
||||||
|
public int $timeoutBeforeRetrySec;
|
||||||
|
public array $responseCodes = [];
|
||||||
|
|
||||||
|
public function __construct($API_KEY, $retryCount = 3, $timeoutBeforeRetrySec = 10)
|
||||||
|
{
|
||||||
|
$this->contentTypes = [
|
||||||
|
"application/json" => "Content-Type: application/json",
|
||||||
|
"multipart/form-data" => "Content-Type: multipart/form-data",
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->headers = [
|
||||||
|
$this->contentTypes["application/json"],
|
||||||
|
"Authorization: Bearer $API_KEY",
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->retryCount = $retryCount;
|
||||||
|
$this->timeoutBeforeRetrySec = $timeoutBeforeRetrySec;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
* Remove this method from your code before deploying
|
||||||
|
*/
|
||||||
|
public function getCURLInfo()
|
||||||
|
{
|
||||||
|
return $this->curlInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $timeout
|
||||||
|
*/
|
||||||
|
public function setTimeout(int $timeout)
|
||||||
|
{
|
||||||
|
$this->timeout = $timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $proxy
|
||||||
|
*/
|
||||||
|
public function setProxy(string $proxy)
|
||||||
|
{
|
||||||
|
if ($proxy && strpos($proxy, '://') === false) {
|
||||||
|
$proxy = 'https://' . $proxy;
|
||||||
|
}
|
||||||
|
$this->proxy = $proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $header
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function setHeader(array $header)
|
||||||
|
{
|
||||||
|
if ($header) {
|
||||||
|
foreach ($header as $key => $value) {
|
||||||
|
$this->headers[$key] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $url
|
||||||
|
* @param string $method
|
||||||
|
* @param array $opts
|
||||||
|
* @return bool|string
|
||||||
|
*/
|
||||||
|
public function sendRequest(string $url, string $method, array $opts = [])
|
||||||
|
{
|
||||||
|
$this->responseCodes = [];
|
||||||
|
|
||||||
|
$post_fields = json_encode($opts);
|
||||||
|
|
||||||
|
if (array_key_exists('file', $opts) || array_key_exists('image', $opts)) {
|
||||||
|
$this->headers[0] = $this->contentTypes["multipart/form-data"];
|
||||||
|
$post_fields = $opts;
|
||||||
|
} else {
|
||||||
|
$this->headers[0] = $this->contentTypes["application/json"];
|
||||||
|
}
|
||||||
|
|
||||||
|
$curl_info = [
|
||||||
|
CURLOPT_URL => $url,
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_ENCODING => '',
|
||||||
|
CURLOPT_MAXREDIRS => 10,
|
||||||
|
CURLOPT_TIMEOUT => $this->timeout,
|
||||||
|
CURLOPT_FOLLOWLOCATION => true,
|
||||||
|
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
|
||||||
|
CURLOPT_CUSTOMREQUEST => $method,
|
||||||
|
CURLOPT_POSTFIELDS => $post_fields,
|
||||||
|
CURLOPT_HTTPHEADER => $this->headers,
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($opts == []) {
|
||||||
|
unset($curl_info[CURLOPT_POSTFIELDS]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($this->proxy)) {
|
||||||
|
$curl_info[CURLOPT_PROXY] = $this->proxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (array_key_exists('stream', $opts) && $opts['stream']) {
|
||||||
|
$curl_info[CURLOPT_WRITEFUNCTION] = $this->stream_method;
|
||||||
|
}
|
||||||
|
|
||||||
|
$curl = curl_init();
|
||||||
|
|
||||||
|
curl_setopt_array($curl, $curl_info);
|
||||||
|
$retryCount = 0;
|
||||||
|
$responseCode = 0;
|
||||||
|
$response = null;
|
||||||
|
|
||||||
|
do {
|
||||||
|
$retryCount++;
|
||||||
|
$response = curl_exec($curl);
|
||||||
|
$responseCode = curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
|
||||||
|
array_push($this->responseCodes, $responseCode);
|
||||||
|
} while (
|
||||||
|
$responseCode !== 200 &&
|
||||||
|
$retryCount < $this->retryCount &&
|
||||||
|
sleep($this->timeoutBeforeRetrySec) !== false
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->curlInfo = curl_getinfo($curl);
|
||||||
|
|
||||||
|
curl_close($curl);
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
20
privet/ailabs/includes/resultParse.php
Normal file
20
privet/ailabs/includes/resultParse.php
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<?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\includes;
|
||||||
|
|
||||||
|
class resultParse
|
||||||
|
{
|
||||||
|
public $message;
|
||||||
|
public $images;
|
||||||
|
public $info;
|
||||||
|
public $json;
|
||||||
|
}
|
18
privet/ailabs/includes/resultSubmit.php
Normal file
18
privet/ailabs/includes/resultSubmit.php
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<?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\includes;
|
||||||
|
|
||||||
|
class resultSubmit
|
||||||
|
{
|
||||||
|
public string $response;
|
||||||
|
public array $responseCodes;
|
||||||
|
};
|
28
privet/ailabs/language/en/common.php
Normal file
28
privet/ailabs/language/en/common.php
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* AI Labs extension
|
||||||
|
*
|
||||||
|
* @copyright (c) 2023, privet.fun, https://privet.fun
|
||||||
|
* @license GNU General Public License, version 2 (GPL-2.0)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('IN_PHPBB')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($lang) || !is_array($lang)) {
|
||||||
|
$lang = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$lang = array_merge($lang, [
|
||||||
|
'AILABS_ERROR_CHECK_LOGS' => '[color=#FF0000]Error. Pelase check logs.[/color]',
|
||||||
|
'AILABS_POSTS_DISCARDED' => ', posts starting from [url=/viewtopic.php?p=%1$d#p%1$d]this post[/url] were discarded',
|
||||||
|
'AILABS_DISCARDED_INFO' => '[size=75][url=/viewtopic.php?p=%1$d#p%1$d]Beginning[/url] of a conversation containing %2$d posts%3$s (%4$d tokens of %5$d were used)[/size]',
|
||||||
|
'AILABS_THINKING' => 'thinking',
|
||||||
|
'AILABS_REPLYING' => 'replying…',
|
||||||
|
'AILABS_REPLIED' => 'replied ↓',
|
||||||
|
'AILABS_UNABLE_TO_REPLY' => 'unable to reply'
|
||||||
|
]);
|
58
privet/ailabs/language/en/info_acp_ailabs.php
Normal file
58
privet/ailabs/language/en/info_acp_ailabs.php
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* AI Labs extension
|
||||||
|
*
|
||||||
|
* @copyright (c) 2023, privet.fun, https://privet.fun
|
||||||
|
* @license GNU General Public License, version 2 (GPL-2.0)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('IN_PHPBB')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($lang) || !is_array($lang)) {
|
||||||
|
$lang = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$lang = array_merge($lang, [
|
||||||
|
'ACP_AILABS_TITLE' => 'AI Labs',
|
||||||
|
'ACP_AILABS_TITLE_VIEW' => 'AI Labs View Configuration',
|
||||||
|
'ACP_AILABS_TITLE_ADD' => 'AI Labs Add Configuration',
|
||||||
|
'ACP_AILABS_TITLE_EDIT' => 'AI Labs Edit Configuration',
|
||||||
|
'ACP_AILABS_SETTINGS' => 'Settings',
|
||||||
|
|
||||||
|
'ACP_AILABS_ADD' => 'Add Configuration',
|
||||||
|
|
||||||
|
'AILABS_USER_EMPTY' => 'Please select user',
|
||||||
|
'AILABS_USER_NOT_FOUND' => 'Unable to locate user %1$s',
|
||||||
|
'AILABS_USER_ALREADY_CONFIGURED' => 'User %1$s already configured, only one configuration per user supported',
|
||||||
|
'AILABS_SPECIFY_POST_OR_MENTION' => 'Both Reply on a post and Reply when quoted can\'t be empty, please specify at least one',
|
||||||
|
|
||||||
|
'LOG_ACP_AILABS_ADDED' => 'AI Labs configuration added',
|
||||||
|
'LOG_ACP_AILABS_EDITED' => 'AI Labs configuration updated',
|
||||||
|
'LOG_ACP_AILABS_DELETED' => 'AI Labs configuration deleted',
|
||||||
|
|
||||||
|
'ACP_AILABS_ADDED' => 'Configuration successfully created',
|
||||||
|
'ACP_AILABS_UPDATED' => 'Configuration successfully updated',
|
||||||
|
'ACP_AILABS_DELETED_CONFIRM' => 'Are you sure that you wish to delete the configuration associated with user %1$s?',
|
||||||
|
|
||||||
|
'LBL_AILABS_SETTINGS_DESC' => 'Please visit 👉 <a href="https://github.com/privet-fun/phpbb_ailabs">https://github.com/privet-fun/phpbb_ailabs</a> for detailed configuration instructions and examples',
|
||||||
|
'LBL_AILABS_USERNAME' => 'User Name',
|
||||||
|
'LBL_AILABS_CONTROLLER' => 'AI',
|
||||||
|
'LBL_AILABS_CONFIG' => 'Configuration JSON',
|
||||||
|
'LBL_AILABS_TEMPLATE' => 'Template',
|
||||||
|
'LBL_AILABS_REPLY_POST_FORUMS' => 'Reply on a post',
|
||||||
|
'LBL_AILABS_REPLY_QUOTE_FORUMS' => 'Reply when quoted',
|
||||||
|
'LBL_AILABS_ENABLED' => 'Enabled',
|
||||||
|
'LBL_AILABS_SELECT_FORUMS' => 'Select forums...',
|
||||||
|
|
||||||
|
'LBL_AILABS_CONFIG_EXPLAIN' => 'Must be valid JSON, please refer to documnetation for details',
|
||||||
|
'LBL_AILABS_TEMPLATE_EXPLAIN' => 'Valid variables: {post_id}, {request}, {info}, {response}, {images}, {attachments}, {poster_id}, {poster_name}, {ailabs_username}',
|
||||||
|
'LBL_AILABS_REPLY_POST_FORUMS_EXPLAIN' => 'Specify forums where AI will reply to new posts',
|
||||||
|
'LBL_AILABS_REPLY_QUOTE_FORUMS_EXPLAIN' => 'Specify forums where AI will reply to quoted posts',
|
||||||
|
'LBL_AILABS_CONFIG_DEFAULT' => 'Load default configuration',
|
||||||
|
'LBL_AILABS_TEMPLATE_DEFAULT' => 'Load default template',
|
||||||
|
]);
|
28
privet/ailabs/language/ru/common.php
Normal file
28
privet/ailabs/language/ru/common.php
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* AI Labs extension
|
||||||
|
*
|
||||||
|
* @copyright (c) 2023, privet.fun, https://privet.fun
|
||||||
|
* @license GNU General Public License, version 2 (GPL-2.0)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('IN_PHPBB')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($lang) || !is_array($lang)) {
|
||||||
|
$lang = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$lang = array_merge($lang, [
|
||||||
|
'AILABS_ERROR_CHECK_LOGS' => '[color=#FF0000]Ошибка. Лог содержит детальную информацию.[/color]',
|
||||||
|
'AILABS_POSTS_DISCARDED' => ', сообщения начиная с [url=/viewtopic.php?p=%1$d#p%1$d]этого[/url] не включены',
|
||||||
|
'AILABS_DISCARDED_INFO' => '[size=75][url=/viewtopic.php?p=%1$d#p%1$d]Начало[/url] беседы из %2$d сообщений%3$s (%4$d токенов из %5$d использовано)[/size]',
|
||||||
|
'AILABS_THINKING' => 'думает',
|
||||||
|
'AILABS_REPLYING' => 'отвечает…',
|
||||||
|
'AILABS_REPLIED' => 'ответил ↓',
|
||||||
|
'AILABS_UNABLE_TO_REPLY' => 'ответить не смог'
|
||||||
|
]);
|
58
privet/ailabs/language/ru/info_acp_ailabs.php
Normal file
58
privet/ailabs/language/ru/info_acp_ailabs.php
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* AI Labs extension
|
||||||
|
*
|
||||||
|
* @copyright (c) 2023, privet.fun, https://privet.fun
|
||||||
|
* @license GNU General Public License, version 2 (GPL-2.0)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (!defined('IN_PHPBB')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($lang) || !is_array($lang)) {
|
||||||
|
$lang = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$lang = array_merge($lang, [
|
||||||
|
'ACP_AILABS_TITLE' => 'AI Labs',
|
||||||
|
'ACP_AILABS_TITLE_VIEW' => 'Просмотр конфигурации AI Labs',
|
||||||
|
'ACP_AILABS_TITLE_ADD' => 'Добавить конфигурацию AI Labs',
|
||||||
|
'ACP_AILABS_TITLE_EDIT' => 'Изменить конфигурацию AI Labs',
|
||||||
|
'ACP_AILABS_SETTINGS' => 'Настройки',
|
||||||
|
|
||||||
|
'ACP_AILABS_ADD' => 'Добавить конфигурацию',
|
||||||
|
|
||||||
|
'AILABS_USER_EMPTY' => 'Пожалуйста, выберите пользователя',
|
||||||
|
'AILABS_USER_NOT_FOUND' => 'Не удалось найти пользователя %1$s',
|
||||||
|
'AILABS_USER_ALREADY_CONFIGURED' => 'Пользователь %1$s уже настроен, поддерживается только одна конфигурация на пользователя',
|
||||||
|
'AILABS_SPECIFY_POST_OR_MENTION' => 'Нельзя оставлять пустыми и "Ответ на сообщение" и "Ответ при цитировании", пожалуйста, укажите хотя бы одно значение',
|
||||||
|
|
||||||
|
'LOG_ACP_AILABS_ADDED' => 'Конфигурация AI Labs добавлена',
|
||||||
|
'LOG_ACP_AILABS_EDITED' => 'Конфигурация AI Labs изменена',
|
||||||
|
'LOG_ACP_AILABS_DELETED' => 'Конфигурация AI Labs удалена',
|
||||||
|
|
||||||
|
'ACP_AILABS_ADDED' => 'Конфигурация успешно создана',
|
||||||
|
'ACP_AILABS_UPDATED' => 'Конфигурация успешно обновлена',
|
||||||
|
'ACP_AILABS_DELETED_CONFIRM' => 'Вы уверены, что хотите удалить конфигурацию, связанную с пользователем %1$s?',
|
||||||
|
|
||||||
|
'LBL_AILABS_SETTINGS_DESC' => 'Пожалуйста, посетите 👉 <a href="https://github.com/privet-fun/phpbb_ailabs">https://github.com/privet-fun/phpbb_ailabs</a> для получения подробных инструкций по настройке и примеров',
|
||||||
|
'LBL_AILABS_USERNAME' => 'Имя пользователя',
|
||||||
|
'LBL_AILABS_CONTROLLER' => 'AI',
|
||||||
|
'LBL_AILABS_CONFIG' => 'Конфигурация в формате JSON',
|
||||||
|
'LBL_AILABS_TEMPLATE' => 'Шаблон',
|
||||||
|
'LBL_AILABS_REPLY_POST_FORUMS' => 'Ответ на сообщение',
|
||||||
|
'LBL_AILABS_REPLY_QUOTE_FORUMS' => 'Ответ при цитировании',
|
||||||
|
'LBL_AILABS_ENABLED' => 'Включено',
|
||||||
|
'LBL_AILABS_SELECT_FORUMS' => 'Выберите форумы...',
|
||||||
|
|
||||||
|
'LBL_AILABS_CONFIG_EXPLAIN' => 'Пожалуйста, обратитесь к документации для получения подробных инструкций по настройке и примеров',
|
||||||
|
'LBL_AILABS_TEMPLATE_EXPLAIN' => 'Допустимые переменные: {post_id}, {request}, {info}, {response}, {images}, {attachments}, {poster_id}, {poster_name}, {ailabs_username}',
|
||||||
|
'LBL_AILABS_REPLY_POST_FORUMS_EXPLAIN' => 'Укажите форумы, на которых AI будет отвечать на новые сообщения',
|
||||||
|
'LBL_AILABS_REPLY_QUOTE_FORUMS_EXPLAIN' => 'Укажите форумы, на которых AI будет отвечать на цитируемые сообщения',
|
||||||
|
'LBL_AILABS_CONFIG_DEFAULT' => 'Загрузить конфигурацию по умолчанию',
|
||||||
|
'LBL_AILABS_TEMPLATE_DEFAULT' => 'Загрузить шаблон по умолчанию',
|
||||||
|
]);
|
280
privet/ailabs/license.txt
Normal file
280
privet/ailabs/license.txt
Normal file
|
@ -0,0 +1,280 @@
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 2, June 1991
|
||||||
|
|
||||||
|
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||||
|
675 Mass Ave, Cambridge, MA 02139, USA
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The licenses for most software are designed to take away your
|
||||||
|
freedom to share and change it. By contrast, the GNU General Public
|
||||||
|
License is intended to guarantee your freedom to share and change free
|
||||||
|
software--to make sure the software is free for all its users. This
|
||||||
|
General Public License applies to most of the Free Software
|
||||||
|
Foundation's software and to any other program whose authors commit to
|
||||||
|
using it. (Some other Free Software Foundation software is covered by
|
||||||
|
the GNU Library General Public License instead.) You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
this service if you wish), that you receive source code or can get it
|
||||||
|
if you want it, that you can change the software or use pieces of it
|
||||||
|
in new free programs; and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to make restrictions that forbid
|
||||||
|
anyone to deny you these rights or to ask you to surrender the rights.
|
||||||
|
These restrictions translate to certain responsibilities for you if you
|
||||||
|
distribute copies of the software, or if you modify it.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must give the recipients all the rights that
|
||||||
|
you have. You must make sure that they, too, receive or can get the
|
||||||
|
source code. And you must show them these terms so they know their
|
||||||
|
rights.
|
||||||
|
|
||||||
|
We protect your rights with two steps: (1) copyright the software, and
|
||||||
|
(2) offer you this license which gives you legal permission to copy,
|
||||||
|
distribute and/or modify the software.
|
||||||
|
|
||||||
|
Also, for each author's protection and ours, we want to make certain
|
||||||
|
that everyone understands that there is no warranty for this free
|
||||||
|
software. If the software is modified by someone else and passed on, we
|
||||||
|
want its recipients to know that what they have is not the original, so
|
||||||
|
that any problems introduced by others will not reflect on the original
|
||||||
|
authors' reputations.
|
||||||
|
|
||||||
|
Finally, any free program is threatened constantly by software
|
||||||
|
patents. We wish to avoid the danger that redistributors of a free
|
||||||
|
program will individually obtain patent licenses, in effect making the
|
||||||
|
program proprietary. To prevent this, we have made it clear that any
|
||||||
|
patent must be licensed for everyone's free use or not licensed at all.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. This License applies to any program or other work which contains
|
||||||
|
a notice placed by the copyright holder saying it may be distributed
|
||||||
|
under the terms of this General Public License. The "Program", below,
|
||||||
|
refers to any such program or work, and a "work based on the Program"
|
||||||
|
means either the Program or any derivative work under copyright law:
|
||||||
|
that is to say, a work containing the Program or a portion of it,
|
||||||
|
either verbatim or with modifications and/or translated into another
|
||||||
|
language. (Hereinafter, translation is included without limitation in
|
||||||
|
the term "modification".) Each licensee is addressed as "you".
|
||||||
|
|
||||||
|
Activities other than copying, distribution and modification are not
|
||||||
|
covered by this License; they are outside its scope. The act of
|
||||||
|
running the Program is not restricted, and the output from the Program
|
||||||
|
is covered only if its contents constitute a work based on the
|
||||||
|
Program (independent of having been made by running the Program).
|
||||||
|
Whether that is true depends on what the Program does.
|
||||||
|
|
||||||
|
1. You may copy and distribute verbatim copies of the Program's
|
||||||
|
source code as you receive it, in any medium, provided that you
|
||||||
|
conspicuously and appropriately publish on each copy an appropriate
|
||||||
|
copyright notice and disclaimer of warranty; keep intact all the
|
||||||
|
notices that refer to this License and to the absence of any warranty;
|
||||||
|
and give any other recipients of the Program a copy of this License
|
||||||
|
along with the Program.
|
||||||
|
|
||||||
|
You may charge a fee for the physical act of transferring a copy, and
|
||||||
|
you may at your option offer warranty protection in exchange for a fee.
|
||||||
|
|
||||||
|
2. You may modify your copy or copies of the Program or any portion
|
||||||
|
of it, thus forming a work based on the Program, and copy and
|
||||||
|
distribute such modifications or work under the terms of Section 1
|
||||||
|
above, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) You must cause the modified files to carry prominent notices
|
||||||
|
stating that you changed the files and the date of any change.
|
||||||
|
|
||||||
|
b) You must cause any work that you distribute or publish, that in
|
||||||
|
whole or in part contains or is derived from the Program or any
|
||||||
|
part thereof, to be licensed as a whole at no charge to all third
|
||||||
|
parties under the terms of this License.
|
||||||
|
|
||||||
|
c) If the modified program normally reads commands interactively
|
||||||
|
when run, you must cause it, when started running for such
|
||||||
|
interactive use in the most ordinary way, to print or display an
|
||||||
|
announcement including an appropriate copyright notice and a
|
||||||
|
notice that there is no warranty (or else, saying that you provide
|
||||||
|
a warranty) and that users may redistribute the program under
|
||||||
|
these conditions, and telling the user how to view a copy of this
|
||||||
|
License. (Exception: if the Program itself is interactive but
|
||||||
|
does not normally print such an announcement, your work based on
|
||||||
|
the Program is not required to print an announcement.)
|
||||||
|
|
||||||
|
These requirements apply to the modified work as a whole. If
|
||||||
|
identifiable sections of that work are not derived from the Program,
|
||||||
|
and can be reasonably considered independent and separate works in
|
||||||
|
themselves, then this License, and its terms, do not apply to those
|
||||||
|
sections when you distribute them as separate works. But when you
|
||||||
|
distribute the same sections as part of a whole which is a work based
|
||||||
|
on the Program, the distribution of the whole must be on the terms of
|
||||||
|
this License, whose permissions for other licensees extend to the
|
||||||
|
entire whole, and thus to each and every part regardless of who wrote it.
|
||||||
|
|
||||||
|
Thus, it is not the intent of this section to claim rights or contest
|
||||||
|
your rights to work written entirely by you; rather, the intent is to
|
||||||
|
exercise the right to control the distribution of derivative or
|
||||||
|
collective works based on the Program.
|
||||||
|
|
||||||
|
In addition, mere aggregation of another work not based on the Program
|
||||||
|
with the Program (or with a work based on the Program) on a volume of
|
||||||
|
a storage or distribution medium does not bring the other work under
|
||||||
|
the scope of this License.
|
||||||
|
|
||||||
|
3. You may copy and distribute the Program (or a work based on it,
|
||||||
|
under Section 2) in object code or executable form under the terms of
|
||||||
|
Sections 1 and 2 above provided that you also do one of the following:
|
||||||
|
|
||||||
|
a) Accompany it with the complete corresponding machine-readable
|
||||||
|
source code, which must be distributed under the terms of Sections
|
||||||
|
1 and 2 above on a medium customarily used for software interchange; or,
|
||||||
|
|
||||||
|
b) Accompany it with a written offer, valid for at least three
|
||||||
|
years, to give any third party, for a charge no more than your
|
||||||
|
cost of physically performing source distribution, a complete
|
||||||
|
machine-readable copy of the corresponding source code, to be
|
||||||
|
distributed under the terms of Sections 1 and 2 above on a medium
|
||||||
|
customarily used for software interchange; or,
|
||||||
|
|
||||||
|
c) Accompany it with the information you received as to the offer
|
||||||
|
to distribute corresponding source code. (This alternative is
|
||||||
|
allowed only for noncommercial distribution and only if you
|
||||||
|
received the program in object code or executable form with such
|
||||||
|
an offer, in accord with Subsection b above.)
|
||||||
|
|
||||||
|
The source code for a work means the preferred form of the work for
|
||||||
|
making modifications to it. For an executable work, complete source
|
||||||
|
code means all the source code for all modules it contains, plus any
|
||||||
|
associated interface definition files, plus the scripts used to
|
||||||
|
control compilation and installation of the executable. However, as a
|
||||||
|
special exception, the source code distributed need not include
|
||||||
|
anything that is normally distributed (in either source or binary
|
||||||
|
form) with the major components (compiler, kernel, and so on) of the
|
||||||
|
operating system on which the executable runs, unless that component
|
||||||
|
itself accompanies the executable.
|
||||||
|
|
||||||
|
If distribution of executable or object code is made by offering
|
||||||
|
access to copy from a designated place, then offering equivalent
|
||||||
|
access to copy the source code from the same place counts as
|
||||||
|
distribution of the source code, even though third parties are not
|
||||||
|
compelled to copy the source along with the object code.
|
||||||
|
|
||||||
|
4. You may not copy, modify, sublicense, or distribute the Program
|
||||||
|
except as expressly provided under this License. Any attempt
|
||||||
|
otherwise to copy, modify, sublicense or distribute the Program is
|
||||||
|
void, and will automatically terminate your rights under this License.
|
||||||
|
However, parties who have received copies, or rights, from you under
|
||||||
|
this License will not have their licenses terminated so long as such
|
||||||
|
parties remain in full compliance.
|
||||||
|
|
||||||
|
5. You are not required to accept this License, since you have not
|
||||||
|
signed it. However, nothing else grants you permission to modify or
|
||||||
|
distribute the Program or its derivative works. These actions are
|
||||||
|
prohibited by law if you do not accept this License. Therefore, by
|
||||||
|
modifying or distributing the Program (or any work based on the
|
||||||
|
Program), you indicate your acceptance of this License to do so, and
|
||||||
|
all its terms and conditions for copying, distributing or modifying
|
||||||
|
the Program or works based on it.
|
||||||
|
|
||||||
|
6. Each time you redistribute the Program (or any work based on the
|
||||||
|
Program), the recipient automatically receives a license from the
|
||||||
|
original licensor to copy, distribute or modify the Program subject to
|
||||||
|
these terms and conditions. You may not impose any further
|
||||||
|
restrictions on the recipients' exercise of the rights granted herein.
|
||||||
|
You are not responsible for enforcing compliance by third parties to
|
||||||
|
this License.
|
||||||
|
|
||||||
|
7. If, as a consequence of a court judgment or allegation of patent
|
||||||
|
infringement or for any other reason (not limited to patent issues),
|
||||||
|
conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot
|
||||||
|
distribute so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you
|
||||||
|
may not distribute the Program at all. For example, if a patent
|
||||||
|
license would not permit royalty-free redistribution of the Program by
|
||||||
|
all those who receive copies directly or indirectly through you, then
|
||||||
|
the only way you could satisfy both it and this License would be to
|
||||||
|
refrain entirely from distribution of the Program.
|
||||||
|
|
||||||
|
If any portion of this section is held invalid or unenforceable under
|
||||||
|
any particular circumstance, the balance of the section is intended to
|
||||||
|
apply and the section as a whole is intended to apply in other
|
||||||
|
circumstances.
|
||||||
|
|
||||||
|
It is not the purpose of this section to induce you to infringe any
|
||||||
|
patents or other property right claims or to contest validity of any
|
||||||
|
such claims; this section has the sole purpose of protecting the
|
||||||
|
integrity of the free software distribution system, which is
|
||||||
|
implemented by public license practices. Many people have made
|
||||||
|
generous contributions to the wide range of software distributed
|
||||||
|
through that system in reliance on consistent application of that
|
||||||
|
system; it is up to the author/donor to decide if he or she is willing
|
||||||
|
to distribute software through any other system and a licensee cannot
|
||||||
|
impose that choice.
|
||||||
|
|
||||||
|
This section is intended to make thoroughly clear what is believed to
|
||||||
|
be a consequence of the rest of this License.
|
||||||
|
|
||||||
|
8. If the distribution and/or use of the Program is restricted in
|
||||||
|
certain countries either by patents or by copyrighted interfaces, the
|
||||||
|
original copyright holder who places the Program under this License
|
||||||
|
may add an explicit geographical distribution limitation excluding
|
||||||
|
those countries, so that distribution is permitted only in or among
|
||||||
|
countries not thus excluded. In such case, this License incorporates
|
||||||
|
the limitation as if written in the body of this License.
|
||||||
|
|
||||||
|
9. The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the Program
|
||||||
|
specifies a version number of this License which applies to it and "any
|
||||||
|
later version", you have the option of following the terms and conditions
|
||||||
|
either of that version or of any later version published by the Free
|
||||||
|
Software Foundation. If the Program does not specify a version number of
|
||||||
|
this License, you may choose any version ever published by the Free Software
|
||||||
|
Foundation.
|
||||||
|
|
||||||
|
10. If you wish to incorporate parts of the Program into other free
|
||||||
|
programs whose distribution conditions are different, write to the author
|
||||||
|
to ask for permission. For software which is copyrighted by the Free
|
||||||
|
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||||
|
make exceptions for this. Our decision will be guided by the two goals
|
||||||
|
of preserving the free status of all derivatives of our free software and
|
||||||
|
of promoting the sharing and reuse of software generally.
|
||||||
|
|
||||||
|
NO WARRANTY
|
||||||
|
|
||||||
|
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||||
|
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||||
|
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||||
|
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||||
|
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||||
|
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||||
|
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||||
|
REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||||
|
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||||
|
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||||
|
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||||
|
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||||
|
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||||
|
POSSIBILITY OF SUCH DAMAGES.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
110
privet/ailabs/migrations/v1x/release_1_0_0_schema.php
Normal file
110
privet/ailabs/migrations/v1x/release_1_0_0_schema.php
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
<?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_0_schema extends \phpbb\db\migration\migration
|
||||||
|
{
|
||||||
|
static public function depends_on()
|
||||||
|
{
|
||||||
|
return array('\phpbb\db\migration\data\v320\v320');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update_schema()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'add_tables' => [
|
||||||
|
// Config table
|
||||||
|
$this->table_prefix . 'ailabs_users' => [
|
||||||
|
'COLUMNS' => [
|
||||||
|
'user_id' => ['UINT', 0],
|
||||||
|
'controller' => ['VCHAR', ''], // eg /ailabs/chatgpt
|
||||||
|
'config' => ['TEXT_UNI', ''], // JSON
|
||||||
|
'template' => ['TEXT_UNI', ''], // eg [quote={poster_name} post_id={post_id} user_id={poster_id}]{request}[/quote]{response}{attachments}
|
||||||
|
'forums_post' => ['VCHAR', ''], // eg ["forum_id1","forum_id2"]
|
||||||
|
'forums_mention' => ['VCHAR', ''], // eg ["forum_id1","forum_id2"]
|
||||||
|
'enabled' => ['BOOL', 0],
|
||||||
|
],
|
||||||
|
'PRIMARY_KEY' => 'user_id',
|
||||||
|
],
|
||||||
|
// Jobs table
|
||||||
|
$this->table_prefix . 'ailabs_jobs' => [
|
||||||
|
'COLUMNS' => [
|
||||||
|
'job_id' => ['UINT', null, 'auto_increment'],
|
||||||
|
'ailabs_user_id' => ['UINT', 0],
|
||||||
|
'ailabs_username' => ['VCHAR', ''],
|
||||||
|
'status' => ['VCHAR:10', null], // exec, ok, fail (null -> exec -> ok | fail, null -> exec -> null)
|
||||||
|
'attempts' => ['UINT', 0],
|
||||||
|
'request_time' => ['UINT:11', 0],
|
||||||
|
'response_time' => ['UINT:11', null],
|
||||||
|
'post_mode' => ['VCHAR:10', ''], // post, reply, quote
|
||||||
|
'post_id' => ['UINT:10', 0],
|
||||||
|
'forum_id' => ['UINT:8', 0],
|
||||||
|
'poster_id' => ['UINT:10', 0],
|
||||||
|
'poster_name' => ['VCHAR', ''],
|
||||||
|
'request' => ['TEXT_UNI', ''],
|
||||||
|
'request_tokens' => ['UINT:8', null],
|
||||||
|
'response' => ['TEXT_UNI', null],
|
||||||
|
'response_tokens' => ['UINT:8', null],
|
||||||
|
'response_post_id' => ['UINT:10', null],
|
||||||
|
'log' => ['TEXT_UNI', null],
|
||||||
|
],
|
||||||
|
'PRIMARY_KEY' => 'job_id',
|
||||||
|
'KEYS' => [
|
||||||
|
'idx_ailabs_jobs' => [null, ['response_post_id', 'status']],
|
||||||
|
'idx_ailabs_post_id' => [null, ['post_id']],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'add_columns' => [
|
||||||
|
$this->table_prefix . 'posts' => [
|
||||||
|
'post_ailabs_data' => ['TEXT_UNI', null],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function revert_schema()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'drop_columns' => [
|
||||||
|
$this->table_prefix . 'posts' => [
|
||||||
|
'post_ailabs_data',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'drop_tables' => [
|
||||||
|
$this->table_prefix . 'ailabs_users',
|
||||||
|
$this->table_prefix . 'ailabs_jobs',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://area51.phpbb.com/docs/dev/master/extensions/tutorial_modules.html
|
||||||
|
public function update_data()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
['module.add', [
|
||||||
|
'acp',
|
||||||
|
'ACP_CAT_DOT_MODS',
|
||||||
|
'ACP_AILABS_TITLE'
|
||||||
|
]],
|
||||||
|
|
||||||
|
['module.add', [
|
||||||
|
'acp',
|
||||||
|
'ACP_AILABS_TITLE',
|
||||||
|
[
|
||||||
|
'module_basename' => '\privet\ailabs\acp\main_module',
|
||||||
|
'modes' => ['settings'],
|
||||||
|
],
|
||||||
|
]],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
{% INCLUDECSS '@privet_ailabs/ailabs.css' %}
|
||||||
|
<script>
|
||||||
|
function ailabs_view_log(url)
|
||||||
|
{
|
||||||
|
popup(url, 450, 450, 'AI Labs Log');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
</script>
|
3
privet/ailabs/styles/all/template/js/chosen.jquery.min.js
vendored
Normal file
3
privet/ailabs/styles/all/template/js/chosen.jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
privet/ailabs/styles/all/theme/chosen-sprite.png
Normal file
BIN
privet/ailabs/styles/all/theme/chosen-sprite.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 538 B |
11
privet/ailabs/styles/all/theme/chosen.min.css
vendored
Normal file
11
privet/ailabs/styles/all/theme/chosen.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
43
privet/ailabs/styles/prosilver/template/ailabs.css
Normal file
43
privet/ailabs/styles/prosilver/template/ailabs.css
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
.ailabs {
|
||||||
|
padding: 1px 0;
|
||||||
|
display: inline-flex;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ailabs div {
|
||||||
|
margin-left: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ailabs-log div {
|
||||||
|
padding-bottom: 3px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ailabs-log label {
|
||||||
|
width: 100px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ailabs-log textarea {
|
||||||
|
width: 100%;
|
||||||
|
resize: vertical;
|
||||||
|
/* Allow resizing in both directions */
|
||||||
|
overflow: auto;
|
||||||
|
/* Add scrollbars when content overflows */
|
||||||
|
/* pointer-events: none; */
|
||||||
|
/* Make the textarea read-only */
|
||||||
|
border: none;
|
||||||
|
/* Remove the border from the textarea */
|
||||||
|
outline: none;
|
||||||
|
/* Remove the outline when focused */
|
||||||
|
/* background: inherit; */
|
||||||
|
/* Remove the background */
|
||||||
|
font-family: inherit;
|
||||||
|
/* Inherit the font-family from the parent */
|
||||||
|
font-size: inherit;
|
||||||
|
/* Inherit the font-size from the parent */
|
||||||
|
line-height: inherit;
|
||||||
|
/* Inherit the line-height from the parent */
|
||||||
|
color: inherit;
|
||||||
|
/* Inherit the text color from the parent */
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
{% INCLUDE 'post_ailabs.html' %}
|
19
privet/ailabs/styles/prosilver/template/post_ailabs.html
Normal file
19
privet/ailabs/styles/prosilver/template/post_ailabs.html
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{% if postrow.U_AILABS %}
|
||||||
|
<div id="ailabs-post-{{ postrow.POST_ID }}" class="ailabs">
|
||||||
|
<span>AI </span>
|
||||||
|
{% if postrow.U_AILABS_VIEW_LOG %}
|
||||||
|
<a href="{{ postrow.U_AILABS_VIEW_LOG }}" onclick="ailabs_view_log(this.href);return false;"><i
|
||||||
|
class="icon tpr-font fa-external-link fa-fw"></i></a>
|
||||||
|
{% endif %}
|
||||||
|
{% for ailabs in postrow.U_AILABS %}
|
||||||
|
<div id="ailabs-job-{{ ailabs.job_id }}">
|
||||||
|
<a href="{{ ailabs.user_url }}">{{ ailabs.ailabs_username }}</a>
|
||||||
|
{% if ailabs.response_url %}
|
||||||
|
<a href="{{ ailabs.response_url }}">{{ ailabs.status }}</a>
|
||||||
|
{% else %}
|
||||||
|
<span>{{ ailabs.status }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
88
privet/ailabs/styles/prosilver/template/post_ailabs_log.html
Normal file
88
privet/ailabs/styles/prosilver/template/post_ailabs_log.html
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
{% INCLUDECSS '@privet_ailabs/ailabs.css' %}
|
||||||
|
{% INCLUDE 'simple_header.html' %}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function close_popup() {
|
||||||
|
if (opener != null) {
|
||||||
|
if (opener.close_waitscreen != null) {
|
||||||
|
if (opener.close_waitscreen == 1) {
|
||||||
|
opener.close_waitscreen = 0;
|
||||||
|
self.close();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setTimeout("close_popup()", 1000);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- IF .ailabs_log -->
|
||||||
|
{% for logs in ailabs_log %}
|
||||||
|
{% for log in logs['LOGS'] %}
|
||||||
|
<div class="ailabs-log">
|
||||||
|
<div style="display: block">
|
||||||
|
<label>#{{ log.job_id }} AI
|
||||||
|
<a href="{{ log.ailabs_user_url }}">{{ log.ailabs_username }}</a>
|
||||||
|
→
|
||||||
|
<a href="{{ log.poster_user_url }}">{{ log.poster_name }}</a>
|
||||||
|
{% if log.response_url %}
|
||||||
|
→
|
||||||
|
<a href="{{ log.response_url }}">post #{{ log.response_post_id }}</a>
|
||||||
|
{% endif %}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div><label>Status</label>{{ log.status }}</div>
|
||||||
|
<div><label>Attempts</label>{{ log.attempts }}</div>
|
||||||
|
<div><label>Mode</label>{{ log.post_mode }}</div>
|
||||||
|
<div><label>Request time</label>
|
||||||
|
<div class="datetime-raw">{{ log.request_time }}</div>
|
||||||
|
</div>
|
||||||
|
<!-- IF log.response_time -->
|
||||||
|
<div><label>Response time</label>
|
||||||
|
<div class="datetime-raw">{{ log.response_time }}</div>
|
||||||
|
</div>
|
||||||
|
<!-- ENDIF -->
|
||||||
|
<!-- IF log.request_tokens -->
|
||||||
|
<div><label>Request tokens</label>{{ log.request_tokens }}</div>
|
||||||
|
<!-- ENDIF -->
|
||||||
|
<!-- IF log.response_tokens -->
|
||||||
|
<div><label>Response tokens</label>{{ log.response_tokens }}</div>
|
||||||
|
<!-- ENDIF -->
|
||||||
|
<div><label>Request</label></div>
|
||||||
|
<textarea readonly class="bg1" rows="3">{{ log.request }}</textarea>
|
||||||
|
<!-- IF log.response -->
|
||||||
|
<div><label>Response</label></div>
|
||||||
|
<textarea readonly class="bg1" rows="5">{{ log.response }}</textarea>
|
||||||
|
<!-- ENDIF -->
|
||||||
|
<div><label>Log</label></div>
|
||||||
|
<textarea readonly class="json-raw bg1" rows="9">{{ log.log }}</textarea>
|
||||||
|
<div class="clear"></div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% endfor %}
|
||||||
|
<!-- ENDIF -->
|
||||||
|
|
||||||
|
<div class="clear"></div>
|
||||||
|
|
||||||
|
<div style="text-align: center;"><a href="#" onclick="self.close();return false;">[ {{ lang('CLOSE_WINDOW') }} ]</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
close_popup();
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
var elements = document.getElementsByClassName("json-raw");
|
||||||
|
Array.from(elements).forEach(element => {
|
||||||
|
var obj = JSON.parse(element.innerHTML);
|
||||||
|
element.innerHTML = JSON.stringify(obj, undefined, 2);
|
||||||
|
|
||||||
|
});
|
||||||
|
var elements = document.getElementsByClassName("datetime-raw");
|
||||||
|
Array.from(elements).forEach(element => {
|
||||||
|
if (element && element.innerHTML)
|
||||||
|
element.innerHTML = new Date(element.innerHTML * 1000).toLocaleString();
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
{% INCLUDE 'simple_footer.html' %}
|
Loading…
Reference in a new issue