Skip to content

Commit 1f1d49c

Browse files
author
Leonid Vakulenko
committed
Shop-Script v.11.4.0
* Added artificial intelligence (Webasyst AI service) to help you quickly fill product pages with text content. * Bulk product images uploading functionality moved to the Products section and now available via the Add many button. * Fixed a few found errors.
1 parent 15f87bc commit 1f1d49c

File tree

62 files changed

+2672
-215
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+2672
-215
lines changed

api/swagger/v1.yaml

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,70 @@ servers:
1717

1818
paths:
1919
#CATEGORY
20+
/shop.ai.gpt:
21+
post:
22+
summary: Генерация текстов с помощью ИИ
23+
security:
24+
- ApiKeyAuth: []
25+
externalDocs:
26+
description: shop.ai.gpt
27+
url: https://developers.webasyst.ru/api/explorer/shop/shop.ai.gpt/
28+
parameters:
29+
- $ref: '#/components/parameters/requestFormat'
30+
requestBody:
31+
required: true
32+
content:
33+
application/x-www-form-urlencoded:
34+
schema:
35+
type: object
36+
properties:
37+
entity:
38+
type: string
39+
description: Тип сущности, для которой генерить текст (например, товар ШС - product)
40+
enum:
41+
- product
42+
field:
43+
type: string
44+
description: Для какого поля сущности генерить текст (например, для описания товара - description)
45+
enum:
46+
- description
47+
- summary
48+
- meta_title
49+
- meta_keywords
50+
- meta_description
51+
- page
52+
entity_id:
53+
type: integer
54+
description: ID сущности, для которой генерить текст (например, id товара). Сохранённые в ней данные будут использованы при генерации.
55+
minimum: 0
56+
objective:
57+
type: string
58+
description: Текстовый запрос для генерации, используется (и обязателен) для entity=product field=page
59+
required:
60+
- entity
61+
- field
62+
responses:
63+
200:
64+
description: Результат генерации содержит поле field, указанное в запросе.
65+
content:
66+
application/json:
67+
schema:
68+
type: object
69+
400:
70+
description: Некорректное содержимое запроса.
71+
content:
72+
application/json:
73+
schema:
74+
$ref: '#/components/schemas/invalidResponse'
75+
402:
76+
description: Payment required
77+
content:
78+
application/json:
79+
schema:
80+
$ref: '#/components/schemas/invalidResponse'
81+
500:
82+
description: Внутренняя ошибка сервера.
83+
2084
/shop.category.add:
2185
post:
2286
summary: Добавляет новую категорию товаров.

api/v1/shop.ai.gpt.method.php

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<?php
2+
/** @since 11.4.0 */
3+
class shopAiGptMethod extends shopApiMethod
4+
{
5+
protected $method = 'POST';
6+
7+
public function execute()
8+
{
9+
if (!shopLicensing::hasPremiumLicense()) {
10+
throw new waAPIException('payment_required', _w('Only available with the premium license.'), 402);
11+
}
12+
13+
$entity_type = (string) $this->post('entity', true);
14+
15+
switch ($entity_type) {
16+
case 'product':
17+
$this->executeProduct();
18+
break;
19+
default:
20+
throw new waAPIException('entity_required', 'entity is unknown or missing', 400);
21+
break;
22+
}
23+
}
24+
25+
protected function executeProduct()
26+
{
27+
$field = (string) $this->post('field', true);
28+
29+
switch ($field) {
30+
case 'description':
31+
case 'summary':
32+
$product_id = (int) $this->post('entity_id');
33+
34+
$description_request = (new shopAiApiRequest())
35+
->loadFieldsFromApi('store_product')
36+
->loadFieldValuesFromSettings();
37+
38+
if ($product_id) {
39+
try {
40+
$product = new shopProduct($product_id);
41+
$description_request->loadFieldValuesFromProduct($product);
42+
} catch (waException $e) {
43+
throw new waAPIException('product_not_found', 'Bad product_id', 400);
44+
}
45+
}
46+
47+
if ($field == 'summary') {
48+
$description_request->setFieldValue('text_length', 'minimal');
49+
if (!empty($product) && !empty($product['description'])) {
50+
$description_request->setFieldValue('traits', $product['description']);
51+
}
52+
}
53+
54+
$res = $description_request->generate();
55+
if (isset($res['description'])) {
56+
$this->response[$field] = $res['description'];
57+
} else {
58+
throw new waAPIException(ifempty($res, 'error', 'server_error'), ifempty($res, 'error_description', 'Unable to generate AI response'), 500);
59+
}
60+
break;
61+
62+
case 'meta_title':
63+
case 'meta_keywords':
64+
case 'meta_description':
65+
$product_id = (int) $this->post('entity_id', true);
66+
67+
$seo_request = (new shopAiApiRequest())
68+
->loadFieldsFromApi('store_product_seo')
69+
->loadFieldValuesFromSettings();
70+
if (!empty($product)) {
71+
$seo_request->loadFieldValuesFromProduct($product);
72+
}
73+
$res = $seo_request->generate();
74+
if (!empty($res['error_description']) || !empty($res['error'])) {
75+
throw new waAPIException(ifempty($res, 'error', 'server_error'), ifempty($res, 'error_description', 'Unable to generate AI response'), 500);
76+
}
77+
foreach (['meta_title', 'meta_keywords', 'meta_description'] as $k) {
78+
$res_k = substr($k, 5);
79+
if (!empty($res[$res_k])) {
80+
$this->response[$k] = $res[$res_k];
81+
}
82+
}
83+
break;
84+
85+
case 'page':
86+
$product_id = (int) $this->post('entity_id', true);
87+
$objective = (string) $this->post('objective', true);
88+
89+
$product = new shopProduct($product_id);
90+
$description_request = (new shopAiApiRequest())
91+
->loadFieldsFromApi('store_product_page')
92+
->loadFieldValuesFromSettings()
93+
->loadFieldValuesFromProduct($product)
94+
->setFieldValues([
95+
'objective' => $objective,
96+
]);
97+
98+
$res = $description_request->generate();
99+
if (!empty($res['content'])) {
100+
$this->response['content'] = $res['content'];
101+
} else {
102+
throw new waAPIException(ifempty($res, 'error', 'server_error'), ifempty($res, 'error_description', 'Unable to generate AI response'), 500);
103+
}
104+
break;
105+
106+
default:
107+
throw new waAPIException('field_required', 'field is unknown or missing', 400);
108+
break;
109+
}
110+
}
111+
}

