Compare commits

..

10 commits

Author SHA1 Message Date
49a3185585
Make LocalAI Stable Diffusion work in plugin. 2023-12-24 22:44:02 +08:00
privet.fun
625c47ab44 More changes for extension certification process 2023-10-08 09:46:16 -07:00
privet.fun
8c7622eb0b Deleted commented out code to please EPV extension checker 2023-10-07 13:40:30 -07:00
privet.fun
ed3eacff44 Commented out core.modify_attachment_sql_ary_on_submit 2023-10-07 13:34:57 -07:00
privet.fun
8707a1c135 Better comments 2023-10-07 13:16:24 -07:00
privet.fun
13c1b6b66d Addressing phpBB extension certification issues 2023-10-07 12:40:11 -07:00
privet.fun
5554ef8593 Spellcheck 2023-10-01 16:20:44 -07:00
privet.fun
8a9a15a8ac Updated documentation. 2023-10-01 16:16:42 -07:00
privet.fun
177d396c6d Code minus docs 2023-10-01 14:08:48 -07:00
privet.fun
c675a9537c 1.0.4 2023-06-04 14:05:48 -07:00
32 changed files with 1003 additions and 197 deletions

View file

@ -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

View file

@ -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,6 +141,7 @@
function doReset() {
setTimeout(function () {
resetValue('ailabs_forums_post', {{ ailabs_forums_post }});
resetValue('ailabs_forums_reply', {{ ailabs_forums_reply }});
resetValue('ailabs_forums_mention', {{ ailabs_forums_mention }});
}, 500);
}
@ -127,6 +149,7 @@
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>

View file

@ -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",

View file

@ -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 }

View file

@ -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:

View file

@ -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 . '&amp;action=add' : $this->u_action . '&amp;action=edit&amp;user_id=' . $this->user_id,
'U_BACK' => $this->u_action,
'U_FIND_USERNAME' => append_sid("{$phpbb_root_path}memberlist.$phpEx", 'mode=searchuser&amp;form=ailabs_configuration&amp;field=ailabs_username&amp;select_single=true'),
'U_FIND_USERNAME' => generate_board_url() . '/' . append_sid("memberlist.$this->php_ext", 'mode=searchuser&amp;form=ailabs_configuration&amp;field=ailabs_username&amp;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&amp;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 . '&amp;action=edit&amp;user_id=' . $row['user_id'] . '&amp;hash=' . generate_link_hash('acp_ailabs');
$row['U_DELETE'] = $this->u_action . '&amp;action=delete&amp;user_id=' . $row['user_id'] . '&amp;username=' . $row['username'] . '&amp;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 . '&amp;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,13 +325,13 @@ class acp_controller //implements acp_interface
return $return;
}
protected function get_forums_names($str, $forums) {
$result = [];
if(!empty($str)) {
$arr = json_decode($str);
if(!empty($arr) && is_array($arr)) {
foreach($arr as $id)
protected function get_forums_names($str, $forums)
{
$result = [];
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);
}
@ -320,5 +339,4 @@ class acp_controller //implements acp_interface
}
return join(', ', $result);
}
}

View file

@ -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,7 +96,6 @@ 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']];
$pattern = '/<QUOTE\sauthor="' . $this->job['ailabs_username'] . '"\spost_id="(.*)"\stime="(.*)"\suser_id="' . $this->job['ailabs_user_id'] . '">/';
@ -100,6 +103,7 @@ class chatgpt extends AIController
$this->log['history.pattern'] = $pattern;
$this->log_flush();
// Attempt to unwind history using quoted posts
$history_tokens = 0;
$round = -1;
do {
@ -116,7 +120,7 @@ class chatgpt extends AIController
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 ' .
$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]);
@ -145,11 +149,34 @@ class chatgpt extends AIController
$post_first_taken = $postid;
$history_tokens += $count_tokens;
$history_decoded_request = utf8_decode_ncr($history['request']);
$history_decoded_response = utf8_decode_ncr($history['response']);
array_unshift(
$messages,
['role' => 'user', 'content' => trim($history['request'])],
['role' => 'assistant', 'content' => trim($history['response'])]
['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));
@ -158,7 +185,6 @@ class chatgpt extends AIController
$this->log['history.posts'] = $posts;
$this->log_flush();
}
}
if (!empty($this->cfg->prefix)) {
array_unshift(
@ -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,

View file

@ -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,
];
}
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->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++;

View file

@ -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&amp;u=' . $row['poster_id'], true, '');
$row['ailabs_user_url'] = generate_board_url() . '/' . append_sid("memberlist.$this->php_ext", 'mode=viewprofile&amp;u=' . $row['ailabs_user_id'], true, '');
$row['poster_user_url'] = generate_board_url() . '/' . append_sid("memberlist.$this->php_ext", 'mode=viewprofile&amp;u=' . $row['poster_id'], true, $this->user->session_id);
$row['ailabs_user_url'] = generate_board_url() . '/' . append_sid("memberlist.$this->php_ext", 'mode=viewprofile&amp;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);
}
}

View file

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

View file

@ -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 . '" }';
}

View file

@ -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++;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 65 KiB

View file

@ -114,11 +114,15 @@ class listener implements EventSubscriberInterface
foreach ($ailabs_users_forum as $user) {
if ($mode == 'post' && $user['post'] == 1) {
array_push($ailabs_users, $user);
} else {
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);
}
}
}
if (empty($ailabs_users)) {
return false;
@ -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&amp;u=' . $value->ailabs_user_id, true, '');
$value->user_url = generate_board_url() . '/' . append_sid("memberlist.$this->php_ext", 'mode=viewprofile&amp;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);

View file

@ -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) . '...'; //'…';
}
}

View file

@ -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();

View file

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

View file

@ -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_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'
]);

View file

@ -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',
]);

View file

@ -18,12 +18,16 @@ if (empty($lang) || !is_array($lang)) {
}
$lang = array_merge($lang, [
'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_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_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'
]);

View file

@ -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' => 'Загрузить шаблон по умолчанию',
]);

View 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',
]
]
];
}
}

View 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'
]
]
];
}
}

View 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')),
);
}
}