Compare commits
10 commits
076310e100
...
49a3185585
Author | SHA1 | Date | |
---|---|---|---|
49a3185585 | |||
|
625c47ab44 | ||
|
8c7622eb0b | ||
|
ed3eacff44 | ||
|
8707a1c135 | ||
|
13c1b6b66d | ||
|
5554ef8593 | ||
|
8a9a15a8ac | ||
|
177d396c6d | ||
|
c675a9537c |
|
@ -1,16 +1,31 @@
|
|||
# AI Labs v 1.0.3 RC
|
||||
##### [Changelog](#changelog_link)
|
||||
# AI Labs v 1.0.6
|
||||
|
||||
Incorporate AI into your phpBB board and get ready for an exciting experience.
|
||||
Currently supported ChatGPT, DALL-E (OpenAI) and Stable Diffusion (Stability AI).
|
||||
Midjourney support coming soon.
|
||||
Currently supported Midjourney, ChatGPT, DALL-E (OpenAI) and Stable Diffusion (Stability AI).
|
||||
|
||||
Examples:
|
||||
# Table of Contents
|
||||
1. [Examples](#examples)
|
||||
2. [Requirements](#requirements)
|
||||
3. [Important notes](#important-notes)
|
||||
4. [Installation](#installation)
|
||||
5. [Midjourney setup](#midjourney-setup)
|
||||
6. [ChatGPT setup ](#chatgpt-setup)
|
||||
7. [ChatGPT advanced setup](#chatgpt-advanced-setup)
|
||||
8. [DALL-E setup](#dall-e-setup)
|
||||
9. [DALL-E advanced features](#dall-e-advanced-features)
|
||||
10. [Stable Diffusion setup](#stable-diffusion-setup)
|
||||
11. [Troubleshooting](#troubleshooting)
|
||||
12. [Support and suggestions](#support-and-suggestions)
|
||||
13. [Changelog](#changelog)
|
||||
14. [License](#license)
|
||||
|
||||
## Examples
|
||||
|
||||
- [Midjourney](https://privet-fun.translate.goog/viewtopic.php?t=3404&_x_tr_sl=auto&_x_tr_tl=en&_x_tr_hl=en&_x_tr_pto=wapp)
|
||||
- [ChatGPT](https://privet.fun/viewtopic.php?t=2802)
|
||||
- [ChatGPT, custom prompt](https://privet.fun/viewtopic.php?t=2799)
|
||||
- [DALL-E](https://privet.fun/viewtopic.php?t=2800)
|
||||
- [Stable Diffusion by Stability AI](https://privet.fun/viewtopic.php?t=2801)
|
||||
- [Midjourney, coming soon 🚀](https://privet.fun/viewtopic.php?t=2718)
|
||||
- [Stable Diffusion by Leonardo AI, coming soon 🚀](https://privet.fun/viewtopic.php?t=2605)
|
||||
Also available as Telegram bot https://t.me/stable_diffusion_superbot
|
||||
|
||||
|
@ -31,17 +46,48 @@ Examples:
|
|||
Go to `ACP` > `Posting` > `Manage attachment extensions`, look for `webp`, add it if missing:
|
||||
![Attachment settings](../privet/ailabs/docs/attachment_webp.png)
|
||||
|
||||
Above does not apply to Midjourney, as all generated images are actually stored on your Discord account and served via the Discord CDN.
|
||||
|
||||
* If you have extensions installed that require users to log in, such as [Login Required](https://www.phpbb.com/customise/db/extension/login_required) you will need to whitelist `/ailabs/*` and `/app.php/ailabs/*` since AI Labs extension uses callbacks.
|
||||
|
||||
* Adjust [PHP configuration](https://www.php.net/manual/en/info.configuration.php) to allow longer script execution. ChatGPT API responses may take up to 90 seconds to respond in certain cases. If you have the default settings, your SQL connection will be closed after 30 seconds, preventing the extension from functioning properly.
|
||||
Suggested values for `php.ini`:
|
||||
> max_execution_time = 180
|
||||
> max_input_time = 90
|
||||
|
||||
## Installation
|
||||
|
||||
Download https://github.com/privet-fun/phpbb_ailabs and copy `/privet/ailabs` to `phppp/ext` folder:
|
||||
![Attachment settings](../privet/ailabs/docs/ext_location.png)
|
||||
|
||||
If you have a previous version of this extension installed, you will need to disable it and then enable it again after the new version has been copied over.
|
||||
|
||||
Go to `ACP` > `Customise` > `Manage extensions` and enable the `AI Labs` extension.
|
||||
|
||||
Finally go to `ACP` > `Extensions` > `AI Labs` > `Settings` and add desired AI configurations:
|
||||
![Attachment settings](../privet/ailabs/docs/ailabs_settings.png)
|
||||
|
||||
## ChatGPT basic setup
|
||||
## Midjourney setup
|
||||
|
||||
* You'll need Midjourney Discord and useapi.net accounts with active subscriptions.
|
||||
Follow instructions at https://www.useapi.net/docs/start-here to setup and verify both.
|
||||
|
||||
* Create new board user who will act as AI bot, for our example we will use user `Midjourney`.
|
||||
Make sure this user account is activated and fully functional.
|
||||
|
||||
* Got to `ACP` > `Extensions` > `AI Labs` > `Settings` and add new configuration, select `midjourney` from AI dropdown:
|
||||
![Attachment settings](../privet/ailabs/docs/midjourney_setup.png)
|
||||
|
||||
- Use `Load default configuration/template` to get defaults.
|
||||
Replace Configuration JSON `api-key`, `discord`, `server` and `channel` with your values.
|
||||
- Select forums where you want `Midjourney` AI user to reply to new posts and/or to quoted and [@mention](https://www.phpbb.com/customise/db/extension/simple_mentions) (if you are using Simple mentions extension) posts.
|
||||
|
||||
* Save changes, navigate to forum configured above and create new post (if you configured `Reply on a post`) or quote/[@mention]() `Midjourney` user:
|
||||
![Attachment settings](../privet/ailabs/docs/midjourney_example.png)
|
||||
|
||||
* Images generated by Midjourney Discord bot via useapi.net stored and served from Discord CDN.
|
||||
|
||||
## ChatGPT setup
|
||||
|
||||
* You will need OpenAI account, sign up at https://platform.openai.com/.
|
||||
To obtain API key go to https://platform.openai.com/account/api-keys, click on `Create new secret key`, copy and save in a safe place generated API key.
|
||||
|
@ -69,13 +115,14 @@ Finally go to `ACP` > `Extensions` > `AI Labs` > `Settings` and add desired AI c
|
|||
- `max_tokens`, default 1024, define size reserved for AI reply when quoted
|
||||
- `prefix`, default empty, can be used to prompt model
|
||||
- `prefix_tokens`, default 0, copy above `prefix` to https://platform.openai.com/tokenizer to get size of your `prefix` in tokens and update `prefix_tokens` with number returned by tokenizer
|
||||
- `max_quote_length`, if provided, the quoted response text will be truncated to the number of words defined by the max_quote_length value. Set it to 0 to remove all quoted text entirely.
|
||||
|
||||
## ChatGPT advanced setup
|
||||
|
||||
You can setup ChatGPT to pretend it is somebody else.
|
||||
Let's create new board user `Bender` and configure as shown below:
|
||||
![Attachment settings](../privet/ailabs/docs/chatgpt_bender_example.png)
|
||||
Notice we used `prefix` and `prefix_tokens` to fine-tune ChatGPT AI behaviour.
|
||||
Notice we used `prefix` and `prefix_tokens` to fine-tune ChatGPT AI behavior.
|
||||
Our AI bot `Bender` will provide responses like [this](https://privet.fun/viewtopic.php?t=2799), mostly staying in a character.
|
||||
|
||||
## DALL-E setup
|
||||
|
@ -105,11 +152,36 @@ Refer to https://platform.openai.com/docs/api-reference/images/create to learn m
|
|||
|
||||
* Refer to https://api.stability.ai/docs#tag/v1generation/operation/textToImage to learn more about configuration JSON parameters.
|
||||
|
||||
## Troubleshooting
|
||||
AI Labs extension maintains internal logs, you should have admin or moderator rights to see log icon:
|
||||
![Attachment settings](../privet/ailabs/docs/debugging_post_icon.png)
|
||||
|
||||
You can see entire AI communication history in the log:
|
||||
![Attachment settings](../privet/ailabs/docs/debugging_log.png)
|
||||
If Log entry is empty it usually means that `/ailabs/*` or `/app.php/ailabs/*` routes blocked by one of phpBB extensions (eg <a href="https://www.phpbb.com/customise/db/extension/login_required">Login Required</a>) and you will need to add `/ailabs/*` or `/app.php/ailabs/*` to extension whitelist.
|
||||
You can examine Log `response` (JSON) to see details for AI response.
|
||||
Please feel free to post your questions or concerns at https://github.com/privet-fun/phpbb_ailabs/issues.
|
||||
## Support and suggestions
|
||||
|
||||
This extension is currently being actively developed. For communication, please use https://github.com/privet-fun/phpbb_ailabs/issues.
|
||||
|
||||
## <a name="changelog_link"></a>Changelog
|
||||
## Changelog
|
||||
|
||||
|
||||
* 1.0.6 October 7, 2023
|
||||
- Minor internal changes to address phpBB extension certification
|
||||
|
||||
* 1.0.5 October 1, 2023
|
||||
- Midjourney support added
|
||||
- `max_quote_length` option added for ChatGPT
|
||||
|
||||
* 1.0.4 June 4, 2023
|
||||
- Troubleshooting section added
|
||||
- Added configuration for reply in topics
|
||||
- Fixed links generation for cases where cookies disabled
|
||||
- AI Labs internal controllers (`/ailabs/*`) will attempt to establish session to deal with phpBB extensions like <a href="https://www.phpbb.com/customise/db/extension/login_required">Login Required</a>
|
||||
- Better descriptions added to help with setup
|
||||
- Minor bugfixes
|
||||
|
||||
* 1.0.3 June 1, 2023
|
||||
- bumped php requirements to >= 7.4
|
||||
|
|
|
@ -6,15 +6,23 @@
|
|||
|
||||
<a id="maincontent"></a>
|
||||
|
||||
<h1>{{ lang('ACP_AILABS_TITLE') }}</h1>
|
||||
<div style="display: flex; align-items: baseline;">
|
||||
<h1 style="white-space: nowrap;">{{ lang('ACP_AILABS_TITLE') }}</h1>
|
||||
<div style="width: 100%;"></div><span style="white-space: nowrap;">v. {{ U_AILABS_VERSION }}</span>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
{{ lang('LBL_AILABS_SETTINGS_DESC') }}
|
||||
<span>{{ lang('LBL_AILABS_SETTINGS_DESC') }}</span>
|
||||
|
||||
{% if U_AILABS_VEIW %}
|
||||
<a href="{{ U_ADD }}" class="button2" style="float: {{ S_CONTENT_FLOW_END }};">{{ lang('ACP_AILABS_ADD') }}</a>
|
||||
<br>
|
||||
<br>
|
||||
|
||||
{% if U_IP_CHECK %}
|
||||
<span>{{ U_IP_CHECK }}</span>
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% if U_AILABS_ADD_EDIT %}
|
||||
|
@ -35,9 +43,20 @@
|
|||
|
||||
<script type="text/javascript">
|
||||
const defaultConfigs = {
|
||||
"midjourney": {
|
||||
"url_imagine": "https://api.useapi.net/v1/jobs/imagine",
|
||||
"url_button": "https://api.useapi.net/v1/jobs/button",
|
||||
"api_key": "your-useapi.net-key-goes-here",
|
||||
"discord": "your-discord-token",
|
||||
"server": "your-discord-server-id",
|
||||
"channel": "your-discord-channel-id",
|
||||
"maxJobs": 3,
|
||||
"retryCount": 80,
|
||||
"timeoutBeforeRetrySec": 15
|
||||
},
|
||||
"chatgpt": {
|
||||
"api_key": "your-openai-api-key-goes-here",
|
||||
"url_chat": "https://api.openai.com/v1/chat/completions",
|
||||
"api_key": "your-openai-api-key-goes-here",
|
||||
"model": "gpt-3.5-turbo",
|
||||
"temperature": 0.9,
|
||||
"max_tokens": 4096,
|
||||
|
@ -46,19 +65,20 @@
|
|||
"frequency_penalty": 0,
|
||||
"presence_penalty": 0.6,
|
||||
"prefix": "",
|
||||
"prefix_tokens": 0
|
||||
"prefix_tokens": 0,
|
||||
"max_quote_length": 10
|
||||
},
|
||||
"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",
|
||||
"api_key": "your-openai-api-key-goes-here",
|
||||
"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",
|
||||
"api_key": "your-stablityai-api-key-goes-here",
|
||||
"cfg_scale": 7.5,
|
||||
"clip_guidance_preset": "FAST_BLUE",
|
||||
"height": 512,
|
||||
|
@ -75,6 +95,7 @@
|
|||
}
|
||||
|
||||
const defaultTemplate = {
|
||||
"midjourney": "[quote=\{poster_name\} post_id=\{post_id\} user_id=\{poster_id\}]\{request\}[/quote]\n\{response\}\n\{images\}\n\{info\}",
|
||||
"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\}",
|
||||
|
@ -120,13 +141,15 @@
|
|||
function doReset() {
|
||||
setTimeout(function () {
|
||||
resetValue('ailabs_forums_post', {{ ailabs_forums_post }});
|
||||
resetValue('ailabs_forums_mention', {{ ailabs_forums_mention }});
|
||||
resetValue('ailabs_forums_reply', {{ ailabs_forums_reply }});
|
||||
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_reply', {{ ailabs_forums_reply }});
|
||||
setupSelect('ailabs_forums_mention', {{ ailabs_forums_mention }});
|
||||
});
|
||||
|
||||
|
@ -151,7 +174,7 @@
|
|||
</dd>
|
||||
</dl>
|
||||
<dl>
|
||||
<dt><label for="ailabs_username">{L_ENTER_USERNAME}{L_COLON}</label></dt>
|
||||
<dt><label for="ailabs_username">{{ lang('LBL_AILABS_USERNAME') ~ lang('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> ]
|
||||
|
@ -195,10 +218,10 @@
|
|||
</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%;">
|
||||
<legend>{{ lang('LBL_AILABS_POST_FORUMS') }}</legend>
|
||||
<span>{{ lang('LBL_AILABS_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 key,value in AILABS_FORUMS_LIST %}
|
||||
<option value="{{ key }}">{{ value }}</option>
|
||||
{% endfor %}
|
||||
|
@ -206,10 +229,21 @@
|
|||
</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%;">
|
||||
<legend>{{ lang('LBL_AILABS_REPLY_FORUMS') }}</legend>
|
||||
<span>{{ lang('LBL_AILABS_REPLY_FORUMS_EXPLAIN') }}</span>
|
||||
<select id="ailabs_forums_reply_select" class="chosen-select" multiple
|
||||
data-placeholder="{{ lang('LBL_AILABS_SELECT_FORUMS') }}" style="width: 100%;">
|
||||
{% for key,value in AILABS_FORUMS_LIST %}
|
||||
<option value="{{ key }}">{{ value }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>{{ lang('LBL_AILABS_QUOTE_FORUMS') }}</legend>
|
||||
<span>{{ lang('LBL_AILABS_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 key,value in AILABS_FORUMS_LIST %}
|
||||
<option value="{{ key }}">{{ value }}</option>
|
||||
{% endfor %}
|
||||
|
@ -220,6 +254,7 @@
|
|||
<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_reply" name="ailabs_forums_reply">
|
||||
<input type="hidden" id="ailabs_forums_mention" name="ailabs_forums_mention">
|
||||
|
||||
<input type="reset" class="button2" value="{{ lang('RESET') }}" onclick="doReset()">
|
||||
|
@ -235,11 +270,17 @@
|
|||
|
||||
<table class="tableUsers zebra-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="2" style="background: transparent; border: none;"></th>
|
||||
<th colspan="3">{{ lang('LBL_AILABS_REPLY_TO') }}</th>
|
||||
<th colspan="3" style="background: transparent; border: none;"></th>
|
||||
</tr>
|
||||
<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>{{ lang('LBL_AILABS_POST_FORUMS') }}</th>
|
||||
<th>{{ lang('LBL_AILABS_REPLY_FORUMS') }}</th>
|
||||
<th>{{ lang('LBL_AILABS_QUOTE_FORUMS') }}</th>
|
||||
<th class="centered-text">{{ lang('LBL_AILABS_ENABLED') }}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
|
@ -247,9 +288,10 @@
|
|||
<tbody>
|
||||
{% for user in U_AILABS_USERS %}
|
||||
<tr>
|
||||
<td><a href="/memberlist.php?mode=viewprofile&u={{ user.user_id }}">{{ user.username }}</a></td>
|
||||
<td><a href="{{ user.username_url }}">{{ user.username }}</a></td>
|
||||
<td>{{ user.controller }}</td>
|
||||
<td>{{ user.forums_post_names }}</td>
|
||||
<td>{{ user.forums_reply_names }}</td>
|
||||
<td>{{ user.forums_mention_names }}</td>
|
||||
<td class="centered-text"><input type="checkbox" onclick="return false" {{ user.enabled ? 'checked' : '' }}>
|
||||
</td>
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
"type": "phpbb-extension",
|
||||
"description": "AI Labs",
|
||||
"homepage": "https://privet.fun",
|
||||
"version": "1.0.3",
|
||||
"time": "2023-06-01",
|
||||
"version": "1.0.6",
|
||||
"time": "2023-10-07",
|
||||
"keywords": [
|
||||
"phpbb",
|
||||
"extension",
|
||||
|
|
|
@ -1,16 +1,31 @@
|
|||
privet_chatgpt_page:
|
||||
privet_ailabs_chatgpt_page:
|
||||
path: /ailabs/chatgpt
|
||||
defaults: { _controller: privet.ailabs.controller_chatgpt:execute }
|
||||
|
||||
privet_dalle_page:
|
||||
privet_ailabs_dalle_page:
|
||||
path: /ailabs/dalle
|
||||
defaults: { _controller: privet.ailabs.controller_dalle:execute }
|
||||
|
||||
privet_stablediffusion_page:
|
||||
privet_ailabs_stablediffusion_page:
|
||||
path: /ailabs/stablediffusion
|
||||
defaults: { _controller: privet.ailabs.controller_stablediffusion:execute }
|
||||
|
||||
privet_scriptexecute_page:
|
||||
privet_ailabs_midjourney_page:
|
||||
path: /ailabs/midjourney
|
||||
defaults: { _controller: privet.ailabs.controller_midjourney:execute }
|
||||
|
||||
privet_ailabs_midjourney_callback:
|
||||
path: /ailabs/midjourney/callback/{job_id}/{ref}/{action}
|
||||
methods: [POST]
|
||||
defaults:
|
||||
_controller: privet.ailabs.controller_midjourney:callback
|
||||
mode: 'post'
|
||||
requirements:
|
||||
job_id: \d+
|
||||
ref: "[a-zA-Z0-9]+"
|
||||
action: posted|reply
|
||||
|
||||
privet_ailabs_scriptexecute_page:
|
||||
path: /ailabs/scriptexecute
|
||||
defaults: { _controller: privet.ailabs.controller_scriptexecute:execute }
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ services:
|
|||
- '@template'
|
||||
- '@user'
|
||||
- '%core.root_path%'
|
||||
- '%core.php_ext%'
|
||||
- '%privet.ailabs.tables.users%'
|
||||
|
||||
privet.ailabs.listener:
|
||||
|
@ -84,6 +85,23 @@ services:
|
|||
- '%privet.ailabs.tables.users%'
|
||||
- '%privet.ailabs.tables.jobs%'
|
||||
|
||||
privet.ailabs.controller_midjourney:
|
||||
class: privet\ailabs\controller\midjourney
|
||||
arguments:
|
||||
- '@auth'
|
||||
- '@config'
|
||||
- '@dbal.conn'
|
||||
- '@controller.helper'
|
||||
- '@language'
|
||||
- '@request'
|
||||
- '@template'
|
||||
- '@user'
|
||||
- '@service_container'
|
||||
- '%core.php_ext%'
|
||||
- '%core.root_path%'
|
||||
- '%privet.ailabs.tables.users%'
|
||||
- '%privet.ailabs.tables.jobs%'
|
||||
|
||||
privet.ailabs.controller_scriptexecute:
|
||||
class: privet\ailabs\controller\scriptexecute
|
||||
arguments:
|
||||
|
|
|
@ -23,6 +23,7 @@ class acp_controller //implements acp_interface
|
|||
protected $template;
|
||||
protected $user;
|
||||
protected $root_path;
|
||||
protected $php_ext;
|
||||
protected $ailabs_users_table;
|
||||
|
||||
protected $id;
|
||||
|
@ -37,6 +38,7 @@ class acp_controller //implements acp_interface
|
|||
'/ailabs/chatgpt',
|
||||
'/ailabs/dalle',
|
||||
'/ailabs/stablediffusion',
|
||||
'/ailabs/midjourney',
|
||||
'/ailabs/scriptexecute'
|
||||
];
|
||||
|
||||
|
@ -51,6 +53,7 @@ class acp_controller //implements acp_interface
|
|||
\phpbb\template\template $template,
|
||||
\phpbb\user $user,
|
||||
$root_path,
|
||||
$php_ext,
|
||||
$ailabs_users_table
|
||||
) {
|
||||
$this->config = $config;
|
||||
|
@ -63,6 +66,7 @@ class acp_controller //implements acp_interface
|
|||
$this->template = $template;
|
||||
$this->user = $user;
|
||||
$this->root_path = $root_path;
|
||||
$this->php_ext = $php_ext;
|
||||
$this->ailabs_users_table = $ailabs_users_table;
|
||||
}
|
||||
|
||||
|
@ -93,6 +97,7 @@ class acp_controller //implements acp_interface
|
|||
'config' => $this->request->variable('ailabs_config', '', true),
|
||||
'template' => $this->request->variable('ailabs_template', '', true),
|
||||
'forums_post' => $this->request->variable('ailabs_forums_post', ''),
|
||||
'forums_reply' => $this->request->variable('ailabs_forums_reply', ''),
|
||||
'forums_mention' => $this->request->variable('ailabs_forums_mention', ''),
|
||||
'enabled' => $this->request->variable('ailabs_enabled', true),
|
||||
];
|
||||
|
@ -111,8 +116,8 @@ class acp_controller //implements acp_interface
|
|||
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 (empty($data['forums_post']) && empty($data['forums_reply']) && empty($data['forums_mention'])) {
|
||||
trigger_error($this->language->lang('AILABS_SPECIFY_FORUM') . adm_back_link($this->u_action), E_USER_WARNING);
|
||||
}
|
||||
|
||||
if (!isset($error)) {
|
||||
|
@ -122,6 +127,7 @@ class acp_controller //implements acp_interface
|
|||
'config' => (string) html_entity_decode($data['config']),
|
||||
'template' => (string) html_entity_decode($data['template']),
|
||||
'forums_post' => (string) html_entity_decode($data['forums_post']),
|
||||
'forums_reply' => (string) html_entity_decode($data['forums_reply']),
|
||||
'forums_mention' => (string) html_entity_decode($data['forums_mention']),
|
||||
'enabled' => (bool) $data['enabled']
|
||||
];
|
||||
|
@ -159,6 +165,7 @@ class acp_controller //implements acp_interface
|
|||
'ailabs_config' => (string) $row['config'],
|
||||
'ailabs_template' => (string) $row['template'],
|
||||
'ailabs_forums_post' => (string) $row['forums_post'],
|
||||
'ailabs_forums_reply' => (string) $row['forums_reply'],
|
||||
'ailabs_forums_mention' => (string) $row['forums_mention'],
|
||||
'ailabs_enabled' => (bool) $row['enabled']
|
||||
];
|
||||
|
@ -177,8 +184,6 @@ class acp_controller //implements acp_interface
|
|||
]);
|
||||
}
|
||||
|
||||
global $phpbb_root_path, $phpEx;
|
||||
|
||||
$this->template->assign_vars(
|
||||
array_merge(
|
||||
$edit,
|
||||
|
@ -187,8 +192,9 @@ class acp_controller //implements acp_interface
|
|||
'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'),
|
||||
'U_FIND_USERNAME' => generate_board_url() . '/' . append_sid("memberlist.$this->php_ext", 'mode=searchuser&form=ailabs_configuration&field=ailabs_username&select_single=true', true, $this->user->session_id),
|
||||
'AILABS_FORUMS_LIST' => $this->build_forums_list(),
|
||||
'U_AILABS_VERSION' => $this->config['privet_ailabs_version'],
|
||||
]
|
||||
)
|
||||
);
|
||||
|
@ -229,7 +235,9 @@ class acp_controller //implements acp_interface
|
|||
|
||||
$controller = explode("/", $row['controller']);
|
||||
$row['controller'] = end($controller);
|
||||
$row['username_url'] = generate_board_url() . '/' . append_sid("memberlist.$this->php_ext", 'mode=viewprofile&u=' . $row['user_id'], true, $this->user->session_id);
|
||||
$row['forums_post_names'] = $this->get_forums_names($row['forums_post'], $forums);
|
||||
$row['forums_reply_names'] = $this->get_forums_names($row['forums_reply'], $forums);
|
||||
$row['forums_mention_names'] = $this->get_forums_names($row['forums_mention'], $forums);
|
||||
$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');
|
||||
|
@ -243,12 +251,23 @@ class acp_controller //implements acp_interface
|
|||
'U_AILABS_USERS' => $ailabs_users,
|
||||
'U_ADD' => $this->u_action . '&action=add',
|
||||
'U_ACTION' => $this->u_action,
|
||||
'U_AILABS_VEIW' => true
|
||||
'U_AILABS_VEIW' => true,
|
||||
'U_AILABS_VERSION' => $this->config['privet_ailabs_version'],
|
||||
'U_IP_CHECK' => $this->warn_ip_check()
|
||||
];
|
||||
|
||||
return $this->template->assign_vars($template_vars);
|
||||
}
|
||||
|
||||
protected function warn_ip_check()
|
||||
{
|
||||
if ($this->config['ip_check'] != 0) {
|
||||
$url = generate_board_url() . '/' . append_sid("adm/index.$this->php_ext", ['i' => 'acp_board', 'mode' => 'security'], true, $this->user->session_id);
|
||||
return $this->language->lang('LBL_AILABS_IP_VALIDATION', $url);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function find_user_id($username)
|
||||
{
|
||||
$user_id = null;
|
||||
|
@ -306,19 +325,18 @@ class acp_controller //implements acp_interface
|
|||
return $return;
|
||||
}
|
||||
|
||||
protected function get_forums_names($str, $forums) {
|
||||
protected function get_forums_names($str, $forums)
|
||||
{
|
||||
$result = [];
|
||||
if(!empty($str)) {
|
||||
if (!empty($str)) {
|
||||
$arr = json_decode($str);
|
||||
if(!empty($arr) && is_array($arr)) {
|
||||
foreach($arr as $id)
|
||||
{
|
||||
$name = empty($forums[$id]) ? $id : $forums[$id];
|
||||
array_push($result, $name);
|
||||
if (!empty($arr) && is_array($arr)) {
|
||||
foreach ($arr as $id) {
|
||||
$name = empty($forums[$id]) ? $id : $forums[$id];
|
||||
array_push($result, $name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return join(', ', $result);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,7 +18,8 @@ use privet\ailabs\includes\resultParse;
|
|||
|
||||
/*
|
||||
|
||||
config
|
||||
config (example)
|
||||
|
||||
{
|
||||
"api_key": "<api-key>",
|
||||
"url_chat": "https://api.openai.com/v1/chat/completions",
|
||||
|
@ -31,10 +32,13 @@ config
|
|||
"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
|
||||
"max_quote_length": 10
|
||||
}
|
||||
|
||||
template
|
||||
|
||||
{info}[quote={poster_name} post_id={post_id} user_id={poster_id}]{request}[/quote]{response}
|
||||
|
||||
*/
|
||||
|
||||
class chatgpt extends AIController
|
||||
|
@ -92,72 +96,94 @@ class chatgpt extends AIController
|
|||
$post_first_discarded = null;
|
||||
$mode = $this->job['post_mode'];
|
||||
|
||||
if ($mode == 'reply' || $mode == 'quote') {
|
||||
$history = ['post_text' => $this->job['post_text']];
|
||||
$history = ['post_text' => $this->job['post_text']];
|
||||
|
||||
$pattern = '/<QUOTE\sauthor="' . $this->job['ailabs_username'] . '"\spost_id="(.*)"\stime="(.*)"\suser_id="' . $this->job['ailabs_user_id'] . '">/';
|
||||
$pattern = '/<QUOTE\sauthor="' . $this->job['ailabs_username'] . '"\spost_id="(.*)"\stime="(.*)"\suser_id="' . $this->job['ailabs_user_id'] . '">/';
|
||||
|
||||
$this->log['history.pattern'] = $pattern;
|
||||
$this->log_flush();
|
||||
$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
|
||||
);
|
||||
// Attempt to unwind history using quoted posts
|
||||
$history_tokens = 0;
|
||||
$round = -1;
|
||||
do {
|
||||
$round++;
|
||||
$matches = null;
|
||||
preg_match_all(
|
||||
$pattern,
|
||||
$history['post_text'],
|
||||
$matches
|
||||
);
|
||||
|
||||
$history = null;
|
||||
$history = null;
|
||||
|
||||
if ($matches != null && !empty($matches) && !empty($matches[1][0])) {
|
||||
$postid = (int) $matches[1][0];
|
||||
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);
|
||||
$sql = 'SELECT j.job_id, j.post_id, j.response_post_id, j.request, j.response, p.post_text, p.post_time, j.request_tokens, j.response_tokens ' .
|
||||
'FROM ' . $this->jobs_table . ' j ' .
|
||||
'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'];
|
||||
if (!empty($history)) {
|
||||
$count_tokens = $history['request_tokens'] + $history['response_tokens'];
|
||||
|
||||
$discard = $this->max_tokens < ($this->message_tokens + $history_tokens + $count_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
|
||||
];
|
||||
$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;
|
||||
}
|
||||
if ($discard) {
|
||||
$post_first_discarded = $postid;
|
||||
break;
|
||||
}
|
||||
|
||||
$post_first_taken = $postid;
|
||||
$history_tokens += $count_tokens;
|
||||
$post_first_taken = $postid;
|
||||
$history_tokens += $count_tokens;
|
||||
|
||||
array_unshift(
|
||||
$messages,
|
||||
['role' => 'user', 'content' => trim($history['request'])],
|
||||
['role' => 'assistant', 'content' => trim($history['response'])]
|
||||
$history_decoded_request = utf8_decode_ncr($history['request']);
|
||||
$history_decoded_response = utf8_decode_ncr($history['response']);
|
||||
|
||||
array_unshift(
|
||||
$messages,
|
||||
['role' => 'user', 'content' => trim($history_decoded_request)],
|
||||
['role' => 'assistant', 'content' => trim($history_decoded_response)]
|
||||
);
|
||||
|
||||
if ($round == 0) {
|
||||
// Remove quoted content from the quoted post
|
||||
$post_text = sprintf(
|
||||
'<r><QUOTE author="%1$s" post_id="%2$s" time="%3$s" user_id="%4$s"><s>[quote=%1$s post_id=%2$s time=%3$s user_id=%4$s]</s>%6$s<e>[/quote]</e></QUOTE>%5$s</r>',
|
||||
$this->job['ailabs_username'],
|
||||
(string) $postid,
|
||||
(string) $this->job['post_time'],
|
||||
(string) $this->job['ailabs_user_id'],
|
||||
$this->job['request'],
|
||||
property_exists($this->cfg, 'max_quote_length') ?
|
||||
$this->trim_words($history_decoded_response, (int) $this->cfg->max_quote_length) : $history_decoded_response
|
||||
);
|
||||
|
||||
$sql = 'UPDATE ' . POSTS_TABLE .
|
||||
' SET ' . $this->db->sql_build_array('UPDATE', ['post_text' => utf8_encode_ucr($post_text)]) .
|
||||
' WHERE post_id = ' . (int) $this->job['post_id'];
|
||||
$result = $this->db->sql_query($sql);
|
||||
$this->db->sql_freeresult($result);
|
||||
}
|
||||
}
|
||||
} while (!empty($history));
|
||||
|
||||
if (!empty($posts)) {
|
||||
$this->log['history.posts'] = $posts;
|
||||
$this->log_flush();
|
||||
}
|
||||
} while (!empty($history));
|
||||
|
||||
if (!empty($posts)) {
|
||||
$this->log['history.posts'] = $posts;
|
||||
$this->log_flush();
|
||||
}
|
||||
|
||||
if (!empty($this->cfg->prefix)) {
|
||||
|
@ -237,14 +263,16 @@ class chatgpt extends AIController
|
|||
$this->log['finish'] = date('Y-m-d H:i:s');
|
||||
|
||||
if (!empty($posts)) {
|
||||
$viewtopic = "{$this->root_path}viewtopic.{$this->php_ext}";
|
||||
$discarded = '';
|
||||
if ($post_first_discarded != null) {
|
||||
$discarded = $this->language->lang('AILABS_POSTS_DISCARDED', $post_first_discarded);
|
||||
$discarded = $this->language->lang('AILABS_POSTS_DISCARDED', $viewtopic, $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',
|
||||
$viewtopic,
|
||||
$post_first_taken,
|
||||
$total_posts_count,
|
||||
$discarded,
|
||||
|
@ -268,7 +296,7 @@ class chatgpt extends AIController
|
|||
'status' => $this->job['status'],
|
||||
'attempts' => $this->job['attempts'] + 1,
|
||||
'response_time' => $this->job['response_time'],
|
||||
'response' => $api_response,
|
||||
'response' => utf8_encode_ucr($api_response),
|
||||
'request_tokens' => $request_tokens - $history_tokens - $prefix_tokens,
|
||||
'response_post_id' => $this->job['response_post_id'],
|
||||
'response_tokens' => $response_tokens,
|
||||
|
|
|
@ -66,22 +66,10 @@ class dalle extends GenericController
|
|||
|
||||
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,
|
||||
];
|
||||
}
|
||||
|
||||
$opts += [
|
||||
'prompt' => trim($this->job['request']),
|
||||
];
|
||||
|
||||
return $opts;
|
||||
}
|
||||
|
@ -93,13 +81,7 @@ class dalle extends GenericController
|
|||
|
||||
$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->response = $api->sendRequest($this->cfg->url_generations, 'POST', $opts);
|
||||
|
||||
$result->responseCodes = $api->responseCodes;
|
||||
|
||||
|
@ -159,7 +141,7 @@ class dalle extends GenericController
|
|||
array_push($images, $item->url);
|
||||
} else {
|
||||
$filename = $this->save_base64_to_temp_file($item->b64_json, $ind);
|
||||
$item->b64_json = '<reducted>';
|
||||
$item->b64_json = '<redacted>';
|
||||
array_push($images, $filename);
|
||||
}
|
||||
$ind++;
|
||||
|
|
|
@ -33,10 +33,10 @@ class log extends AIController
|
|||
|
||||
if (!empty($data)) {
|
||||
foreach ($data as &$row) {
|
||||
$row['poster_user_url'] = generate_board_url() . '/' . append_sid("memberlist.$this->php_ext", 'mode=viewprofile&u=' . $row['poster_id'], true, '');
|
||||
$row['ailabs_user_url'] = generate_board_url() . '/' . append_sid("memberlist.$this->php_ext", 'mode=viewprofile&u=' . $row['ailabs_user_id'], true, '');
|
||||
$row['poster_user_url'] = generate_board_url() . '/' . append_sid("memberlist.$this->php_ext", 'mode=viewprofile&u=' . $row['poster_id'], true, $this->user->session_id);
|
||||
$row['ailabs_user_url'] = generate_board_url() . '/' . append_sid("memberlist.$this->php_ext", 'mode=viewprofile&u=' . $row['ailabs_user_id'], true, $this->user->session_id);
|
||||
if (!empty($row['response_post_id'])) {
|
||||
$row['response_url'] = generate_board_url() . '/' . append_sid('viewtopic.php?p=' . $row['response_post_id'] . '#p' . $row['response_post_id'], true, '');
|
||||
$row['response_url'] = generate_board_url() . '/' . append_sid("viewtopic.$this->php_ext", 'p=' . $row['response_post_id'] . '#p' . $row['response_post_id'], true, $this->user->session_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
359
privet/ailabs/controller/midjourney.php
Normal file
|
@ -0,0 +1,359 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* AI Labs extension
|
||||
*
|
||||
* @copyright (c) 2023, privet.fun, https://privet.fun
|
||||
* @license GNU General Public License, version 2 (GPL-2.0)
|
||||
*
|
||||
*/
|
||||
|
||||
namespace privet\ailabs\controller;
|
||||
|
||||
use privet\ailabs\includes\GenericCurl;
|
||||
use privet\ailabs\includes\GenericController;
|
||||
use privet\ailabs\includes\resultSubmit;
|
||||
use privet\ailabs\includes\resultParse;
|
||||
|
||||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
|
||||
/*
|
||||
|
||||
// How to get api token and configure Discord
|
||||
// https://useapi.net/docs/start-here
|
||||
|
||||
config:
|
||||
|
||||
{
|
||||
"api_key": "<useapi.net api token>",
|
||||
"url_imagine": "https://api.useapi.net/v1/jobs/imagine",
|
||||
"url_button": "https://api.useapi.net/v1/jobs/button",
|
||||
"discord": "<Discord token, required>",
|
||||
"server": "<Discord server id, required>",
|
||||
"channel": "<Discord channel id, required>",
|
||||
"maxJobs": "<Midjourney subscription plan Maximum Concurrent Jobs, optional, default 3>",
|
||||
"retryCount": "<Maximum attempts to submit request, optional, default 80>",
|
||||
"timeoutBeforeRetrySec": "<Time to wait before next retry, optional, default 15>",
|
||||
}
|
||||
|
||||
template:
|
||||
|
||||
[quote={poster_name} post_id={post_id} user_id={poster_id}]{request}[/quote]
|
||||
{response}
|
||||
{images}
|
||||
{info}
|
||||
|
||||
*/
|
||||
|
||||
class midjourney extends GenericController
|
||||
{
|
||||
/**
|
||||
* @return \Symfony\Component\HttpFoundation\Response A Symfony Response object
|
||||
*/
|
||||
public function callback($job_id, $ref, $action)
|
||||
{
|
||||
$this->job_id = $job_id;
|
||||
|
||||
$this->load_job();
|
||||
|
||||
if (empty($this->job))
|
||||
return new JsonResponse('job_id ' . $job_id . ' not found in the database');
|
||||
|
||||
if ($this->job['ref'] !== $ref)
|
||||
return new JsonResponse('wrong reference ' . $ref);
|
||||
|
||||
if (in_array($this->job['status'], ['ok', 'failed']))
|
||||
return new JsonResponse('job_id ' . $job_id . ' already has final status ' . $this->job['status']);
|
||||
|
||||
$this->log = json_decode($this->job['log'], true);
|
||||
|
||||
// POST body as json
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
$json = null;
|
||||
|
||||
switch ($action) {
|
||||
case 'posted':
|
||||
$response_codes = null;
|
||||
|
||||
// Store entire posted response into log
|
||||
foreach ($data as $key => $value) {
|
||||
$this->log[$key] = $value;
|
||||
if ($key === 'response.json')
|
||||
$json = $value;
|
||||
if ($key === 'response.codes') {
|
||||
$response_codes = $value;
|
||||
// We may get no response body at all in some cases
|
||||
if (!in_array(200, $response_codes))
|
||||
$this->job['status'] = 'failed';
|
||||
}
|
||||
}
|
||||
|
||||
$response_message_id = $this->process_response_message_id($json);
|
||||
|
||||
// https://useapi.net/docs/api-v1/jobs-button
|
||||
// HTTP 409 Conflict
|
||||
// Button <U1 | U2 | U3 | U4> already executed by job <jobid>
|
||||
if (!empty($response_message_id) && !empty($response_codes) && in_array(409, $response_codes)) {
|
||||
$sql = 'SELECT j.response_post_id FROM ' . $this->jobs_table . ' j WHERE ' .
|
||||
$this->db->sql_build_array('SELECT', ['response_message_id' => $response_message_id]);
|
||||
$result = $this->db->sql_query($sql);
|
||||
$row = $this->db->sql_fetchrow($result);
|
||||
$this->db->sql_freeresult($result);
|
||||
|
||||
if (!empty($row)) {
|
||||
$viewtopic = "{$this->root_path}viewtopic.{$this->php_ext}";
|
||||
$json['response'] = $this->language->lang('AILABS_MJ_BUTTON_ALREADY_USED', $json['button'], $viewtopic, $row['response_post_id']);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case 'reply':
|
||||
// Raw response from useapi.net /imagine or /button API endpoints
|
||||
$json = $data;
|
||||
|
||||
// Upscale buttons U1..U4 may create race condition, let's rely on .../posted to process response
|
||||
if (!empty($json) && !empty($json['code']) && $json['code'] === 409)
|
||||
return new JsonResponse('Skipping 409');
|
||||
|
||||
$this->process_response_message_id($json);
|
||||
|
||||
$this->log['response.json'] = $json;
|
||||
$this->log['response.time'] = date('Y-m-d H:i:s');
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Assume the worst
|
||||
$this->job['status'] = 'failed';
|
||||
$this->job['response'] = $this->language->lang('AILABS_ERROR_CHECK_LOGS');
|
||||
|
||||
if (!empty($json)) {
|
||||
if (!empty($json['status']))
|
||||
switch ($json['status']) {
|
||||
case 'created':
|
||||
case 'started':
|
||||
case 'progress':
|
||||
$this->job['status'] = 'exec';
|
||||
break;
|
||||
case 'completed':
|
||||
$this->job['status'] = 'ok';
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (!empty($json['code']))
|
||||
switch ($json['code']) {
|
||||
case 200: // HTTP OK
|
||||
$this->job['response'] = preg_replace('/<@(\d+)>/', '', $json['content']);
|
||||
break;
|
||||
case 422: // HTTP 422 Unprocessable Content - Moderated
|
||||
$this->job['response'] = $this->language->lang('AILABS_MJ_MODERATED');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($json) && in_array($this->job['status'], ['ok', 'failed'])) {
|
||||
$resultParse = new resultParse();
|
||||
$resultParse->message = $this->job['response'];
|
||||
|
||||
// Only attach successfully generated images, seems like all other images will be deleted from Discord CDN
|
||||
if (($this->job['status'] == 'ok') && !empty($json['attachments'])) {
|
||||
$url_adjusted = (string) $json['attachments'][0]['url'];
|
||||
$url_adjusted = preg_replace('/\?.*$/', '', $url_adjusted);
|
||||
$resultParse->images = array($url_adjusted);
|
||||
}
|
||||
|
||||
if (!empty($json['buttons']))
|
||||
$resultParse->info = $this->language->lang('AILABS_MJ_BUTTONS') . implode(" • ", $json['buttons']);
|
||||
|
||||
$response = $this->replace_vars($this->job, $resultParse);
|
||||
|
||||
$data = $this->post_response($this->job, $response);
|
||||
|
||||
$this->job['response_post_id'] = $data['post_id'];
|
||||
}
|
||||
|
||||
$set = [
|
||||
'status' => $this->job['status'],
|
||||
'response' => utf8_encode_ucr($this->job['response']),
|
||||
'response_time' => time(),
|
||||
'response_post_id' => $this->job['response_post_id'],
|
||||
'log' => json_encode($this->log)
|
||||
];
|
||||
|
||||
$this->job_update($set);
|
||||
$this->post_update($this->job);
|
||||
|
||||
return new JsonResponse($this->log);
|
||||
}
|
||||
|
||||
protected function prepare($opts)
|
||||
{
|
||||
$pattern = '/<QUOTE\sauthor="' . $this->job['ailabs_username'] . '"\spost_id="(.*)"\stime="(.*)"\suser_id="' . $this->job['ailabs_user_id'] . '">/';
|
||||
|
||||
$parent_job = null;
|
||||
$matches = null;
|
||||
|
||||
preg_match_all(
|
||||
$pattern,
|
||||
$this->job['post_text'],
|
||||
$matches
|
||||
);
|
||||
|
||||
if (!empty($matches) && !empty($matches[1][0])) {
|
||||
$response_post_id = (int) $matches[1][0];
|
||||
|
||||
$sql = 'SELECT j.job_id, j.response_post_id, j.log, j.response ' .
|
||||
'FROM ' . $this->jobs_table . ' j ' .
|
||||
'WHERE ' . $this->db->sql_build_array('SELECT', ['response_post_id' => $response_post_id]);
|
||||
$result = $this->db->sql_query($sql);
|
||||
$parent_job = $this->db->sql_fetchrow($result);
|
||||
$this->db->sql_freeresult($result);
|
||||
|
||||
// Remove quoted content from the quoted post
|
||||
$post_text = sprintf(
|
||||
'<r><QUOTE author="%1$s" post_id="%2$s" time="%3$s" user_id="%4$s"><s>[quote=%1$s post_id=%2$s time=%3$s user_id=%4$s]</s>%6$s<e>[/quote]</e></QUOTE>%5$s</r>',
|
||||
$this->job['ailabs_username'],
|
||||
(string) $response_post_id,
|
||||
(string) $this->job['post_time'],
|
||||
(string) $this->job['ailabs_user_id'],
|
||||
$this->job['request'],
|
||||
$parent_job ? utf8_decode_ncr($parent_job['response']) : '...'
|
||||
);
|
||||
|
||||
$sql = 'UPDATE ' . POSTS_TABLE .
|
||||
' SET ' . $this->db->sql_build_array('UPDATE', ['post_text' => utf8_encode_ucr($post_text)]) .
|
||||
' WHERE post_id = ' . (int) $this->job['post_id'];
|
||||
$result = $this->db->sql_query($sql);
|
||||
$this->db->sql_freeresult($result);
|
||||
}
|
||||
|
||||
$maxJobs = empty($this->cfg->maxJobs) ? 3 : $this->cfg->maxJobs;
|
||||
|
||||
$url_callback = generate_board_url(true) .
|
||||
$this->helper->route(
|
||||
'privet_ailabs_midjourney_callback',
|
||||
[
|
||||
'job_id' => $this->job_id,
|
||||
'ref' => $this->job['ref'],
|
||||
'action' => 'reply'
|
||||
]
|
||||
);
|
||||
|
||||
$request = $this->job['request'];
|
||||
$payload = null;
|
||||
|
||||
if (!empty($parent_job)) {
|
||||
$log = json_decode($parent_job['log'], true);
|
||||
|
||||
// https://useapi.net/docs/api-v1/jobs-button
|
||||
if (
|
||||
!empty($log) &&
|
||||
!empty($log['response.json']) &&
|
||||
!empty($log['response.json']['jobid']) &&
|
||||
!empty($log['response.json']['buttons']) &&
|
||||
in_array($request, $log['response.json']['buttons'], true)
|
||||
) {
|
||||
$payload = [
|
||||
'jobid' => $log['response.json']['jobid'],
|
||||
'button' => $request,
|
||||
'discord' => $this->cfg->discord,
|
||||
'maxJobs' => $maxJobs,
|
||||
'replyUrl' => $url_callback,
|
||||
'replyRef' => $this->job_id,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// https://useapi.net/docs/api-v1/jobs-imagine
|
||||
if (empty($payload)) {
|
||||
$payload = [
|
||||
'prompt' => $request,
|
||||
'discord' => $this->cfg->discord,
|
||||
'server' => $this->cfg->server,
|
||||
'channel' => $this->cfg->channel,
|
||||
'maxJobs' => $maxJobs,
|
||||
'replyUrl' => $url_callback,
|
||||
'replyRef' => $this->job_id,
|
||||
];
|
||||
}
|
||||
|
||||
array_push($this->redactOpts, 'discord');
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
protected function submit($opts): resultSubmit
|
||||
{
|
||||
$this->job['status'] = 'query';
|
||||
$this->job_update(['status' => $this->job['status']]);
|
||||
$this->post_update($this->job);
|
||||
|
||||
$api = new GenericCurl($this->cfg->api_key, 0);
|
||||
$this->cfg->api_key = null;
|
||||
|
||||
$retryCount = empty($this->cfg->retryCount) ? 80 : $this->cfg->retryCount;
|
||||
$timeoutBeforeRetrySec = empty($this->cfg->timeoutBeforeRetrySec) ? 15 : $this->cfg->timeoutBeforeRetrySec;
|
||||
|
||||
$count = 0;
|
||||
$response = null;
|
||||
// https://useapi.net/docs/api-v1/jobs-imagine
|
||||
// https://useapi.net/docs/api-v1/jobs-button
|
||||
$url = empty($opts['jobid']) ? $this->cfg->url_imagine : $this->cfg->url_button;
|
||||
|
||||
// Attempt to submit request for (retryCount * timeoutBeforeRetrySec) seconds.
|
||||
// Required for cases where multiple users simultaneously submitting requests or Midjourney query is full.
|
||||
do {
|
||||
$count++;
|
||||
$response = $api->sendRequest($url, 'POST', $opts);
|
||||
} while (
|
||||
// 429: Maximum of xx jobs executing in parallel supported
|
||||
// 504: Unable to lock Discord after xx attempts
|
||||
(in_array(429, $api->responseCodes) || in_array(504, $api->responseCodes)) &&
|
||||
$count < $retryCount &&
|
||||
sleep($timeoutBeforeRetrySec) !== false
|
||||
);
|
||||
|
||||
$data = [
|
||||
'request.time' => date('Y-m-d H:i:s'),
|
||||
'request.config.retryCount' => $retryCount,
|
||||
'request.config.timeoutBeforeRetrySec' => $timeoutBeforeRetrySec,
|
||||
'request.attempts' => $count,
|
||||
'response.codes' => $api->responseCodes,
|
||||
'response.length' => strlen($response),
|
||||
'response.json' => json_decode($response)
|
||||
];
|
||||
|
||||
$url_callback = generate_board_url(true) .
|
||||
$this->helper->route(
|
||||
'privet_ailabs_midjourney_callback',
|
||||
[
|
||||
'job_id' => $this->job_id,
|
||||
'ref' => $this->job['ref'],
|
||||
'action' => 'posted'
|
||||
]
|
||||
);
|
||||
|
||||
$api->sendRequest($url_callback, 'POST', $data);
|
||||
|
||||
$result = new resultSubmit();
|
||||
$result->ignore = true;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function process_response_message_id($json)
|
||||
{
|
||||
$response_message_id = null;
|
||||
|
||||
if (!empty($json) && !empty($json['jobid']))
|
||||
$response_message_id = $json['jobid'];
|
||||
|
||||
if (!empty($response_message_id) && empty($this->job['response_message_id']))
|
||||
$this->job_update(['response_message_id' => $response_message_id]);
|
||||
|
||||
return $response_message_id;
|
||||
}
|
||||
}
|
|
@ -64,7 +64,7 @@ class scriptexecute extends GenericController
|
|||
// 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);
|
||||
// Code removed, see 1.0.5
|
||||
} catch (\Exception $e) {
|
||||
$result->response = '{ "error" : "' . $e . '" }';
|
||||
}
|
||||
|
|
|
@ -128,11 +128,11 @@ class stablediffusion extends GenericController
|
|||
if ($item->finishReason !== 'SUCCESS') {
|
||||
$message = $item->finishReason;
|
||||
if (!empty($item->base64))
|
||||
$item->base64 = '<reducted>';
|
||||
$item->base64 = '<redacted>';
|
||||
} else {
|
||||
$filename = $this->save_base64_to_temp_file($item->base64, $ind);
|
||||
array_push($images, $filename);
|
||||
$item->base64 = '<reducted>';
|
||||
$item->base64 = '<redacted>';
|
||||
}
|
||||
$ind++;
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 84 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 62 KiB |
BIN
privet/ailabs/docs/debugging_log.png
Normal file
After Width: | Height: | Size: 60 KiB |
BIN
privet/ailabs/docs/debugging_post_icon.png
Normal file
After Width: | Height: | Size: 8.5 KiB |
BIN
privet/ailabs/docs/midjourney_example.png
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
privet/ailabs/docs/midjourney_setup.png
Normal file
After Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 65 KiB |
|
@ -115,8 +115,12 @@ class listener implements EventSubscriberInterface
|
|||
if ($mode == 'post' && $user['post'] == 1) {
|
||||
array_push($ailabs_users, $user);
|
||||
} else {
|
||||
if ($user['mention'] == 1 && in_array($user['user_id'], $ailabs_users_notified))
|
||||
if ($mode == 'reply' && $user['reply'] == 1) {
|
||||
array_push($ailabs_users, $user);
|
||||
} else {
|
||||
if ($user['mention'] == 1 && in_array($user['user_id'], $ailabs_users_notified))
|
||||
array_push($ailabs_users, $user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -154,6 +158,39 @@ class listener implements EventSubscriberInterface
|
|||
$request = utf8_encode_ucr($request);
|
||||
}
|
||||
|
||||
global $config;
|
||||
|
||||
$cookie_name = $config['cookie_name'];
|
||||
$headers = [];
|
||||
|
||||
$copy_headers = ['X-Forwarded-For', 'User-Agent'];
|
||||
foreach ($copy_headers as $header_name) {
|
||||
if (!empty($this->request->header($header_name))) {
|
||||
array_push($headers, "$header_name: " . $this->request->header($header_name));
|
||||
}
|
||||
}
|
||||
|
||||
$cookies = [];
|
||||
if ($this->request->is_set($cookie_name . '_sid', \phpbb\request\request_interface::COOKIE) || $this->request->is_set($cookie_name . '_u', \phpbb\request\request_interface::COOKIE)) {
|
||||
array_push($cookies, $cookie_name . '_u=' . $this->request->variable($cookie_name . '_u', 0, false, \phpbb\request\request_interface::COOKIE));
|
||||
array_push($cookies, $cookie_name . '_k=' . $this->request->variable($cookie_name . '_k', '', false, \phpbb\request\request_interface::COOKIE));
|
||||
array_push($cookies, $cookie_name . '_sid=' . $this->request->variable($cookie_name . '_sid', '', false, \phpbb\request\request_interface::COOKIE));
|
||||
|
||||
array_push($headers, "Cookie: " . implode('; ', $cookies));
|
||||
}
|
||||
|
||||
$context = null;
|
||||
if (!empty($headers)) {
|
||||
$context = stream_context_create(
|
||||
array(
|
||||
'http' => array(
|
||||
'method' => "HEAD",
|
||||
'header' => implode("\r\n", $headers)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// https://area51.phpbb.com/docs/dev/master/db/dbal.html
|
||||
foreach ($ailabs_users as $user) {
|
||||
$data = [
|
||||
|
@ -166,6 +203,7 @@ class listener implements EventSubscriberInterface
|
|||
'poster_id' => $this->user->data['user_id'],
|
||||
'poster_name' => $this->user->data['username'],
|
||||
'request' => utf8_encode_ucr($request),
|
||||
'ref' => bin2hex(random_bytes(21))
|
||||
];
|
||||
$sql = 'INSERT INTO ' . $this->jobs_table . ' ' . $this->db->sql_build_array('INSERT', $data);
|
||||
$result = $this->db->sql_query($sql);
|
||||
|
@ -174,8 +212,11 @@ class listener implements EventSubscriberInterface
|
|||
|
||||
$this->update_post($data);
|
||||
|
||||
$url = generate_board_url() . $user['controller'] . '?job_id=' . $data['job_id'];
|
||||
get_headers($url);
|
||||
$url = append_sid(generate_board_url() . $user['controller'], ['job_id' => $data['job_id']], true, $this->user->session_id);
|
||||
|
||||
// Provide same cookies, session id and browser name so phpBB can authenticate user.
|
||||
// Verify that Server Configuration > Security Settings > Session IP validation set to none
|
||||
get_headers($url, false, $context);
|
||||
unset($data);
|
||||
}
|
||||
}
|
||||
|
@ -211,6 +252,7 @@ class listener implements EventSubscriberInterface
|
|||
$return = array();
|
||||
$sql = 'SELECT c.user_id, ' .
|
||||
'c.forums_post LIKE \'%"' . $id . '"%\' as post, ' .
|
||||
'c.forums_reply LIKE \'%"' . $id . '"%\' as reply, ' .
|
||||
'c.forums_mention LIKE \'%"' . $id . '"%\' as mention, ' .
|
||||
'c.controller, ' .
|
||||
'u.username ' .
|
||||
|
@ -223,6 +265,7 @@ class listener implements EventSubscriberInterface
|
|||
'user_id' => $row['user_id'],
|
||||
'username' => $row['username'],
|
||||
'post' => $row['post'],
|
||||
'reply' => $row['reply'],
|
||||
'mention' => $row['mention'],
|
||||
'controller' => $row['controller']
|
||||
));
|
||||
|
@ -261,6 +304,8 @@ class listener implements EventSubscriberInterface
|
|||
return $this->language->lang('AILABS_REPLIED');
|
||||
case 'fail':
|
||||
return $this->language->lang('AILABS_UNABLE_TO_REPLY');
|
||||
case 'query':
|
||||
return $this->language->lang('AILABS_QUERY');
|
||||
}
|
||||
|
||||
return $status;
|
||||
|
@ -312,9 +357,9 @@ class listener implements EventSubscriberInterface
|
|||
$ailabs = array();
|
||||
|
||||
foreach ($jobs as $key => $value) {
|
||||
$value->user_url = generate_board_url() . '/' . append_sid("memberlist.$this->php_ext", 'mode=viewprofile&u=' . $value->ailabs_user_id, true, '');
|
||||
$value->user_url = generate_board_url() . '/' . append_sid("memberlist.$this->php_ext", 'mode=viewprofile&u=' . $value->ailabs_user_id, true, $this->user->session_id);
|
||||
if (!empty($value->response_post_id)) {
|
||||
$value->response_url = generate_board_url() . '/' . append_sid('viewtopic.php?p=' . $value->response_post_id . '#p' . $value->response_post_id, true, '');
|
||||
$value->response_url = generate_board_url() . '/' . append_sid("viewtopic.$this->php_ext", "p=$value->response_post_id#p$value->response_post_id", true, $this->user->session_id);
|
||||
}
|
||||
$value->status = $this->get_status(empty($value->status) ? null : $value->status);
|
||||
array_push($ailabs, $value);
|
||||
|
|
|
@ -80,7 +80,8 @@ class AIController
|
|||
$streamedResponse = new StreamedResponse();
|
||||
$streamedResponse->headers->set('X-Accel-Buffering', 'no');
|
||||
$streamedResponse->setCallback(function () {
|
||||
var_dump('Processing');
|
||||
// Uncomment to debug callback response
|
||||
// echo 'Processing';
|
||||
flush();
|
||||
});
|
||||
$streamedResponse->send();
|
||||
|
@ -91,20 +92,7 @@ class AIController
|
|||
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);
|
||||
$this->load_job();
|
||||
|
||||
if (empty($this->job)) {
|
||||
return new JsonResponse('job_id not found in the database');
|
||||
|
@ -116,8 +104,6 @@ class AIController
|
|||
|
||||
$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) {
|
||||
|
@ -143,6 +129,27 @@ class AIController
|
|||
return $this->process();
|
||||
}
|
||||
|
||||
protected function load_job()
|
||||
{
|
||||
$where = [
|
||||
'job_id' => $this->job_id
|
||||
];
|
||||
|
||||
$sql = 'SELECT j.job_id, j.ailabs_user_id, j.status, j.attempts, j.post_mode, j.post_id, j.forum_id, j.poster_id, j.poster_name, j.request, j.response, j.log, j.ref, j.response_message_id, c.config, c.template, u.username as ailabs_username, p.topic_id, p.post_subject, p.post_text, p.post_time, f.forum_name ' .
|
||||
'FROM ' . $this->jobs_table . ' j ' .
|
||||
'JOIN ' . $this->users_table . ' c ON c.user_id = j.ailabs_user_id ' .
|
||||
'JOIN ' . USERS_TABLE . ' u ON u.user_id = j.ailabs_user_id ' .
|
||||
'JOIN ' . POSTS_TABLE . ' p ON p.post_id = j.post_id ' .
|
||||
'JOIN ' . FORUMS_TABLE . ' f ON f.forum_id = j.forum_id ' .
|
||||
'WHERE ' . $this->db->sql_build_array('SELECT', $where);
|
||||
$result = $this->db->sql_query($sql);
|
||||
$this->job = $this->db->sql_fetchrow($result);
|
||||
$this->db->sql_freeresult($result);
|
||||
|
||||
if (!empty($this->job) && !empty($this->job['request']))
|
||||
$this->job['request'] = utf8_decode_ncr($this->job['request']);
|
||||
}
|
||||
|
||||
protected function process()
|
||||
{
|
||||
return new JsonResponse($this->log);
|
||||
|
@ -240,8 +247,14 @@ class AIController
|
|||
{
|
||||
// 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);
|
||||
generate_text_for_storage($response, $uid, $bitfield, $options, true, true, true);
|
||||
|
||||
// For some reason uid is not calculated by generate_text_for_storage
|
||||
if (empty($uid)) {
|
||||
$message_parser = new \parse_message($response);
|
||||
$message_parser->parse(true, true, true);
|
||||
$uid = $message_parser->bbcode_uid;
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'poster_id' => $job['ailabs_user_id'],
|
||||
|
@ -410,12 +423,6 @@ class AIController
|
|||
'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());
|
||||
|
||||
|
@ -447,4 +454,17 @@ class AIController
|
|||
|
||||
return $filename;
|
||||
}
|
||||
|
||||
protected function trim_words($inputString, $numWords)
|
||||
{
|
||||
$words = explode(' ', $inputString);
|
||||
|
||||
if (count($words) <= $numWords) {
|
||||
return $inputString;
|
||||
}
|
||||
|
||||
$trimmedWords = array_slice($words, 0, $numWords);
|
||||
|
||||
return implode(' ', $trimmedWords) . '...'; //'…';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@ use privet\ailabs\includes\resultSubmit;
|
|||
|
||||
class GenericController extends AIController
|
||||
{
|
||||
public $redactOpts = [];
|
||||
|
||||
// Cleansing and setting back $this->job['request']
|
||||
// Return $opts or empty array
|
||||
protected function init()
|
||||
|
@ -73,7 +75,13 @@ class GenericController extends AIController
|
|||
|
||||
$opts = $this->prepare($opts);
|
||||
|
||||
$this->log['request.json'] = $opts;
|
||||
$optsCloned = json_decode(json_encode($opts), true);
|
||||
|
||||
foreach ($this->redactOpts as $key) {
|
||||
unset($optsCloned[$key]);
|
||||
}
|
||||
|
||||
$this->log['request.json'] = $optsCloned;
|
||||
$this->log_flush();
|
||||
|
||||
$this->job['status'] = 'fail';
|
||||
|
@ -84,6 +92,9 @@ class GenericController extends AIController
|
|||
try {
|
||||
$resultSubmit = $this->submit($opts);
|
||||
|
||||
if ($resultSubmit->ignore)
|
||||
return new JsonResponse('waiting for callback');
|
||||
|
||||
$this->log['response.length'] = strlen($resultSubmit->response);
|
||||
$this->log['response.codes'] = $resultSubmit->responseCodes;
|
||||
$this->log_flush();
|
||||
|
|
|
@ -15,4 +15,5 @@ class resultSubmit
|
|||
{
|
||||
public string $response;
|
||||
public $responseCodes;
|
||||
public bool $ignore = false;
|
||||
};
|
|
@ -18,12 +18,16 @@ if (empty($lang) || !is_array($lang)) {
|
|||
}
|
||||
|
||||
$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',
|
||||
'L_AILABS_AI' => 'AI'
|
||||
'AILABS_MJ_BUTTONS' => 'Reply by quoting one of the supported actions [size=70][url=https://docs.midjourney.com/docs/quick-start#8-upscale-or-create-variations]1[/url] [url=https://docs.midjourney.com/docs/quick-start#9-enhance-or-modify-your-image]2[/url][/size]: ',
|
||||
'AILABS_MJ_BUTTON_ALREADY_USED' => 'Action %1s was already [url=%2$s?p=%3$d#p%3$d]executed[/url]',
|
||||
'AILABS_ERROR_CHECK_LOGS' => '[color=#FF0000]Error. Please check logs.[/color]',
|
||||
'AILABS_MJ_MODERATED' => '[color=#FF0000]Moderated or invalid prompt.[/color]',
|
||||
'AILABS_POSTS_DISCARDED' => ', posts starting from [url=%1$s?p=%2$d#p%2$d]this post[/url] were discarded',
|
||||
'AILABS_DISCARDED_INFO' => '[size=75][url=%1$s?p=%2$d#p%2$d]Beginning[/url] of a conversation containing %3$d posts%4$s (%5$d tokens of %6$d were used)[/size]',
|
||||
'AILABS_THINKING' => 'thinking',
|
||||
'AILABS_REPLYING' => 'replying…',
|
||||
'AILABS_REPLIED' => 'replied ↓',
|
||||
'AILABS_UNABLE_TO_REPLY' => 'unable to reply',
|
||||
'AILABS_QUERY' => 'querying',
|
||||
'L_AILABS_AI' => 'AI'
|
||||
]);
|
||||
|
|
|
@ -29,7 +29,7 @@ $lang = array_merge($lang, [
|
|||
'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',
|
||||
'AILABS_SPECIFY_FORUM' => 'Please select at least one forum',
|
||||
|
||||
'LOG_ACP_AILABS_ADDED' => 'AI Labs configuration added',
|
||||
'LOG_ACP_AILABS_EDITED' => 'AI Labs configuration updated',
|
||||
|
@ -39,20 +39,31 @@ $lang = array_merge($lang, [
|
|||
'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_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, troubleshooting and examples.',
|
||||
'LBL_AILABS_USERNAME' => 'AI bot',
|
||||
'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_REPLY_TO' => 'Forums where AI bot reply to',
|
||||
'LBL_AILABS_POST_FORUMS' => 'New topic',
|
||||
'LBL_AILABS_REPLY_FORUMS' => 'Reply in a topic',
|
||||
'LBL_AILABS_QUOTE_FORUMS' => 'Quote or <a href="https://www.phpbb.com/customise/db/extension/simple_mentions/" rel="nofollow">mention</a>',
|
||||
'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_POST_FORUMS_EXPLAIN' => 'Specify forums where AI will reply to new topic',
|
||||
'LBL_AILABS_REPLY_FORUMS_EXPLAIN' => 'Specify forums where AI will reply to reply in the topic',
|
||||
'LBL_AILABS_QUOTE_FORUMS_EXPLAIN' => 'Specify forums where AI will reply when quoted or <a href="https://www.phpbb.com/customise/db/extension/simple_mentions/" rel="nofollow">mentioned</a>',
|
||||
'LBL_AILABS_IP_VALIDATION' => '⚠️ Warning: Your ACP > General > Server Configuration > Security Settings > ' .
|
||||
'<a href="%1$s">Session IP validation setting NOT set to None</a>, ' .
|
||||
'this may prevent AI Labs to reply if you are using phpBB extensions which force user to be logged in ' .
|
||||
'(eg <a href="https://www.phpbb.com/customise/db/extension/login_required">Login Required</a>). ' .
|
||||
'Set Session IP validation to None or add "/ailabs/*" to extension whitelist. ' .
|
||||
'Please refer to <a href="https://github.com/privet-fun/phpbb_ailabs#troubleshooting">troubleshooting section</a> for more details.',
|
||||
|
||||
'LBL_AILABS_CONFIG_DEFAULT' => 'Load default configuration',
|
||||
'LBL_AILABS_TEMPLATE_DEFAULT' => 'Load default template',
|
||||
]);
|
||||
|
|
|
@ -18,12 +18,16 @@ if (empty($lang) || !is_array($lang)) {
|
|||
}
|
||||
|
||||
$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' => 'ответить не смог',
|
||||
'L_AILABS_AI' => 'AI'
|
||||
'AILABS_MJ_BUTTONS' => 'Ответьте, процитировав одну из поддерживаемых команд [size=70][url=https://docs.midjourney.com/docs/quick-start#8-upscale-or-create-variations]1[/url] [url=https://docs.midjourney.com/docs/quick-start#9-enhance-or-modify-your-image]2[/url][/size]: ',
|
||||
'AILABS_MJ_BUTTON_ALREADY_USED' => 'Команда %1s уже была [url=%2$s?p=%3$d#p%3$d]выполнена[/url]',
|
||||
'AILABS_ERROR_CHECK_LOGS' => '[color=#FF0000]Ошибка. Лог содержит детальную информацию.[/color]',
|
||||
'AILABS_MJ_MODERATED' => '[color=#FF0000]Запрос составлен неверно или подвергся модерации Midjourney.[/color]',
|
||||
'AILABS_POSTS_DISCARDED' => ', сообщения начиная с [url=%1$s?p=%2$d#p%2$d]этого[/url] не включены',
|
||||
'AILABS_DISCARDED_INFO' => '[size=75][url=%1$s?p=%2$d#p%2$d]Начало[/url] беседы из %3$d сообщений%4$s (%5$d токенов из %6$d использовано)[/size]',
|
||||
'AILABS_THINKING' => 'думает',
|
||||
'AILABS_REPLYING' => 'отвечает…',
|
||||
'AILABS_REPLIED' => 'ответил ↓',
|
||||
'AILABS_UNABLE_TO_REPLY' => 'ответить не смог',
|
||||
'AILABS_QUERY' => 'в очереди',
|
||||
'L_AILABS_AI' => 'AI'
|
||||
]);
|
||||
|
|
|
@ -29,7 +29,7 @@ $lang = array_merge($lang, [
|
|||
'AILABS_USER_EMPTY' => 'Пожалуйста, выберите пользователя',
|
||||
'AILABS_USER_NOT_FOUND' => 'Не удалось найти пользователя %1$s',
|
||||
'AILABS_USER_ALREADY_CONFIGURED' => 'Пользователь %1$s уже настроен, поддерживается только одна конфигурация на пользователя',
|
||||
'AILABS_SPECIFY_POST_OR_MENTION' => 'Нельзя оставлять пустыми и "Ответ на сообщение" и "Ответ при цитировании", пожалуйста, укажите хотя бы одно значение',
|
||||
'AILABS_SPECIFY_FORUM' => 'Укажите хотя бы один форум',
|
||||
|
||||
'LOG_ACP_AILABS_ADDED' => 'Конфигурация AI Labs добавлена',
|
||||
'LOG_ACP_AILABS_EDITED' => 'Конфигурация AI Labs изменена',
|
||||
|
@ -39,20 +39,33 @@ $lang = array_merge($lang, [
|
|||
'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_SETTINGS_DESC' => 'Пожалуйста, посетите 👉 <a href="https://github.com/privet-fun/phpbb_ailabs">https://github.com/privet-fun/phpbb_ailabs</a> для получения подробных инструкций по настройке и примеров.',
|
||||
'LBL_AILABS_USERNAME' => 'AI бот',
|
||||
'LBL_AILABS_CONTROLLER' => 'AI',
|
||||
'LBL_AILABS_CONFIG' => 'Конфигурация в формате JSON',
|
||||
'LBL_AILABS_TEMPLATE' => 'Шаблон',
|
||||
'LBL_AILABS_REPLY_POST_FORUMS' => 'Ответ на сообщение',
|
||||
'LBL_AILABS_REPLY_QUOTE_FORUMS' => 'Ответ при цитировании',
|
||||
|
||||
'LBL_AILABS_REPLY_TO' => 'Форумы где AI бот отвечает на',
|
||||
'LBL_AILABS_POST_FORUMS' => 'Новую тему',
|
||||
'LBL_AILABS_REPLY_FORUMS' => 'Ответ в теме',
|
||||
'LBL_AILABS_QUOTE_FORUMS' => 'Цитирование или <a href="https://www.phpbb.com/customise/db/extension/simple_mentions/" rel="nofollow">упоминание</a>',
|
||||
'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_POST_FORUMS_EXPLAIN' => 'Укажите форумы, на которых AI будет отвечать на новые темы',
|
||||
'LBL_AILABS_REPLY_FORUMS_EXPLAIN' => 'Укажите форумы, на которых AI будет отвечать на ответы',
|
||||
'LBL_AILABS_QUOTE_FORUMS_EXPLAIN' => 'Укажите форумы, на которых AI будет отвечать на цитируемые сообщения или <a href="https://www.phpbb.com/customise/db/extension/simple_mentions/" rel="nofollow">упоминания</a>',
|
||||
'LBL_AILABS_IP_VALIDATION' => '⚠️ Предупреждение: Администрировать > Общие > Конфигурация сервера > Безопасность > ' .
|
||||
'<a href="%1$s">Проверка IP-адреса сессии НЕ установлена в значение «Нет»</a>. ' .
|
||||
'Это может препятствовать работе AI Labs в случае если вы используете расширения phpBB, ' .
|
||||
'требующие авторизацию пользователя ' .
|
||||
'(например <a href="https://www.phpbb.com/customise/db/extension/login_required">Login Required</a>). ' .
|
||||
'Установите проверку IP-адреса сессии в значение "Нет" или добавьте "/ailabs/*" в белый список расширений. ' .
|
||||
'Пожалуйста, обратитесь к <a href="https://github.com/privet-fun/phpbb_ailabs#troubleshooting">разделу по ' .
|
||||
'устранению неполадок</a> для получения дополнительной информации.',
|
||||
|
||||
'LBL_AILABS_CONFIG_DEFAULT' => 'Загрузить конфигурацию по умолчанию',
|
||||
'LBL_AILABS_TEMPLATE_DEFAULT' => 'Загрузить шаблон по умолчанию',
|
||||
]);
|
||||
|
|
61
privet/ailabs/migrations/v1x/release_1_0_4_schema.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?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_4_schema extends \phpbb\db\migration\migration
|
||||
{
|
||||
static public function depends_on()
|
||||
{
|
||||
return array('\privet\ailabs\migrations\v1x\release_1_0_0_schema');
|
||||
}
|
||||
|
||||
public function effectively_installed()
|
||||
{
|
||||
return isset($this->config['privet_ailabs_version']) && version_compare($this->config['privet_ailabs_version'], '1.0.4', '>=');
|
||||
}
|
||||
|
||||
public function update_data()
|
||||
{
|
||||
return array(
|
||||
array('config.add', array('privet_ailabs_version', '1.0.4')),
|
||||
);
|
||||
}
|
||||
|
||||
public function revert_data()
|
||||
{
|
||||
return array(
|
||||
array('config.remove', array('privet_ailabs_version')),
|
||||
);
|
||||
}
|
||||
|
||||
public function update_schema()
|
||||
{
|
||||
return [
|
||||
'add_columns' => [
|
||||
$this->table_prefix . 'ailabs_users' => [
|
||||
'forums_reply' => ['VCHAR', ''], // eg ["forum_id1","forum_id2"]
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function revert_schema()
|
||||
{
|
||||
return [
|
||||
'drop_columns' => [
|
||||
$this->table_prefix . 'ailabs_jobs' => [
|
||||
'forums_reply',
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
63
privet/ailabs/migrations/v1x/release_1_0_5_schema.php
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* AI Labs extension
|
||||
*
|
||||
* @copyright (c) 2023, privet.fun, https://privet.fun
|
||||
* @license GNU General Public License, version 2 (GPL-2.0)
|
||||
*
|
||||
*/
|
||||
|
||||
namespace privet\ailabs\migrations\v1x;
|
||||
|
||||
class release_1_0_5_schema extends \phpbb\db\migration\migration
|
||||
{
|
||||
static public function depends_on()
|
||||
{
|
||||
return array('\privet\ailabs\migrations\v1x\release_1_0_4_schema');
|
||||
}
|
||||
|
||||
public function effectively_installed()
|
||||
{
|
||||
return isset($this->config['privet_ailabs_version']) && version_compare($this->config['privet_ailabs_version'], '1.0.5', '>=');
|
||||
}
|
||||
|
||||
public function update_data()
|
||||
{
|
||||
return array(
|
||||
array('config.update', array('privet_ailabs_version', '1.0.5')),
|
||||
);
|
||||
}
|
||||
|
||||
public function revert_data()
|
||||
{
|
||||
return array(
|
||||
array('config.update', array('privet_ailabs_version', '1.0.4')),
|
||||
);
|
||||
}
|
||||
|
||||
public function update_schema()
|
||||
{
|
||||
return [
|
||||
'add_columns' => [
|
||||
$this->table_prefix . 'ailabs_jobs' => [
|
||||
'ref' => ['VCHAR', ''], // uniquer alpha-numeric value
|
||||
'response_message_id' => ['VCHAR', ''], // uniquer alpha-numeric value
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function revert_schema()
|
||||
{
|
||||
return [
|
||||
'drop_columns' => [
|
||||
$this->table_prefix . 'ailabs_jobs' => [
|
||||
'ref',
|
||||
'response_message_id'
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
}
|
39
privet/ailabs/migrations/v1x/release_1_0_6_schema.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?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_6_schema extends \phpbb\db\migration\migration
|
||||
{
|
||||
static public function depends_on()
|
||||
{
|
||||
return array('\privet\ailabs\migrations\v1x\release_1_0_5_schema');
|
||||
}
|
||||
|
||||
public function effectively_installed()
|
||||
{
|
||||
return isset($this->config['privet_ailabs_version']) && version_compare($this->config['privet_ailabs_version'], '1.0.6', '>=');
|
||||
}
|
||||
|
||||
public function update_data()
|
||||
{
|
||||
return array(
|
||||
array('config.update', array('privet_ailabs_version', '1.0.6')),
|
||||
);
|
||||
}
|
||||
|
||||
public function revert_data()
|
||||
{
|
||||
return array(
|
||||
array('config.update', array('privet_ailabs_version', '1.0.5')),
|
||||
);
|
||||
}
|
||||
}
|