strapi-llm-translator 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +86 -0
- package/dist/_chunks/App-Boy_i56G.mjs +223 -0
- package/dist/_chunks/App-COSMdnpL.js +223 -0
- package/dist/_chunks/de-CvAR4QXO.js +29 -0
- package/dist/_chunks/de-D3VSS3Mq.mjs +29 -0
- package/dist/_chunks/en-BjVvIBv8.js +29 -0
- package/dist/_chunks/en-BnBUQVgF.mjs +29 -0
- package/dist/_chunks/index-BEhzLY5B.mjs +277 -0
- package/dist/_chunks/index-DuLkx3CK.js +276 -0
- package/dist/admin/index.js +3 -0
- package/dist/admin/index.mjs +4 -0
- package/dist/admin/src/components/Initializer.d.ts +5 -0
- package/dist/admin/src/components/LLMButton.d.ts +2 -0
- package/dist/admin/src/components/PluginIcon.d.ts +18 -0
- package/dist/admin/src/index.d.ts +11 -0
- package/dist/admin/src/pages/App.d.ts +2 -0
- package/dist/admin/src/pages/HomePage.d.ts +2 -0
- package/dist/admin/src/pluginId.d.ts +1 -0
- package/dist/admin/src/utils/constants.d.ts +4 -0
- package/dist/admin/src/utils/getLocaleFromUrl.d.ts +1 -0
- package/dist/admin/src/utils/getTranslation.d.ts +2 -0
- package/dist/server/index.js +392 -0
- package/dist/server/index.mjs +393 -0
- package/dist/server/src/bootstrap.d.ts +5 -0
- package/dist/server/src/config/constants.d.ts +2 -0
- package/dist/server/src/config/index.d.ts +9 -0
- package/dist/server/src/content-types/index.d.ts +2 -0
- package/dist/server/src/controllers/admin.controller.d.ts +11 -0
- package/dist/server/src/controllers/index.d.ts +23 -0
- package/dist/server/src/destroy.d.ts +5 -0
- package/dist/server/src/index.d.ts +63 -0
- package/dist/server/src/middlewares/index.d.ts +2 -0
- package/dist/server/src/policies/index.d.ts +2 -0
- package/dist/server/src/register.d.ts +5 -0
- package/dist/server/src/routes/admin.d.ts +9 -0
- package/dist/server/src/routes/index.d.ts +14 -0
- package/dist/server/src/services/index.d.ts +6 -0
- package/dist/server/src/services/llm-service.d.ts +6 -0
- package/dist/server/src/types/controllers.d.ts +22 -0
- package/dist/server/src/types/index.d.ts +41 -0
- package/package.json +70 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const en = {
|
|
2
|
+
"strapi-llm-translator.button.label.error": "Translation failed",
|
|
3
|
+
"strapi-llm-translator.button.label.idle": "Translate with AI",
|
|
4
|
+
"strapi-llm-translator.button.label.loading": "Translating content...",
|
|
5
|
+
"strapi-llm-translator.button.label.success": "Translation completed",
|
|
6
|
+
"strapi-llm-translator.button.tooltip.disabled": "Translation is only available for localized content types",
|
|
7
|
+
"strapi-llm-translator.button.tooltip.error": "Translation failed. Click to try again",
|
|
8
|
+
"strapi-llm-translator.button.tooltip.idle": "Translate content using AI",
|
|
9
|
+
"strapi-llm-translator.button.tooltip.loading": "Content is being translated",
|
|
10
|
+
"strapi-llm-translator.button.tooltip.success": "Content has been translated successfully",
|
|
11
|
+
"strapi-llm-translator.notification.error": "Translation failed. Error:",
|
|
12
|
+
"strapi-llm-translator.notification.success": "Translation completed successfully.",
|
|
13
|
+
"strapi-llm-translator.plugin.name": "LLM Translator",
|
|
14
|
+
"strapi-llm-translator.plugin.page.description": "Configure the LLM Translator plugin settings. Be aware that Base Model, API Key and LLM Base URL need to be set as environment variables.",
|
|
15
|
+
"strapi-llm-translator.plugin.page.form.llm_base_url": "LLM Base URL",
|
|
16
|
+
"strapi-llm-translator.plugin.page.form.llm_base_url_hint": "Base URL for the LLM API. It needs to be set as an environment variable.",
|
|
17
|
+
"strapi-llm-translator.plugin.page.form.llm_model": "LLM Model",
|
|
18
|
+
"strapi-llm-translator.plugin.page.form.llm_model_hint": "Model that will be used to generate the translations. It needs to be set as an environment variable.",
|
|
19
|
+
"strapi-llm-translator.plugin.page.form.llm_temperature": "LLM Temperature",
|
|
20
|
+
"strapi-llm-translator.plugin.page.form.llm_temperature_hint": "A higher value will make the output more random, while a lower value will make it more focused and deterministic.",
|
|
21
|
+
"strapi-llm-translator.plugin.page.form.restore": "Restore to default",
|
|
22
|
+
"strapi-llm-translator.plugin.page.form.save": "Save changes",
|
|
23
|
+
"strapi-llm-translator.plugin.page.form.system_prompt": "System Prompt",
|
|
24
|
+
"strapi-llm-translator.plugin.page.form.system_prompt_hint": "This is (part) of the prompt that will be used to instruct the LLM. It should be clear and concise.",
|
|
25
|
+
"strapi-llm-translator.plugin.page.title": "LLM Translator (Configuration)"
|
|
26
|
+
};
|
|
27
|
+
export {
|
|
28
|
+
en as default
|
|
29
|
+
};
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { useRef, useEffect, useState } from "react";
|
|
2
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useTheme } from "styled-components";
|
|
4
|
+
import { useIntl } from "react-intl";
|
|
5
|
+
import { Magic, CheckCircle, WarningCircle } from "@strapi/icons";
|
|
6
|
+
import { Button } from "@strapi/design-system";
|
|
7
|
+
import { unstable_useContentManagerContext, useFetchClient, useNotification } from "@strapi/strapi/admin";
|
|
8
|
+
const __variableDynamicImportRuntimeHelper = (glob, path, segs) => {
|
|
9
|
+
const v = glob[path];
|
|
10
|
+
if (v) {
|
|
11
|
+
return typeof v === "function" ? v() : Promise.resolve(v);
|
|
12
|
+
}
|
|
13
|
+
return new Promise((_, reject) => {
|
|
14
|
+
(typeof queueMicrotask === "function" ? queueMicrotask : setTimeout)(
|
|
15
|
+
reject.bind(
|
|
16
|
+
null,
|
|
17
|
+
new Error(
|
|
18
|
+
"Unknown variable dynamic import: " + path + (path.split("/").length !== segs ? ". Note that variables only represent file names one level deep." : "")
|
|
19
|
+
)
|
|
20
|
+
)
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
const PLUGIN_ID = "strapi-llm-translator";
|
|
25
|
+
const Initializer = ({ setPlugin }) => {
|
|
26
|
+
const ref = useRef(setPlugin);
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
ref.current(PLUGIN_ID);
|
|
29
|
+
}, []);
|
|
30
|
+
return null;
|
|
31
|
+
};
|
|
32
|
+
const PluginIcon = ({
|
|
33
|
+
fill: fillProp = "currentColor",
|
|
34
|
+
stroke: strokeProp,
|
|
35
|
+
...props
|
|
36
|
+
}) => {
|
|
37
|
+
const { colors } = useTheme();
|
|
38
|
+
const fill = String(
|
|
39
|
+
fillProp && fillProp in colors ? colors[fillProp] : fillProp
|
|
40
|
+
);
|
|
41
|
+
const stroke = strokeProp && strokeProp in colors ? String(colors[strokeProp]) : strokeProp ? String(strokeProp) : void 0;
|
|
42
|
+
return /* @__PURE__ */ jsxs(
|
|
43
|
+
"svg",
|
|
44
|
+
{
|
|
45
|
+
width: "32",
|
|
46
|
+
height: "32",
|
|
47
|
+
viewBox: "0 0 32 32",
|
|
48
|
+
fill,
|
|
49
|
+
stroke,
|
|
50
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
51
|
+
...props,
|
|
52
|
+
children: [
|
|
53
|
+
/* @__PURE__ */ jsx(
|
|
54
|
+
"path",
|
|
55
|
+
{
|
|
56
|
+
d: "M28.414 9.171L22.829 3.585C22.6433 3.3992 22.4228 3.25181 22.1801 3.15125C21.9373 3.05069 21.6772 2.99893 21.4145 2.99893C21.1518 2.99893 20.8917 3.05069 20.6489 3.15125C20.4062 3.25181 20.1857 3.3992 20 3.585L4.586 19C4.39938 19.185 4.25145 19.4053 4.15084 19.6481C4.05023 19.8909 3.99896 20.1512 4 20.414V26C4 26.5304 4.21071 27.0391 4.58579 27.4142C4.96086 27.7893 5.46957 28 6 28H11.586C11.8488 28.001 12.1091 27.9498 12.3519 27.8492C12.5947 27.7486 12.815 27.6006 13 27.414L28.414 12C28.5998 11.8143 28.7472 11.5938 28.8478 11.3511C28.9483 11.1084 29.0001 10.8482 29.0001 10.5855C29.0001 10.3228 28.9483 10.0627 28.8478 9.81995C28.7472 9.57725 28.5998 9.35673 28.414 9.171ZM24 13.585L18.414 8L21.414 5L27 10.585L24 13.585Z",
|
|
57
|
+
fill: "currentColor"
|
|
58
|
+
}
|
|
59
|
+
),
|
|
60
|
+
/* @__PURE__ */ jsx(
|
|
61
|
+
"path",
|
|
62
|
+
{
|
|
63
|
+
d: "M4 12H2C1.73478 12 1.48043 11.8946 1.29289 11.7071C1.10536 11.5196 1 11.2652 1 11C1 10.7348 1.10536 10.4804 1.29289 10.2929C1.48043 10.1054 1.73478 10 2 10H4V8C4 7.73478 4.10536 7.48043 4.29289 7.29289C4.48043 7.10536 4.73478 7 5 7C5.26522 7 5.51957 7.10536 5.70711 7.29289C5.89464 7.48043 6 7.73478 6 8V10H8C8.26522 10 8.51957 10.1054 8.70711 10.2929C8.89464 10.4804 9 10.7348 9 11C9 11.2652 8.89464 11.5196 8.70711 11.7071C8.51957 11.8946 8.26522 12 8 12H6V14C6 14.2652 5.89464 14.5196 5.70711 14.7071C5.51957 14.8946 5.26522 15 5 15C4.73478 15 4.48043 14.8946 4.29289 14.7071C4.10536 14.5196 4 14.2652 4 14V12Z",
|
|
64
|
+
fill: "currentColor"
|
|
65
|
+
}
|
|
66
|
+
),
|
|
67
|
+
/* @__PURE__ */ jsx(
|
|
68
|
+
"path",
|
|
69
|
+
{
|
|
70
|
+
d: "M9 2H10C10.2652 2 10.5196 2.10536 10.7071 2.29289C10.8946 2.48043 11 2.73478 11 3C11 3.26522 10.8946 3.51957 10.7071 3.70711C10.5196 3.89464 10.2652 4 10 4H9V5C9 5.26522 8.89464 5.51957 8.70711 5.70711C8.51957 5.89464 8.26522 6 8 6C7.73478 6 7.48043 5.89464 7.29289 5.70711C7.10536 5.51957 7 5.26522 7 5V4H6C5.73478 4 5.48043 3.89464 5.29289 3.70711C5.10536 3.51957 5 3.26522 5 3C5 2.73478 5.10536 2.48043 5.29289 2.29289C5.48043 2.10536 5.73478 2 6 2H7V1C7 0.734784 7.10536 0.48043 7.29289 0.292893C7.48043 0.105357 7.73478 0 8 0C8.26522 0 8.51957 0.105357 8.70711 0.292893C8.89464 0.48043 9 0.734784 9 1V2Z",
|
|
71
|
+
fill: "currentColor"
|
|
72
|
+
}
|
|
73
|
+
)
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
const getLocaleFromUrl = () => {
|
|
79
|
+
try {
|
|
80
|
+
const params = new URL(window.location.href).searchParams;
|
|
81
|
+
return params.get("plugins[i18n][locale]") || "en";
|
|
82
|
+
} catch {
|
|
83
|
+
return "en";
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
const getTranslation = (id) => `${PLUGIN_ID}.${id}`;
|
|
87
|
+
const LLMButton = () => {
|
|
88
|
+
const [loading, setIsLoading] = useState(false);
|
|
89
|
+
const [success, setSuccess] = useState(false);
|
|
90
|
+
const [error, setError] = useState(false);
|
|
91
|
+
const { formatMessage } = useIntl();
|
|
92
|
+
const { form, contentType, components } = unstable_useContentManagerContext();
|
|
93
|
+
const { post } = useFetchClient();
|
|
94
|
+
const { toggleNotification } = useNotification();
|
|
95
|
+
const isI18nEnabled = contentType?.pluginOptions?.i18n?.localized || false;
|
|
96
|
+
const { values, onChange } = form;
|
|
97
|
+
if (!isI18nEnabled) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
const currentLocale = getLocaleFromUrl();
|
|
101
|
+
const getButtonState = () => {
|
|
102
|
+
if (loading) {
|
|
103
|
+
return {
|
|
104
|
+
variant: "secondary",
|
|
105
|
+
icon: Magic,
|
|
106
|
+
loading: true,
|
|
107
|
+
disabled: true,
|
|
108
|
+
tooltip: formatMessage({
|
|
109
|
+
id: getTranslation("button.tooltip.loading"),
|
|
110
|
+
defaultMessage: "Content is being translated"
|
|
111
|
+
}),
|
|
112
|
+
title: formatMessage({
|
|
113
|
+
id: getTranslation("button.label.loading"),
|
|
114
|
+
defaultMessage: "Translating content..."
|
|
115
|
+
})
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
if (success) {
|
|
119
|
+
return {
|
|
120
|
+
variant: "success",
|
|
121
|
+
icon: CheckCircle,
|
|
122
|
+
loading: false,
|
|
123
|
+
disabled: true,
|
|
124
|
+
tooltip: formatMessage({
|
|
125
|
+
id: getTranslation("button.tooltip.success"),
|
|
126
|
+
defaultMessage: "Content has been translated successfully"
|
|
127
|
+
}),
|
|
128
|
+
title: formatMessage({
|
|
129
|
+
id: getTranslation("button.label.success"),
|
|
130
|
+
defaultMessage: "Translation completed"
|
|
131
|
+
})
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
if (error) {
|
|
135
|
+
return {
|
|
136
|
+
variant: "danger",
|
|
137
|
+
icon: WarningCircle,
|
|
138
|
+
loading: false,
|
|
139
|
+
disabled: false,
|
|
140
|
+
tooltip: formatMessage({
|
|
141
|
+
id: getTranslation("button.tooltip.error"),
|
|
142
|
+
defaultMessage: "Translation failed. Click to try again"
|
|
143
|
+
}),
|
|
144
|
+
title: formatMessage({
|
|
145
|
+
id: getTranslation("button.label.error"),
|
|
146
|
+
defaultMessage: "Translation failed"
|
|
147
|
+
})
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
variant: "secondary",
|
|
152
|
+
icon: Magic,
|
|
153
|
+
loading: false,
|
|
154
|
+
disabled: false,
|
|
155
|
+
tooltip: formatMessage({
|
|
156
|
+
id: getTranslation("button.tooltip.idle"),
|
|
157
|
+
defaultMessage: "Translate content using AI"
|
|
158
|
+
}),
|
|
159
|
+
title: formatMessage({
|
|
160
|
+
id: getTranslation("button.label.idle"),
|
|
161
|
+
defaultMessage: "Translate with AI"
|
|
162
|
+
})
|
|
163
|
+
};
|
|
164
|
+
};
|
|
165
|
+
const resetState = () => {
|
|
166
|
+
setSuccess(false);
|
|
167
|
+
setError(false);
|
|
168
|
+
setIsLoading(false);
|
|
169
|
+
};
|
|
170
|
+
const handleLLMRequest = async () => {
|
|
171
|
+
try {
|
|
172
|
+
resetState();
|
|
173
|
+
setIsLoading(true);
|
|
174
|
+
const dataToSend = {
|
|
175
|
+
contentType,
|
|
176
|
+
fields: values,
|
|
177
|
+
components,
|
|
178
|
+
targetLanguage: currentLocale
|
|
179
|
+
};
|
|
180
|
+
const { data: response } = await post(`/${PLUGIN_ID}/generate`, {
|
|
181
|
+
...dataToSend
|
|
182
|
+
});
|
|
183
|
+
if (!response.meta.ok) {
|
|
184
|
+
throw new Error(response.meta.message);
|
|
185
|
+
}
|
|
186
|
+
if (response.data) {
|
|
187
|
+
Object.entries(response.data).forEach(([key, value]) => {
|
|
188
|
+
if (values[key] !== void 0) {
|
|
189
|
+
onChange({ target: { name: key, value } });
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
setSuccess(true);
|
|
193
|
+
toggleNotification({
|
|
194
|
+
type: "success",
|
|
195
|
+
message: formatMessage({
|
|
196
|
+
id: getTranslation("notification.success"),
|
|
197
|
+
defaultMessage: "Translation completed successfully"
|
|
198
|
+
})
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
} catch (error2) {
|
|
202
|
+
setError(true);
|
|
203
|
+
toggleNotification({
|
|
204
|
+
type: "danger",
|
|
205
|
+
message: formatMessage(
|
|
206
|
+
{
|
|
207
|
+
id: `${getTranslation("notification.error")}: ${error2}`,
|
|
208
|
+
defaultMessage: `Error during translation: ${error2}`
|
|
209
|
+
},
|
|
210
|
+
{ error: error2.message }
|
|
211
|
+
)
|
|
212
|
+
});
|
|
213
|
+
} finally {
|
|
214
|
+
setIsLoading(false);
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
const buttonState = getButtonState();
|
|
218
|
+
return /* @__PURE__ */ jsx(
|
|
219
|
+
Button,
|
|
220
|
+
{
|
|
221
|
+
variant: buttonState.variant,
|
|
222
|
+
startIcon: /* @__PURE__ */ jsx(buttonState.icon, {}),
|
|
223
|
+
fullWidth: true,
|
|
224
|
+
loading: buttonState.loading,
|
|
225
|
+
onClick: handleLLMRequest,
|
|
226
|
+
disabled: !isI18nEnabled || buttonState.disabled,
|
|
227
|
+
title: buttonState.tooltip,
|
|
228
|
+
children: buttonState.title
|
|
229
|
+
}
|
|
230
|
+
);
|
|
231
|
+
};
|
|
232
|
+
const index = {
|
|
233
|
+
register(app) {
|
|
234
|
+
app.addMenuLink({
|
|
235
|
+
to: `plugins/${PLUGIN_ID}`,
|
|
236
|
+
icon: PluginIcon,
|
|
237
|
+
intlLabel: {
|
|
238
|
+
id: `${PLUGIN_ID}.plugin.name`,
|
|
239
|
+
defaultMessage: PLUGIN_ID
|
|
240
|
+
},
|
|
241
|
+
Component: async () => {
|
|
242
|
+
const { App } = await import("./App-Boy_i56G.mjs");
|
|
243
|
+
return App;
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
app.registerPlugin({
|
|
247
|
+
id: PLUGIN_ID,
|
|
248
|
+
initializer: Initializer,
|
|
249
|
+
isReady: false,
|
|
250
|
+
name: PLUGIN_ID
|
|
251
|
+
});
|
|
252
|
+
},
|
|
253
|
+
bootstrap(app) {
|
|
254
|
+
app.getPlugin("content-manager").injectComponent("editView", "right-links", {
|
|
255
|
+
name: "llm-assistant-button",
|
|
256
|
+
Component: LLMButton
|
|
257
|
+
});
|
|
258
|
+
},
|
|
259
|
+
async registerTrads({ locales }) {
|
|
260
|
+
return Promise.all(
|
|
261
|
+
locales.map(async (locale) => {
|
|
262
|
+
try {
|
|
263
|
+
const { default: data } = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/de.json": () => import("./de-D3VSS3Mq.mjs"), "./translations/en.json": () => import("./en-BnBUQVgF.mjs") }), `./translations/${locale}.json`, 3);
|
|
264
|
+
return { data, locale };
|
|
265
|
+
} catch {
|
|
266
|
+
return { data: {}, locale };
|
|
267
|
+
}
|
|
268
|
+
})
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
export {
|
|
273
|
+
PluginIcon as P,
|
|
274
|
+
PLUGIN_ID as a,
|
|
275
|
+
getTranslation as g,
|
|
276
|
+
index as i
|
|
277
|
+
};
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const react = require("react");
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const styledComponents = require("styled-components");
|
|
5
|
+
const reactIntl = require("react-intl");
|
|
6
|
+
const icons = require("@strapi/icons");
|
|
7
|
+
const designSystem = require("@strapi/design-system");
|
|
8
|
+
const admin = require("@strapi/strapi/admin");
|
|
9
|
+
const __variableDynamicImportRuntimeHelper = (glob, path, segs) => {
|
|
10
|
+
const v = glob[path];
|
|
11
|
+
if (v) {
|
|
12
|
+
return typeof v === "function" ? v() : Promise.resolve(v);
|
|
13
|
+
}
|
|
14
|
+
return new Promise((_, reject) => {
|
|
15
|
+
(typeof queueMicrotask === "function" ? queueMicrotask : setTimeout)(
|
|
16
|
+
reject.bind(
|
|
17
|
+
null,
|
|
18
|
+
new Error(
|
|
19
|
+
"Unknown variable dynamic import: " + path + (path.split("/").length !== segs ? ". Note that variables only represent file names one level deep." : "")
|
|
20
|
+
)
|
|
21
|
+
)
|
|
22
|
+
);
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
const PLUGIN_ID = "strapi-llm-translator";
|
|
26
|
+
const Initializer = ({ setPlugin }) => {
|
|
27
|
+
const ref = react.useRef(setPlugin);
|
|
28
|
+
react.useEffect(() => {
|
|
29
|
+
ref.current(PLUGIN_ID);
|
|
30
|
+
}, []);
|
|
31
|
+
return null;
|
|
32
|
+
};
|
|
33
|
+
const PluginIcon = ({
|
|
34
|
+
fill: fillProp = "currentColor",
|
|
35
|
+
stroke: strokeProp,
|
|
36
|
+
...props
|
|
37
|
+
}) => {
|
|
38
|
+
const { colors } = styledComponents.useTheme();
|
|
39
|
+
const fill = String(
|
|
40
|
+
fillProp && fillProp in colors ? colors[fillProp] : fillProp
|
|
41
|
+
);
|
|
42
|
+
const stroke = strokeProp && strokeProp in colors ? String(colors[strokeProp]) : strokeProp ? String(strokeProp) : void 0;
|
|
43
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
44
|
+
"svg",
|
|
45
|
+
{
|
|
46
|
+
width: "32",
|
|
47
|
+
height: "32",
|
|
48
|
+
viewBox: "0 0 32 32",
|
|
49
|
+
fill,
|
|
50
|
+
stroke,
|
|
51
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
52
|
+
...props,
|
|
53
|
+
children: [
|
|
54
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
55
|
+
"path",
|
|
56
|
+
{
|
|
57
|
+
d: "M28.414 9.171L22.829 3.585C22.6433 3.3992 22.4228 3.25181 22.1801 3.15125C21.9373 3.05069 21.6772 2.99893 21.4145 2.99893C21.1518 2.99893 20.8917 3.05069 20.6489 3.15125C20.4062 3.25181 20.1857 3.3992 20 3.585L4.586 19C4.39938 19.185 4.25145 19.4053 4.15084 19.6481C4.05023 19.8909 3.99896 20.1512 4 20.414V26C4 26.5304 4.21071 27.0391 4.58579 27.4142C4.96086 27.7893 5.46957 28 6 28H11.586C11.8488 28.001 12.1091 27.9498 12.3519 27.8492C12.5947 27.7486 12.815 27.6006 13 27.414L28.414 12C28.5998 11.8143 28.7472 11.5938 28.8478 11.3511C28.9483 11.1084 29.0001 10.8482 29.0001 10.5855C29.0001 10.3228 28.9483 10.0627 28.8478 9.81995C28.7472 9.57725 28.5998 9.35673 28.414 9.171ZM24 13.585L18.414 8L21.414 5L27 10.585L24 13.585Z",
|
|
58
|
+
fill: "currentColor"
|
|
59
|
+
}
|
|
60
|
+
),
|
|
61
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
62
|
+
"path",
|
|
63
|
+
{
|
|
64
|
+
d: "M4 12H2C1.73478 12 1.48043 11.8946 1.29289 11.7071C1.10536 11.5196 1 11.2652 1 11C1 10.7348 1.10536 10.4804 1.29289 10.2929C1.48043 10.1054 1.73478 10 2 10H4V8C4 7.73478 4.10536 7.48043 4.29289 7.29289C4.48043 7.10536 4.73478 7 5 7C5.26522 7 5.51957 7.10536 5.70711 7.29289C5.89464 7.48043 6 7.73478 6 8V10H8C8.26522 10 8.51957 10.1054 8.70711 10.2929C8.89464 10.4804 9 10.7348 9 11C9 11.2652 8.89464 11.5196 8.70711 11.7071C8.51957 11.8946 8.26522 12 8 12H6V14C6 14.2652 5.89464 14.5196 5.70711 14.7071C5.51957 14.8946 5.26522 15 5 15C4.73478 15 4.48043 14.8946 4.29289 14.7071C4.10536 14.5196 4 14.2652 4 14V12Z",
|
|
65
|
+
fill: "currentColor"
|
|
66
|
+
}
|
|
67
|
+
),
|
|
68
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
69
|
+
"path",
|
|
70
|
+
{
|
|
71
|
+
d: "M9 2H10C10.2652 2 10.5196 2.10536 10.7071 2.29289C10.8946 2.48043 11 2.73478 11 3C11 3.26522 10.8946 3.51957 10.7071 3.70711C10.5196 3.89464 10.2652 4 10 4H9V5C9 5.26522 8.89464 5.51957 8.70711 5.70711C8.51957 5.89464 8.26522 6 8 6C7.73478 6 7.48043 5.89464 7.29289 5.70711C7.10536 5.51957 7 5.26522 7 5V4H6C5.73478 4 5.48043 3.89464 5.29289 3.70711C5.10536 3.51957 5 3.26522 5 3C5 2.73478 5.10536 2.48043 5.29289 2.29289C5.48043 2.10536 5.73478 2 6 2H7V1C7 0.734784 7.10536 0.48043 7.29289 0.292893C7.48043 0.105357 7.73478 0 8 0C8.26522 0 8.51957 0.105357 8.70711 0.292893C8.89464 0.48043 9 0.734784 9 1V2Z",
|
|
72
|
+
fill: "currentColor"
|
|
73
|
+
}
|
|
74
|
+
)
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
const getLocaleFromUrl = () => {
|
|
80
|
+
try {
|
|
81
|
+
const params = new URL(window.location.href).searchParams;
|
|
82
|
+
return params.get("plugins[i18n][locale]") || "en";
|
|
83
|
+
} catch {
|
|
84
|
+
return "en";
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
const getTranslation = (id) => `${PLUGIN_ID}.${id}`;
|
|
88
|
+
const LLMButton = () => {
|
|
89
|
+
const [loading, setIsLoading] = react.useState(false);
|
|
90
|
+
const [success, setSuccess] = react.useState(false);
|
|
91
|
+
const [error, setError] = react.useState(false);
|
|
92
|
+
const { formatMessage } = reactIntl.useIntl();
|
|
93
|
+
const { form, contentType, components } = admin.unstable_useContentManagerContext();
|
|
94
|
+
const { post } = admin.useFetchClient();
|
|
95
|
+
const { toggleNotification } = admin.useNotification();
|
|
96
|
+
const isI18nEnabled = contentType?.pluginOptions?.i18n?.localized || false;
|
|
97
|
+
const { values, onChange } = form;
|
|
98
|
+
if (!isI18nEnabled) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
const currentLocale = getLocaleFromUrl();
|
|
102
|
+
const getButtonState = () => {
|
|
103
|
+
if (loading) {
|
|
104
|
+
return {
|
|
105
|
+
variant: "secondary",
|
|
106
|
+
icon: icons.Magic,
|
|
107
|
+
loading: true,
|
|
108
|
+
disabled: true,
|
|
109
|
+
tooltip: formatMessage({
|
|
110
|
+
id: getTranslation("button.tooltip.loading"),
|
|
111
|
+
defaultMessage: "Content is being translated"
|
|
112
|
+
}),
|
|
113
|
+
title: formatMessage({
|
|
114
|
+
id: getTranslation("button.label.loading"),
|
|
115
|
+
defaultMessage: "Translating content..."
|
|
116
|
+
})
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
if (success) {
|
|
120
|
+
return {
|
|
121
|
+
variant: "success",
|
|
122
|
+
icon: icons.CheckCircle,
|
|
123
|
+
loading: false,
|
|
124
|
+
disabled: true,
|
|
125
|
+
tooltip: formatMessage({
|
|
126
|
+
id: getTranslation("button.tooltip.success"),
|
|
127
|
+
defaultMessage: "Content has been translated successfully"
|
|
128
|
+
}),
|
|
129
|
+
title: formatMessage({
|
|
130
|
+
id: getTranslation("button.label.success"),
|
|
131
|
+
defaultMessage: "Translation completed"
|
|
132
|
+
})
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if (error) {
|
|
136
|
+
return {
|
|
137
|
+
variant: "danger",
|
|
138
|
+
icon: icons.WarningCircle,
|
|
139
|
+
loading: false,
|
|
140
|
+
disabled: false,
|
|
141
|
+
tooltip: formatMessage({
|
|
142
|
+
id: getTranslation("button.tooltip.error"),
|
|
143
|
+
defaultMessage: "Translation failed. Click to try again"
|
|
144
|
+
}),
|
|
145
|
+
title: formatMessage({
|
|
146
|
+
id: getTranslation("button.label.error"),
|
|
147
|
+
defaultMessage: "Translation failed"
|
|
148
|
+
})
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
variant: "secondary",
|
|
153
|
+
icon: icons.Magic,
|
|
154
|
+
loading: false,
|
|
155
|
+
disabled: false,
|
|
156
|
+
tooltip: formatMessage({
|
|
157
|
+
id: getTranslation("button.tooltip.idle"),
|
|
158
|
+
defaultMessage: "Translate content using AI"
|
|
159
|
+
}),
|
|
160
|
+
title: formatMessage({
|
|
161
|
+
id: getTranslation("button.label.idle"),
|
|
162
|
+
defaultMessage: "Translate with AI"
|
|
163
|
+
})
|
|
164
|
+
};
|
|
165
|
+
};
|
|
166
|
+
const resetState = () => {
|
|
167
|
+
setSuccess(false);
|
|
168
|
+
setError(false);
|
|
169
|
+
setIsLoading(false);
|
|
170
|
+
};
|
|
171
|
+
const handleLLMRequest = async () => {
|
|
172
|
+
try {
|
|
173
|
+
resetState();
|
|
174
|
+
setIsLoading(true);
|
|
175
|
+
const dataToSend = {
|
|
176
|
+
contentType,
|
|
177
|
+
fields: values,
|
|
178
|
+
components,
|
|
179
|
+
targetLanguage: currentLocale
|
|
180
|
+
};
|
|
181
|
+
const { data: response } = await post(`/${PLUGIN_ID}/generate`, {
|
|
182
|
+
...dataToSend
|
|
183
|
+
});
|
|
184
|
+
if (!response.meta.ok) {
|
|
185
|
+
throw new Error(response.meta.message);
|
|
186
|
+
}
|
|
187
|
+
if (response.data) {
|
|
188
|
+
Object.entries(response.data).forEach(([key, value]) => {
|
|
189
|
+
if (values[key] !== void 0) {
|
|
190
|
+
onChange({ target: { name: key, value } });
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
setSuccess(true);
|
|
194
|
+
toggleNotification({
|
|
195
|
+
type: "success",
|
|
196
|
+
message: formatMessage({
|
|
197
|
+
id: getTranslation("notification.success"),
|
|
198
|
+
defaultMessage: "Translation completed successfully"
|
|
199
|
+
})
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
} catch (error2) {
|
|
203
|
+
setError(true);
|
|
204
|
+
toggleNotification({
|
|
205
|
+
type: "danger",
|
|
206
|
+
message: formatMessage(
|
|
207
|
+
{
|
|
208
|
+
id: `${getTranslation("notification.error")}: ${error2}`,
|
|
209
|
+
defaultMessage: `Error during translation: ${error2}`
|
|
210
|
+
},
|
|
211
|
+
{ error: error2.message }
|
|
212
|
+
)
|
|
213
|
+
});
|
|
214
|
+
} finally {
|
|
215
|
+
setIsLoading(false);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
const buttonState = getButtonState();
|
|
219
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
220
|
+
designSystem.Button,
|
|
221
|
+
{
|
|
222
|
+
variant: buttonState.variant,
|
|
223
|
+
startIcon: /* @__PURE__ */ jsxRuntime.jsx(buttonState.icon, {}),
|
|
224
|
+
fullWidth: true,
|
|
225
|
+
loading: buttonState.loading,
|
|
226
|
+
onClick: handleLLMRequest,
|
|
227
|
+
disabled: !isI18nEnabled || buttonState.disabled,
|
|
228
|
+
title: buttonState.tooltip,
|
|
229
|
+
children: buttonState.title
|
|
230
|
+
}
|
|
231
|
+
);
|
|
232
|
+
};
|
|
233
|
+
const index = {
|
|
234
|
+
register(app) {
|
|
235
|
+
app.addMenuLink({
|
|
236
|
+
to: `plugins/${PLUGIN_ID}`,
|
|
237
|
+
icon: PluginIcon,
|
|
238
|
+
intlLabel: {
|
|
239
|
+
id: `${PLUGIN_ID}.plugin.name`,
|
|
240
|
+
defaultMessage: PLUGIN_ID
|
|
241
|
+
},
|
|
242
|
+
Component: async () => {
|
|
243
|
+
const { App } = await Promise.resolve().then(() => require("./App-COSMdnpL.js"));
|
|
244
|
+
return App;
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
app.registerPlugin({
|
|
248
|
+
id: PLUGIN_ID,
|
|
249
|
+
initializer: Initializer,
|
|
250
|
+
isReady: false,
|
|
251
|
+
name: PLUGIN_ID
|
|
252
|
+
});
|
|
253
|
+
},
|
|
254
|
+
bootstrap(app) {
|
|
255
|
+
app.getPlugin("content-manager").injectComponent("editView", "right-links", {
|
|
256
|
+
name: "llm-assistant-button",
|
|
257
|
+
Component: LLMButton
|
|
258
|
+
});
|
|
259
|
+
},
|
|
260
|
+
async registerTrads({ locales }) {
|
|
261
|
+
return Promise.all(
|
|
262
|
+
locales.map(async (locale) => {
|
|
263
|
+
try {
|
|
264
|
+
const { default: data } = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/de.json": () => Promise.resolve().then(() => require("./de-CvAR4QXO.js")), "./translations/en.json": () => Promise.resolve().then(() => require("./en-BjVvIBv8.js")) }), `./translations/${locale}.json`, 3);
|
|
265
|
+
return { data, locale };
|
|
266
|
+
} catch {
|
|
267
|
+
return { data: {}, locale };
|
|
268
|
+
}
|
|
269
|
+
})
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
exports.PLUGIN_ID = PLUGIN_ID;
|
|
274
|
+
exports.PluginIcon = PluginIcon;
|
|
275
|
+
exports.getTranslation = getTranslation;
|
|
276
|
+
exports.index = index;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { SVGProps } from 'react';
|
|
2
|
+
import { DefaultTheme } from 'styled-components';
|
|
3
|
+
declare module 'styled-components' {
|
|
4
|
+
interface DefaultTheme {
|
|
5
|
+
colors: {
|
|
6
|
+
[key: string]: string;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
interface IconProps extends Omit<SVGProps<SVGSVGElement>, 'fill' | 'stroke'> {
|
|
11
|
+
/**
|
|
12
|
+
* @default "currentColor"
|
|
13
|
+
*/
|
|
14
|
+
fill?: keyof DefaultTheme['colors'] | (string & {});
|
|
15
|
+
stroke?: keyof DefaultTheme['colors'] | (string & {});
|
|
16
|
+
}
|
|
17
|
+
declare const PluginIcon: ({ fill: fillProp, stroke: strokeProp, ...props }: IconProps) => import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
export { PluginIcon };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const PLUGIN_ID = "strapi-llm-translator";
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const DEFAULT_SYSTEM_PROMPT = "You are a professional translator. Your task is to translate the provided content accurately while preserving the original meaning and tone.";
|
|
2
|
+
export declare const DEFAULT_LLM_TEMPERATURE = 0.3;
|
|
3
|
+
export declare const MAX_LLM_TEMPERATURE = 2;
|
|
4
|
+
export declare const MIN_LLM_TEMPERATURE = 0.1;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const getLocaleFromUrl: () => string;
|