css/backend/products/main/main.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

css/backend/products/ui/ui.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

css/backend/products/wa2/wa2.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
12.1 KB
Loading
12.1 KB
Loading

js-legacy/settings/settings.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,11 @@ $.extend($.settings = $.settings || {}, {
378378
return true;
379379
},
380380

381+
aiPreLoad: function () {
382+
this.$container.load('?module=settingsAi');
383+
return true;
384+
},
385+
381386
//
382387
// Helpers
383388
//

js/backend/marketing/promo.js

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1928,8 +1928,9 @@
19281928
}
19291929

19301930
function onSubmit() {
1931-
var form_data = getData();
1931+
clearError();
19321932

1933+
var form_data = getData();
19331934
if (form_data.errors.length) {
19341935
renderErrors(form_data.errors);
19351936

@@ -1954,6 +1955,7 @@
19541955
$promise
19551956
.always( function() {
19561957
is_locked = false;
1958+
that.$submit_button.attr("disabled", false);
19571959
})
19581960
.done( function(response) {
19591961
if (response.status === "ok") {
@@ -1971,7 +1973,6 @@
19711973

19721974
} else if (response.errors) {
19731975
renderErrors(response.errors);
1974-
that.$submit_button.attr("disabled", false);
19751976
$message.remove();
19761977
}
19771978
})
@@ -1989,8 +1990,6 @@
19891990

19901991
$(window).scrollTop( that.$wrapper.find(".js-page-errors-wrapper").offset().top );
19911992
}
1992-
1993-
that.$submit_button.attr("disabled", false);
19941993
$message.remove();
19951994
});
19961995
}
@@ -2072,8 +2071,12 @@
20722071
$field = $_rule;
20732072
$error_wrapper = $_rule.find("> .js-section-footer");
20742073
focus_top_array.push($error_wrapper.offset().top);
2075-
} else {
2074+
} else if ($field.length) {
20762075
focus_top_array.push($field.offset().top);
2076+
} else if (error.text) {
2077+
renderError(error.text, that.$submit_button.after(
2078+
$("<span class=\"s-error-message state-error-hint custom-ml-4\" />").text(error.text)
2079+
));
20772080
}
20782081
}
20792082

@@ -2122,6 +2125,11 @@
21222125
}
21232126

21242127
}
2128+
2129+
function clearError() {
2130+
that.$wrapper.find('.s-error-message').remove();
2131+
that.$wrapper.find('.state-error').removeClass('state-error');
2132+
}
21252133
};
21262134

21272135
Promo.prototype.initCouponRulesSection = function(options) {

0 commit comments

Comments
 (0)