@alpaca-editor/core 1.0.4017 → 1.0.4026

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.
Files changed (173) hide show
  1. package/dist/components/SimpleLanguageSelector.d.ts +2 -1
  2. package/dist/components/SimpleLanguageSelector.js +9 -2
  3. package/dist/components/SimpleLanguageSelector.js.map +1 -1
  4. package/dist/components/ui/input.js +1 -1
  5. package/dist/components/ui/input.js.map +1 -1
  6. package/dist/components/ui/tooltip.d.ts +3 -1
  7. package/dist/components/ui/tooltip.js +2 -2
  8. package/dist/components/ui/tooltip.js.map +1 -1
  9. package/dist/config/config.js +4 -0
  10. package/dist/config/config.js.map +1 -1
  11. package/dist/config/types.d.ts +2 -0
  12. package/dist/editor/ContentTree.js +1 -1
  13. package/dist/editor/ContentTree.js.map +1 -1
  14. package/dist/editor/ContextMenu.js +26 -0
  15. package/dist/editor/ContextMenu.js.map +1 -1
  16. package/dist/editor/FieldHistory.js +1 -1
  17. package/dist/editor/FieldHistory.js.map +1 -1
  18. package/dist/editor/FieldListField.js +2 -2
  19. package/dist/editor/FieldListField.js.map +1 -1
  20. package/dist/editor/Terminal.js +3 -1
  21. package/dist/editor/Terminal.js.map +1 -1
  22. package/dist/editor/ai/Agents.js +19 -10
  23. package/dist/editor/ai/Agents.js.map +1 -1
  24. package/dist/editor/ai/AiResponseMessage.js +63 -5
  25. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  26. package/dist/editor/ai/AiTerminal.js +7 -7
  27. package/dist/editor/ai/AiTerminal.js.map +1 -1
  28. package/dist/editor/ai/AiToolCall.js +3 -3
  29. package/dist/editor/ai/AiToolCall.js.map +1 -1
  30. package/dist/editor/client/EditorClient.js +5 -1
  31. package/dist/editor/client/EditorClient.js.map +1 -1
  32. package/dist/editor/client/editContext.d.ts +2 -0
  33. package/dist/editor/client/editContext.js.map +1 -1
  34. package/dist/editor/client/operations.d.ts +1 -0
  35. package/dist/editor/client/operations.js +7 -0
  36. package/dist/editor/client/operations.js.map +1 -1
  37. package/dist/editor/commands/componentCommands.js +1 -1
  38. package/dist/editor/commands/componentCommands.js.map +1 -1
  39. package/dist/editor/field-types/ImageFieldEditor.js +1 -1
  40. package/dist/editor/field-types/ImageFieldEditor.js.map +1 -1
  41. package/dist/editor/field-types/MultiLineText.js +1 -1
  42. package/dist/editor/field-types/MultiLineText.js.map +1 -1
  43. package/dist/editor/field-types/PictureFieldEditor.js +1 -1
  44. package/dist/editor/field-types/PictureFieldEditor.js.map +1 -1
  45. package/dist/editor/field-types/RawEditor.js +1 -1
  46. package/dist/editor/field-types/RawEditor.js.map +1 -1
  47. package/dist/editor/field-types/RichTextEditorComponent.js +16 -17
  48. package/dist/editor/field-types/RichTextEditorComponent.js.map +1 -1
  49. package/dist/editor/field-types/SingleLineText.js +1 -1
  50. package/dist/editor/field-types/SingleLineText.js.map +1 -1
  51. package/dist/editor/field-types/richtext/components/SimpleDropdown.d.ts +18 -0
  52. package/dist/editor/field-types/richtext/components/SimpleDropdown.js +71 -0
  53. package/dist/editor/field-types/richtext/components/SimpleDropdown.js.map +1 -0
  54. package/dist/editor/field-types/richtext/components/SimpleRichTextEditor.d.ts +5 -0
  55. package/dist/editor/field-types/richtext/components/SimpleRichTextEditor.js +359 -0
  56. package/dist/editor/field-types/richtext/components/SimpleRichTextEditor.js.map +1 -0
  57. package/dist/editor/field-types/richtext/components/SimpleToolbar.d.ts +16 -0
  58. package/dist/editor/field-types/richtext/components/SimpleToolbar.js +181 -0
  59. package/dist/editor/field-types/richtext/components/SimpleToolbar.js.map +1 -0
  60. package/dist/editor/field-types/richtext/components/SimpleToolbarButton.d.ts +9 -0
  61. package/dist/editor/field-types/richtext/components/SimpleToolbarButton.js +14 -0
  62. package/dist/editor/field-types/richtext/components/SimpleToolbarButton.js.map +1 -0
  63. package/dist/editor/field-types/richtext/contextMenuFactory.d.ts +4 -0
  64. package/dist/editor/field-types/richtext/contextMenuFactory.js +193 -0
  65. package/dist/editor/field-types/richtext/contextMenuFactory.js.map +1 -0
  66. package/dist/editor/field-types/richtext/index.d.ts +6 -5
  67. package/dist/editor/field-types/richtext/index.js +6 -5
  68. package/dist/editor/field-types/richtext/index.js.map +1 -1
  69. package/dist/editor/field-types/richtext/types.d.ts +16 -16
  70. package/dist/editor/field-types/richtext/types.js +84 -84
  71. package/dist/editor/field-types/richtext/types.js.map +1 -1
  72. package/dist/editor/page-editor-chrome/CommentHighlighting.js +1 -1
  73. package/dist/editor/page-editor-chrome/CommentHighlighting.js.map +1 -1
  74. package/dist/editor/page-editor-chrome/FrameMenu.js +5 -5
  75. package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
  76. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +1 -1
  77. package/dist/editor/page-viewer/PageViewerFrame.js +3 -2
  78. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  79. package/dist/editor/services/agentService.d.ts +14 -4
  80. package/dist/editor/services/agentService.js.map +1 -1
  81. package/dist/editor/services/aiService.js +1 -0
  82. package/dist/editor/services/aiService.js.map +1 -1
  83. package/dist/page-wizard/PageWizard.d.ts +2 -0
  84. package/dist/page-wizard/PageWizard.js +6 -13
  85. package/dist/page-wizard/PageWizard.js.map +1 -1
  86. package/dist/page-wizard/WizardSteps.js +3 -1
  87. package/dist/page-wizard/WizardSteps.js.map +1 -1
  88. package/dist/page-wizard/service.d.ts +1 -0
  89. package/dist/page-wizard/service.js +7 -0
  90. package/dist/page-wizard/service.js.map +1 -1
  91. package/dist/page-wizard/steps/ContentStep.js +210 -33
  92. package/dist/page-wizard/steps/ContentStep.js.map +1 -1
  93. package/dist/page-wizard/steps/FindItemsStep.js +11 -3
  94. package/dist/page-wizard/steps/FindItemsStep.js.map +1 -1
  95. package/dist/page-wizard/steps/LayoutStep.js +1 -1
  96. package/dist/page-wizard/steps/LayoutStep.js.map +1 -1
  97. package/dist/page-wizard/steps/MetaDataStep.js +1 -1
  98. package/dist/page-wizard/steps/MetaDataStep.js.map +1 -1
  99. package/dist/page-wizard/steps/SchottSelectImagesStep.d.ts +2 -0
  100. package/dist/page-wizard/steps/SchottSelectImagesStep.js +55 -0
  101. package/dist/page-wizard/steps/SchottSelectImagesStep.js.map +1 -0
  102. package/dist/page-wizard/steps/StructureStep.js +20 -5
  103. package/dist/page-wizard/steps/StructureStep.js.map +1 -1
  104. package/dist/page-wizard/steps/TranslateStep.d.ts +2 -0
  105. package/dist/page-wizard/steps/TranslateStep.js +413 -0
  106. package/dist/page-wizard/steps/TranslateStep.js.map +1 -0
  107. package/dist/page-wizard/utils/dataAccessor.d.ts +7 -0
  108. package/dist/page-wizard/utils/dataAccessor.js +76 -0
  109. package/dist/page-wizard/utils/dataAccessor.js.map +1 -1
  110. package/dist/revision.d.ts +2 -2
  111. package/dist/revision.js +2 -2
  112. package/dist/splash-screen/NewPage.js +5 -4
  113. package/dist/splash-screen/NewPage.js.map +1 -1
  114. package/dist/splash-screen/OpenPage.js +2 -1
  115. package/dist/splash-screen/OpenPage.js.map +1 -1
  116. package/dist/splash-screen/RecentPages.js +3 -1
  117. package/dist/splash-screen/RecentPages.js.map +1 -1
  118. package/dist/styles.css +57 -0
  119. package/package.json +5 -4
  120. package/src/components/SimpleLanguageSelector.tsx +11 -1
  121. package/src/components/ui/input.tsx +1 -1
  122. package/src/components/ui/tooltip.tsx +3 -2
  123. package/src/config/config.tsx +4 -0
  124. package/src/config/types.ts +6 -0
  125. package/src/editor/ContentTree.tsx +1 -1
  126. package/src/editor/ContextMenu.tsx +39 -0
  127. package/src/editor/FieldHistory.tsx +1 -1
  128. package/src/editor/FieldListField.tsx +2 -6
  129. package/src/editor/Terminal.tsx +5 -1
  130. package/src/editor/ai/Agents.tsx +27 -16
  131. package/src/editor/ai/AiResponseMessage.tsx +138 -23
  132. package/src/editor/ai/AiTerminal.tsx +43 -29
  133. package/src/editor/ai/AiToolCall.tsx +14 -4
  134. package/src/editor/client/EditorClient.tsx +9 -1
  135. package/src/editor/client/editContext.ts +2 -0
  136. package/src/editor/client/operations.ts +9 -0
  137. package/src/editor/commands/componentCommands.tsx +1 -1
  138. package/src/editor/field-types/ImageFieldEditor.tsx +1 -0
  139. package/src/editor/field-types/MultiLineText.tsx +1 -0
  140. package/src/editor/field-types/PictureFieldEditor.tsx +1 -0
  141. package/src/editor/field-types/RawEditor.tsx +1 -0
  142. package/src/editor/field-types/RichTextEditorComponent.tsx +27 -25
  143. package/src/editor/field-types/SingleLineText.tsx +1 -0
  144. package/src/editor/field-types/richtext/components/SimpleDropdown.tsx +165 -0
  145. package/src/editor/field-types/richtext/components/SimpleRichTextEditor.css +261 -0
  146. package/src/editor/field-types/richtext/components/SimpleRichTextEditor.tsx +505 -0
  147. package/src/editor/field-types/richtext/components/SimpleToolbar.tsx +362 -0
  148. package/src/editor/field-types/richtext/components/SimpleToolbarButton.tsx +36 -0
  149. package/src/editor/field-types/richtext/contextMenuFactory.tsx +264 -0
  150. package/src/editor/field-types/richtext/index.ts +6 -5
  151. package/src/editor/field-types/richtext/types.ts +168 -148
  152. package/src/editor/page-editor-chrome/CommentHighlighting.tsx +1 -1
  153. package/src/editor/page-editor-chrome/FrameMenu.tsx +16 -11
  154. package/src/editor/page-editor-chrome/PlaceholderDropZone.tsx +1 -1
  155. package/src/editor/page-viewer/PageViewerFrame.tsx +4 -3
  156. package/src/editor/services/agentService.ts +16 -5
  157. package/src/editor/services/aiService.ts +2 -0
  158. package/src/page-wizard/PageWizard.tsx +10 -13
  159. package/src/page-wizard/WizardSteps.tsx +3 -1
  160. package/src/page-wizard/service.ts +11 -0
  161. package/src/page-wizard/steps/ContentStep.tsx +376 -43
  162. package/src/page-wizard/steps/FindItemsStep.tsx +23 -3
  163. package/src/page-wizard/steps/LayoutStep.tsx +1 -1
  164. package/src/page-wizard/steps/MetaDataStep.tsx +1 -1
  165. package/src/page-wizard/steps/SchottSelectImagesStep.tsx +141 -0
  166. package/src/page-wizard/steps/StructureStep.tsx +40 -5
  167. package/src/page-wizard/steps/TranslateStep.tsx +772 -0
  168. package/src/page-wizard/utils/dataAccessor.ts +85 -0
  169. package/src/revision.ts +2 -2
  170. package/src/splash-screen/NewPage.tsx +18 -3
  171. package/src/splash-screen/OpenPage.tsx +14 -1
  172. package/src/splash-screen/RecentPages.tsx +4 -2
  173. package/tsconfig.build.json +1 -0
@@ -0,0 +1,772 @@
1
+ import { StepComponentProps } from "../../config/types";
2
+ import { useEffect, useState, useCallback } from "react";
3
+ import { useEditContext } from "../../editor/client/editContext";
4
+ import { ItemDescriptor, Language } from "../../editor/pageModel";
5
+ import { ActionButton } from "../../components/ActionButton";
6
+ import { Input } from "../../components/ui/input";
7
+ import { InputTextarea } from "primereact/inputtextarea";
8
+ import { executePrompt } from "../../editor/services/aiService";
9
+ import { createWizardAiContext } from "../service";
10
+ import { SimpleLanguageSelector } from "../../components/SimpleLanguageSelector";
11
+ import { getLanguagesAndVersions } from "../../editor/services/contentService";
12
+ import {
13
+ Languages,
14
+ Upload,
15
+ Wand2,
16
+ CheckCircle,
17
+ AlertCircle,
18
+ Sparkles,
19
+ RefreshCw,
20
+ } from "lucide-react";
21
+ import { WizardBox } from "../WizardBox";
22
+
23
+ interface TranslatableField {
24
+ fieldId: string;
25
+ fieldName: string;
26
+ value: string;
27
+ }
28
+
29
+ interface TranslatableFieldsResponse {
30
+ [itemId: string]: TranslatableField[];
31
+ }
32
+
33
+ interface TranslatedField {
34
+ fieldId: string;
35
+ translatedValue: string;
36
+ }
37
+
38
+ interface TranslatedFieldsResponse {
39
+ [itemId: string]: TranslatedField[];
40
+ }
41
+
42
+ export function TranslateStep({
43
+ step,
44
+ data,
45
+ setData,
46
+ setStepCompleted,
47
+ internalState,
48
+ setInternalState,
49
+ parentItem,
50
+ }: StepComponentProps) {
51
+ const editContext = useEditContext();
52
+ const [translatableFields, setTranslatableFields] =
53
+ useState<TranslatableFieldsResponse>({});
54
+ const [translatedFields, setTranslatedFields] =
55
+ useState<TranslatedFieldsResponse>({});
56
+ const [isLoading, setIsLoading] = useState(false);
57
+ const [isTranslating, setIsTranslating] = useState(false);
58
+ const [isDetectingLanguage, setIsDetectingLanguage] = useState(false);
59
+ const [targetLanguage, setTargetLanguage] = useState<Language | null>(
60
+ data.targetLanguage || null,
61
+ );
62
+ const [detectedLanguageName, setDetectedLanguageName] = useState<string>(
63
+ data.detectedLanguageName || "",
64
+ );
65
+ const [availableSourceLanguages, setAvailableSourceLanguages] = useState<
66
+ Language[]
67
+ >([]);
68
+ const [componentNames, setComponentNames] = useState<Record<string, string>>(
69
+ {},
70
+ );
71
+
72
+ // Get the current item from data (set by previous wizard steps)
73
+ const currentItem = data.currentItem || parentItem;
74
+
75
+ // Add source language state - initialize from current item's language
76
+ const [sourceLanguage, setSourceLanguage] = useState<string>(
77
+ data.sourceLanguage || currentItem?.language || "en",
78
+ );
79
+
80
+ // Get document content from previous wizard step
81
+ const documentContent = data.document;
82
+
83
+ const [isPublishing, setIsPublishing] = useState(false);
84
+ const [publishResults, setPublishResults] = useState<{
85
+ success: string[];
86
+ errors: string[];
87
+ }>({ success: [], errors: [] });
88
+
89
+ const fetchAvailableLanguages = useCallback(async () => {
90
+ if (!currentItem || !editContext) return;
91
+
92
+ try {
93
+ const response = await getLanguagesAndVersions({
94
+ id: currentItem.id,
95
+ language: currentItem.language,
96
+ version: currentItem.version,
97
+ });
98
+
99
+ if (response.data?.languages) {
100
+ // Filter to only languages that have versions (versions > 0)
101
+ const languagesWithVersions = response.data.languages
102
+ .filter((lang) => lang.versions > 0)
103
+ .map((lang) => ({
104
+ languageCode: lang.languageCode,
105
+ name: lang.name,
106
+ icon: lang.icon,
107
+ versions: lang.versions,
108
+ }));
109
+ setAvailableSourceLanguages(languagesWithVersions);
110
+ }
111
+ } catch (error) {
112
+ console.error("Error fetching available languages:", error);
113
+ // Fallback to all languages if there's an error
114
+ setAvailableSourceLanguages(editContext?.itemLanguages || []);
115
+ }
116
+ }, [currentItem, editContext]);
117
+
118
+ const fetchComponentNames = useCallback(
119
+ async (itemIds: string[]) => {
120
+ if (!editContext || itemIds.length === 0) return;
121
+
122
+ const names: Record<string, string> = {};
123
+
124
+ for (const itemId of itemIds) {
125
+ try {
126
+ const item = await editContext.itemsRepository.getItem({
127
+ id: itemId,
128
+ language: sourceLanguage,
129
+ version: 0,
130
+ });
131
+
132
+ if (item?.name) {
133
+ names[itemId] = item.name;
134
+ } else {
135
+ names[itemId] = itemId; // Fallback to ID if name not found
136
+ }
137
+ } catch (error) {
138
+ console.error(`Error fetching item ${itemId}:`, error);
139
+ names[itemId] = itemId; // Fallback to ID on error
140
+ }
141
+ }
142
+
143
+ setComponentNames(names);
144
+ },
145
+ [editContext, sourceLanguage],
146
+ );
147
+
148
+ const fetchTranslatableFields = useCallback(async () => {
149
+ if (!currentItem || !editContext) return;
150
+
151
+ setIsLoading(true);
152
+ try {
153
+ const response = await fetch(
154
+ "/alpaca/editor/page-wizard/getTranslatableFields",
155
+ {
156
+ method: "POST",
157
+ headers: {
158
+ "Content-Type": "application/json",
159
+ },
160
+ body: JSON.stringify({
161
+ id: currentItem.id,
162
+ language: sourceLanguage,
163
+ version: currentItem.version,
164
+ }),
165
+ },
166
+ );
167
+
168
+ if (response.ok) {
169
+ const fieldsData: TranslatableFieldsResponse = await response.json();
170
+ setTranslatableFields(fieldsData);
171
+
172
+ // Initialize translated fields structure
173
+ const initialTranslatedFields: TranslatedFieldsResponse = {};
174
+ Object.keys(fieldsData).forEach((itemId) => {
175
+ if (fieldsData[itemId]) {
176
+ initialTranslatedFields[itemId] = fieldsData[itemId].map(
177
+ (field) => ({
178
+ fieldId: field.fieldId,
179
+ translatedValue: "",
180
+ }),
181
+ );
182
+ }
183
+ });
184
+ setTranslatedFields(initialTranslatedFields);
185
+
186
+ // Fetch component names for all item IDs
187
+ const itemIds = Object.keys(fieldsData);
188
+ if (itemIds.length > 0) {
189
+ fetchComponentNames(itemIds);
190
+ }
191
+ } else {
192
+ console.error("Failed to fetch translatable fields");
193
+ }
194
+ } catch (error) {
195
+ console.error("Error fetching translatable fields:", error);
196
+ } finally {
197
+ setIsLoading(false);
198
+ }
199
+ }, [currentItem, editContext, sourceLanguage, fetchComponentNames]);
200
+
201
+ useEffect(() => {
202
+ fetchAvailableLanguages();
203
+ }, [fetchAvailableLanguages]);
204
+
205
+ useEffect(() => {
206
+ fetchTranslatableFields();
207
+ }, [fetchTranslatableFields, sourceLanguage]);
208
+
209
+ const detectLanguage = async () => {
210
+ if (!editContext) {
211
+ console.warn("Edit context not available for language detection");
212
+ return;
213
+ }
214
+
215
+ setIsDetectingLanguage(true);
216
+ try {
217
+ const prompt = `Analyze the following text and identify the language it is written in. Return only the language name in English (e.g., "Spanish", "French", "German", "Italian", etc.). Do not include any other text or explanation.
218
+
219
+ Text to analyze:
220
+ ${documentContent.substring(0, 1000)}`;
221
+
222
+ const abortController = new AbortController();
223
+
224
+ const result = await executePrompt(
225
+ [
226
+ {
227
+ content: prompt,
228
+ role: "user",
229
+ name: "user",
230
+ id: crypto.randomUUID(),
231
+ },
232
+ ],
233
+ { editContext, createAiContext: createWizardAiContext },
234
+ { model: "gpt-4o-mini" },
235
+ { signal: abortController.signal },
236
+ (response: any) => {
237
+ try {
238
+ const content =
239
+ response.content ||
240
+ response.messages?.[response.messages.length - 1]?.content;
241
+ if (content) {
242
+ const languageName = content.trim();
243
+ setDetectedLanguageName(languageName);
244
+ // Try to find matching Language object from available languages
245
+ const availableLanguages = editContext?.itemLanguages || [];
246
+ const matchingLanguage = availableLanguages.find((lang) =>
247
+ lang.name.toLowerCase().includes(languageName.toLowerCase()),
248
+ );
249
+ if (matchingLanguage) {
250
+ setTargetLanguage(matchingLanguage);
251
+ }
252
+ }
253
+ } catch (parseError: unknown) {
254
+ console.error(
255
+ "Error parsing language detection response:",
256
+ parseError,
257
+ );
258
+ }
259
+ },
260
+ );
261
+
262
+ if (result && result.messages && result.messages.length > 0) {
263
+ const lastMessage = result.messages[result.messages.length - 1];
264
+ if (lastMessage && lastMessage.content) {
265
+ const languageName = lastMessage.content.trim();
266
+ setDetectedLanguageName(languageName);
267
+ // Try to find matching Language object from available languages
268
+ const availableLanguages = editContext?.itemLanguages || [];
269
+ const matchingLanguage = availableLanguages.find((lang) =>
270
+ lang.name.toLowerCase().includes(languageName.toLowerCase()),
271
+ );
272
+ if (matchingLanguage) {
273
+ setTargetLanguage(matchingLanguage);
274
+ }
275
+ console.log(`Detected target language: ${languageName}`);
276
+ }
277
+ }
278
+ } catch (error) {
279
+ console.error("Language detection error:", error);
280
+ } finally {
281
+ setIsDetectingLanguage(false);
282
+ }
283
+ };
284
+
285
+ // Auto-detect language when document content is available
286
+ useEffect(() => {
287
+ if (!detectedLanguageName && !isDetectingLanguage) {
288
+ detectLanguage();
289
+ }
290
+ }, [detectedLanguageName, isDetectingLanguage]);
291
+
292
+ const handleTranslate = async () => {
293
+ if (!targetLanguage?.languageCode) {
294
+ console.warn("Missing target language");
295
+ return;
296
+ }
297
+
298
+ if (!editContext) {
299
+ console.error("Edit context not available");
300
+ return;
301
+ }
302
+
303
+ setIsTranslating(true);
304
+ try {
305
+ // Prepare the fields data for the AI prompt
306
+ const fieldsForTranslation = Object.entries(translatableFields).flatMap(
307
+ ([itemId, fields]) =>
308
+ fields.map((field) => ({
309
+ itemId,
310
+ fieldId: field.fieldId,
311
+ fieldName: field.fieldName,
312
+ originalValue: field.value,
313
+ })),
314
+ );
315
+
316
+ const prompt = `You are a translation assistant. You have been given a document in ${targetLanguage.name} and a list of fields that need to be translated from ${sourceLanguage} to ${targetLanguage.name}.
317
+
318
+ Your task is to:
319
+ 1. Match the content from the provided document to the fields that need translation
320
+ 2. Return ONLY the translated text from the document - do not change, modify, or create any new text
321
+ 3. If you cannot find a suitable translation in the document for a field, leave the translatedValue empty
322
+
323
+ Document content:
324
+ ${documentContent}
325
+
326
+ Fields to translate:
327
+ ${JSON.stringify(fieldsForTranslation, null, 2)}
328
+
329
+ Return your response as a JSON object in this exact format:
330
+ {
331
+ "translations": [
332
+ {
333
+ "itemId": "item-id",
334
+ "fieldId": "field-id",
335
+ "translatedValue": "exact text from document"
336
+ }
337
+ ]
338
+ }`;
339
+
340
+ const abortController = new AbortController();
341
+
342
+ const result = await executePrompt(
343
+ [
344
+ {
345
+ content: prompt,
346
+ role: "user",
347
+ name: "user",
348
+ id: crypto.randomUUID(),
349
+ },
350
+ ],
351
+ { editContext, createAiContext: createWizardAiContext },
352
+ { model: "gpt-4o-mini" },
353
+ { signal: abortController.signal },
354
+ );
355
+
356
+ if (result && result.messages && result.messages.length > 0) {
357
+ const lastMessage = result.messages[result.messages.length - 1];
358
+ if (lastMessage && lastMessage.content) {
359
+ try {
360
+ const parsedResponse = JSON.parse(lastMessage.content);
361
+ const translations = parsedResponse.translations || [];
362
+
363
+ // Update translated fields with AI response
364
+ const updatedTranslatedFields: TranslatedFieldsResponse = {
365
+ ...translatedFields,
366
+ };
367
+
368
+ translations.forEach((translation: any) => {
369
+ const { itemId, fieldId, translatedValue } = translation;
370
+ if (updatedTranslatedFields[itemId]) {
371
+ const fieldIndex = updatedTranslatedFields[itemId].findIndex(
372
+ (f) => f.fieldId === fieldId,
373
+ );
374
+ if (
375
+ fieldIndex !== -1 &&
376
+ updatedTranslatedFields[itemId][fieldIndex]
377
+ ) {
378
+ updatedTranslatedFields[itemId][fieldIndex].translatedValue =
379
+ translatedValue || "";
380
+ }
381
+ }
382
+ });
383
+
384
+ setTranslatedFields(updatedTranslatedFields);
385
+ console.log(
386
+ `Translation complete: ${translations.length} fields translated`,
387
+ );
388
+ } catch (parseError) {
389
+ console.error("Failed to parse AI response:", parseError);
390
+ }
391
+ }
392
+ }
393
+ } catch (error) {
394
+ console.error("Translation error:", error);
395
+ } finally {
396
+ setIsTranslating(false);
397
+ }
398
+ };
399
+
400
+ const handleFieldChange = (
401
+ itemId: string,
402
+ fieldId: string,
403
+ value: string,
404
+ ) => {
405
+ setTranslatedFields((prev) => ({
406
+ ...prev,
407
+ [itemId]:
408
+ prev[itemId]?.map((field) =>
409
+ field.fieldId === fieldId
410
+ ? { ...field, translatedValue: value }
411
+ : field,
412
+ ) || [],
413
+ }));
414
+ };
415
+
416
+ const handleConfirmTranslation = async () => {
417
+ if (!editContext || !currentItem || !targetLanguage?.languageCode) {
418
+ console.error("Missing required data for publishing translations");
419
+ return;
420
+ }
421
+
422
+ setIsPublishing(true);
423
+ setPublishResults({ success: [], errors: [] });
424
+
425
+ try {
426
+ // Get all items that have translations
427
+ const itemsToTranslate = Object.entries(translatedFields).filter(
428
+ ([itemId, fields]) =>
429
+ fields.some((field) => field.translatedValue.trim() !== ""),
430
+ );
431
+
432
+ console.log(
433
+ `Publishing translations for ${itemsToTranslate.length} items to language: ${targetLanguage.languageCode}`,
434
+ );
435
+
436
+ for (const [itemId, translatedFieldsList] of itemsToTranslate) {
437
+ try {
438
+ // Create ItemDescriptor for the component item
439
+ const componentItemDescriptor = {
440
+ id: itemId,
441
+ language: targetLanguage.languageCode,
442
+ version: 0,
443
+ };
444
+
445
+ // Create language version for this item
446
+ console.log(
447
+ `Creating language version for item ${itemId} in language ${targetLanguage.languageCode}`,
448
+ );
449
+ await editContext.operations.createVersion(componentItemDescriptor);
450
+
451
+ // Update field values with translations
452
+ const fieldsWithTranslations = translatedFieldsList.filter(
453
+ (field) => field.translatedValue.trim() !== "",
454
+ );
455
+
456
+ for (const translatedField of fieldsWithTranslations) {
457
+ const fieldDescriptor = {
458
+ fieldId: translatedField.fieldId,
459
+ item: {
460
+ id: itemId,
461
+ language: targetLanguage.languageCode,
462
+ version: 0,
463
+ },
464
+ };
465
+
466
+ console.log(
467
+ `Updating field ${translatedField.fieldId} for item ${itemId}`,
468
+ );
469
+ await editContext.operations.editField({
470
+ field: fieldDescriptor,
471
+ value: translatedField.translatedValue,
472
+ rawValue: translatedField.translatedValue,
473
+ refresh: "none",
474
+ });
475
+ }
476
+
477
+ setPublishResults((prev) => ({
478
+ ...prev,
479
+ success: [
480
+ ...prev.success,
481
+ `${itemId} (${fieldsWithTranslations.length} fields)`,
482
+ ],
483
+ }));
484
+ } catch (error) {
485
+ console.error(`Error translating item ${itemId}:`, error);
486
+ setPublishResults((prev) => ({
487
+ ...prev,
488
+ errors: [
489
+ ...prev.errors,
490
+ `${itemId}: ${error instanceof Error ? error.message : "Unknown error"}`,
491
+ ],
492
+ }));
493
+ }
494
+ }
495
+
496
+ console.log("Translation publishing completed");
497
+ } catch (error) {
498
+ console.error("Error during translation publishing:", error);
499
+ setPublishResults((prev) => ({
500
+ ...prev,
501
+ errors: [
502
+ ...prev.errors,
503
+ `General error: ${error instanceof Error ? error.message : "Unknown error"}`,
504
+ ],
505
+ }));
506
+ } finally {
507
+ setIsPublishing(false);
508
+ }
509
+ };
510
+
511
+ // Check if step is completed (has translations and they've been published successfully)
512
+ useEffect(() => {
513
+ const hasTranslations = Object.values(translatedFields).some((fields) =>
514
+ fields.some((field) => field.translatedValue.trim() !== ""),
515
+ );
516
+ const hasSuccessfulPublish = publishResults.success.length > 0;
517
+
518
+ // Step is completed if we have translations and at least some have been published successfully
519
+ setStepCompleted(hasTranslations && hasSuccessfulPublish);
520
+ }, [translatedFields, publishResults, setStepCompleted]);
521
+
522
+ // Save translated fields and publish results to wizard data
523
+ useEffect(() => {
524
+ setData((prev) => ({
525
+ ...prev,
526
+ translatedFields,
527
+ publishResults,
528
+ targetLanguage,
529
+ detectedLanguageName,
530
+ sourceLanguage,
531
+ }));
532
+ }, [
533
+ translatedFields,
534
+ publishResults,
535
+ targetLanguage,
536
+ detectedLanguageName,
537
+ sourceLanguage,
538
+ setData,
539
+ ]);
540
+
541
+ const totalFields = Object.values(translatableFields).reduce(
542
+ (total, fields) => total + fields.length,
543
+ 0,
544
+ );
545
+ const translatedCount = Object.values(translatedFields).reduce(
546
+ (total, fields) =>
547
+ total +
548
+ fields.filter((field) => field.translatedValue.trim() !== "").length,
549
+ 0,
550
+ );
551
+
552
+ return (
553
+ <div className="mx-auto flex flex-col gap-6 p-6">
554
+ <WizardBox
555
+ title="Translation Setup"
556
+ description="Select source and target languages, target language will be auto-detected from the document"
557
+ icon={<Languages className="h-5 w-5" />}
558
+ >
559
+ <div className="grid grid-cols-2 gap-4">
560
+ <div>
561
+ <label className="mb-2 block text-sm font-medium">
562
+ Source Language
563
+ </label>
564
+ <SimpleLanguageSelector
565
+ selectedLanguage={sourceLanguage}
566
+ onLanguageSelected={(language: Language) =>
567
+ setSourceLanguage(language.languageCode)
568
+ }
569
+ disabled={false}
570
+ availableLanguages={availableSourceLanguages}
571
+ />
572
+ <p className="mt-1 text-xs text-gray-600">
573
+ Select the language of the content to translate from
574
+ </p>
575
+ </div>
576
+ <div>
577
+ <div className="mb-2 flex items-center justify-between">
578
+ <label className="block text-sm font-medium">
579
+ Target Language
580
+ </label>
581
+ {isDetectingLanguage && (
582
+ <div className="flex items-center gap-1 text-xs text-blue-600">
583
+ <RefreshCw className="h-3 w-3 animate-spin" />
584
+ Detecting...
585
+ </div>
586
+ )}
587
+ {detectedLanguageName && !isDetectingLanguage && (
588
+ <div className="flex items-center gap-1 text-xs text-green-600">
589
+ <Sparkles className="h-3 w-3" />
590
+ Auto-detected
591
+ </div>
592
+ )}
593
+ </div>
594
+ <div className="relative">
595
+ <SimpleLanguageSelector
596
+ selectedLanguage={targetLanguage?.languageCode || ""}
597
+ onLanguageSelected={(language: Language) =>
598
+ setTargetLanguage(language)
599
+ }
600
+ disabled={false}
601
+ className={
602
+ detectedLanguageName ? "border-green-200 bg-green-50" : ""
603
+ }
604
+ />
605
+ {detectedLanguageName && (
606
+ <button
607
+ type="button"
608
+ onClick={detectLanguage}
609
+ className="absolute top-1/2 right-2 -translate-y-1/2 transform text-blue-600 hover:text-blue-800"
610
+ title="Re-detect language"
611
+ >
612
+ <RefreshCw className="h-4 w-4" />
613
+ </button>
614
+ )}
615
+ </div>
616
+ {detectedLanguageName && (
617
+ <p className="mt-1 text-xs text-green-600">
618
+ Detected: {detectedLanguageName}
619
+ {targetLanguage && ` → Selected: ${targetLanguage.name}`}
620
+ </p>
621
+ )}
622
+ </div>
623
+ </div>
624
+
625
+ <ActionButton
626
+ onClick={handleTranslate}
627
+ isLoading={isTranslating}
628
+ disabled={!targetLanguage?.languageCode}
629
+ className="mt-4"
630
+ variant="outline"
631
+ >
632
+ <Wand2 className="mr-2 h-4 w-4" />
633
+ Translate Fields
634
+ </ActionButton>
635
+ </WizardBox>
636
+
637
+ <WizardBox
638
+ title="Translation Progress"
639
+ description={`${translatedCount} of ${totalFields} fields translated`}
640
+ icon={<Upload className="h-5 w-5" />}
641
+ >
642
+ {isLoading ? (
643
+ <div className="py-8 text-center">Loading translatable fields...</div>
644
+ ) : (
645
+ <div className="space-y-6">
646
+ {Object.entries(translatableFields).map(([itemId, fields]) => (
647
+ <div key={itemId} className="rounded-lg p-4">
648
+ <h4 className="mb-4 font-medium">
649
+ {componentNames[itemId] || "Component"}
650
+ </h4>
651
+ <div className="space-y-4">
652
+ {fields.map((field) => {
653
+ const translatedField = translatedFields[itemId]?.find(
654
+ (f) => f.fieldId === field.fieldId,
655
+ );
656
+ return (
657
+ <div
658
+ key={field.fieldId}
659
+ className="grid grid-cols-2 gap-4"
660
+ >
661
+ <div className="flex flex-col">
662
+ <label className="mb-1 block text-xs font-medium">
663
+ {field.fieldName}
664
+ </label>
665
+ <div className="min-h-[100px] flex-1 rounded border bg-gray-50 p-3 text-xs">
666
+ {field.value}
667
+ </div>
668
+ </div>
669
+ <div className="flex flex-col">
670
+ <label className="mb-1 block text-xs font-medium">
671
+ {field.fieldName}
672
+ </label>
673
+ <InputTextarea
674
+ value={translatedField?.translatedValue || ""}
675
+ onChange={(e) =>
676
+ handleFieldChange(
677
+ itemId,
678
+ field.fieldId,
679
+ e.target.value,
680
+ )
681
+ }
682
+ placeholder="Translation will appear here..."
683
+ rows={4}
684
+ className="min-h-[100px] w-full flex-1 text-xs"
685
+ />
686
+ </div>
687
+ </div>
688
+ );
689
+ })}
690
+ </div>
691
+ </div>
692
+ ))}
693
+ </div>
694
+ )}
695
+ </WizardBox>
696
+
697
+ {translatedCount > 0 && (
698
+ <WizardBox
699
+ title="Publish Translations"
700
+ description="Create language versions and apply translations"
701
+ icon={<CheckCircle className="h-5 w-5" />}
702
+ >
703
+ <div className="space-y-4">
704
+ <div className="rounded-lg border border-blue-200 bg-blue-50 p-4">
705
+ <h4 className="mb-2 font-medium text-blue-900">
706
+ Ready to Publish
707
+ </h4>
708
+ <p className="text-sm text-blue-800">
709
+ {translatedCount} fields ready to be published to{" "}
710
+ <strong>{targetLanguage?.name}</strong> language versions
711
+ {detectedLanguageName &&
712
+ targetLanguage?.name
713
+ ?.toLowerCase()
714
+ .includes(detectedLanguageName.toLowerCase()) && (
715
+ <span className="text-green-700"> (auto-detected)</span>
716
+ )}
717
+ . This will create new language versions for all translated
718
+ items and update their field values.
719
+ </p>
720
+ </div>
721
+
722
+ {(publishResults.success.length > 0 ||
723
+ publishResults.errors.length > 0) && (
724
+ <div className="space-y-2">
725
+ {publishResults.success.length > 0 && (
726
+ <div className="rounded-lg border border-green-200 bg-green-50 p-3">
727
+ <div className="mb-1 flex items-center gap-2 font-medium text-green-800">
728
+ <CheckCircle className="h-4 w-4" />
729
+ Successfully Published ({publishResults.success.length})
730
+ </div>
731
+ <ul className="list-inside list-disc text-sm text-green-700">
732
+ {publishResults.success.map((item, index) => (
733
+ <li key={index}>{item}</li>
734
+ ))}
735
+ </ul>
736
+ </div>
737
+ )}
738
+
739
+ {publishResults.errors.length > 0 && (
740
+ <div className="rounded-lg border border-red-200 bg-red-50 p-3">
741
+ <div className="mb-1 flex items-center gap-2 font-medium text-red-800">
742
+ <AlertCircle className="h-4 w-4" />
743
+ Errors ({publishResults.errors.length})
744
+ </div>
745
+ <ul className="list-inside list-disc text-sm text-red-700">
746
+ {publishResults.errors.map((error, index) => (
747
+ <li key={index}>{error}</li>
748
+ ))}
749
+ </ul>
750
+ </div>
751
+ )}
752
+ </div>
753
+ )}
754
+
755
+ <ActionButton
756
+ onClick={handleConfirmTranslation}
757
+ isLoading={isPublishing}
758
+ disabled={translatedCount === 0 || !targetLanguage?.languageCode}
759
+ className="w-full"
760
+ variant="outline"
761
+ >
762
+ <CheckCircle className="mr-2 h-4 w-4" />
763
+ {isPublishing
764
+ ? "Publishing Translations..."
765
+ : `Publish ${translatedCount} Translations to ${targetLanguage?.name || "Target Language"}`}
766
+ </ActionButton>
767
+ </div>
768
+ </WizardBox>
769
+ )}
770
+ </div>
771
+ );
772
+ }