@alpaca-editor/core 1.0.4027 → 1.0.4031

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 (220) hide show
  1. package/dist/components/ActionButton.js +2 -2
  2. package/dist/components/ActionButton.js.map +1 -1
  3. package/dist/components/SimpleLanguageSelector.js +3 -1
  4. package/dist/components/SimpleLanguageSelector.js.map +1 -1
  5. package/dist/components/ui/button.d.ts +1 -1
  6. package/dist/config/config.js +1 -1
  7. package/dist/config/config.js.map +1 -1
  8. package/dist/config/types.d.ts +1 -1
  9. package/dist/editor/ContextMenu.js +0 -1
  10. package/dist/editor/ContextMenu.js.map +1 -1
  11. package/dist/editor/Editor.js +9 -3
  12. package/dist/editor/Editor.js.map +1 -1
  13. package/dist/editor/FieldListField.js +16 -24
  14. package/dist/editor/FieldListField.js.map +1 -1
  15. package/dist/editor/ImageEditButton.js +1 -1
  16. package/dist/editor/ImageEditButton.js.map +1 -1
  17. package/dist/editor/ImageEditor.js +1 -1
  18. package/dist/editor/ImageEditor.js.map +1 -1
  19. package/dist/editor/MainLayout.js +2 -2
  20. package/dist/editor/MainLayout.js.map +1 -1
  21. package/dist/editor/Terminal.js +1 -1
  22. package/dist/editor/Terminal.js.map +1 -1
  23. package/dist/editor/Titlebar.js +0 -1
  24. package/dist/editor/Titlebar.js.map +1 -1
  25. package/dist/editor/ai/AgentCostDisplay.d.ts +26 -0
  26. package/dist/editor/ai/AgentCostDisplay.js +65 -0
  27. package/dist/editor/ai/AgentCostDisplay.js.map +1 -0
  28. package/dist/editor/ai/Agents.js +59 -15
  29. package/dist/editor/ai/Agents.js.map +1 -1
  30. package/dist/editor/ai/AiPromptPopover.d.ts +7 -0
  31. package/dist/editor/ai/AiPromptPopover.js +111 -0
  32. package/dist/editor/ai/AiPromptPopover.js.map +1 -0
  33. package/dist/editor/ai/AiResponseMessage.d.ts +1 -0
  34. package/dist/editor/ai/AiResponseMessage.js +101 -23
  35. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  36. package/dist/editor/ai/AiTerminal.d.ts +15 -1
  37. package/dist/editor/ai/AiTerminal.js +379 -48
  38. package/dist/editor/ai/AiTerminal.js.map +1 -1
  39. package/dist/editor/ai/editorAiContext.d.ts +0 -1
  40. package/dist/editor/ai/editorAiContext.js +0 -2
  41. package/dist/editor/ai/editorAiContext.js.map +1 -1
  42. package/dist/editor/client/EditorClient.d.ts +3 -2
  43. package/dist/editor/client/EditorClient.js +326 -68
  44. package/dist/editor/client/EditorClient.js.map +1 -1
  45. package/dist/editor/client/editContext.d.ts +6 -4
  46. package/dist/editor/client/editContext.js.map +1 -1
  47. package/dist/editor/client/fieldModificationStore.d.ts +19 -0
  48. package/dist/editor/client/fieldModificationStore.js +125 -0
  49. package/dist/editor/client/fieldModificationStore.js.map +1 -0
  50. package/dist/editor/client/itemsRepository.d.ts +1 -1
  51. package/dist/editor/client/itemsRepository.js +38 -28
  52. package/dist/editor/client/itemsRepository.js.map +1 -1
  53. package/dist/editor/client/operations.d.ts +1 -0
  54. package/dist/editor/client/operations.js +39 -31
  55. package/dist/editor/client/operations.js.map +1 -1
  56. package/dist/editor/commands/componentCommands.js +5 -3
  57. package/dist/editor/commands/componentCommands.js.map +1 -1
  58. package/dist/editor/commands/itemCommands.js.map +1 -1
  59. package/dist/editor/component-designer/aiContext.js +0 -2
  60. package/dist/editor/component-designer/aiContext.js.map +1 -1
  61. package/dist/editor/field-types/DropLinkEditor.js +1 -1
  62. package/dist/editor/field-types/DropLinkEditor.js.map +1 -1
  63. package/dist/editor/field-types/MultiLineText.js +5 -7
  64. package/dist/editor/field-types/MultiLineText.js.map +1 -1
  65. package/dist/editor/field-types/RichTextEditorComponent.js +5 -7
  66. package/dist/editor/field-types/RichTextEditorComponent.js.map +1 -1
  67. package/dist/editor/field-types/SingleLineText.js +5 -7
  68. package/dist/editor/field-types/SingleLineText.js.map +1 -1
  69. package/dist/editor/hooks/useEditorSettings.d.ts +17 -0
  70. package/dist/editor/hooks/useEditorSettings.js +61 -0
  71. package/dist/editor/hooks/useEditorSettings.js.map +1 -0
  72. package/dist/editor/menubar/ItemActionsMenu.js +2 -2
  73. package/dist/editor/menubar/ItemActionsMenu.js.map +1 -1
  74. package/dist/editor/menubar/PageSelector.js +1 -1
  75. package/dist/editor/menubar/PageSelector.js.map +1 -1
  76. package/dist/editor/menubar/toolbar-sections/EditControls.js +1 -1
  77. package/dist/editor/menubar/toolbar-sections/EditControls.js.map +1 -1
  78. package/dist/editor/menubar/toolbar-sections/InsertControls.js +1 -1
  79. package/dist/editor/menubar/toolbar-sections/InsertControls.js.map +1 -1
  80. package/dist/editor/menubar/toolbar-sections/UtilityControls.js +1 -1
  81. package/dist/editor/menubar/toolbar-sections/UtilityControls.js.map +1 -1
  82. package/dist/editor/menubar/toolbar-sections/ViewportControls.js +1 -1
  83. package/dist/editor/menubar/toolbar-sections/ViewportControls.js.map +1 -1
  84. package/dist/editor/page-editor-chrome/FieldEditedIndicators.js +4 -3
  85. package/dist/editor/page-editor-chrome/FieldEditedIndicators.js.map +1 -1
  86. package/dist/editor/page-editor-chrome/FrameMenu.js +9 -1
  87. package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
  88. package/dist/editor/page-editor-chrome/useInlineAICompletion.js +0 -1
  89. package/dist/editor/page-editor-chrome/useInlineAICompletion.js.map +1 -1
  90. package/dist/editor/page-viewer/EditorForm.js +1 -1
  91. package/dist/editor/page-viewer/EditorForm.js.map +1 -1
  92. package/dist/editor/page-viewer/PageViewer.js +9 -8
  93. package/dist/editor/page-viewer/PageViewer.js.map +1 -1
  94. package/dist/editor/page-viewer/PageViewerFrame.js +7 -1
  95. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  96. package/dist/editor/page-viewer/pageViewContext.js +40 -6
  97. package/dist/editor/page-viewer/pageViewContext.js.map +1 -1
  98. package/dist/editor/reviews/Comment.js +7 -6
  99. package/dist/editor/reviews/Comment.js.map +1 -1
  100. package/dist/editor/services/agentService.d.ts +84 -12
  101. package/dist/editor/services/agentService.js +256 -15
  102. package/dist/editor/services/agentService.js.map +1 -1
  103. package/dist/editor/services/aiService.d.ts +17 -3
  104. package/dist/editor/services/aiService.js +5 -3
  105. package/dist/editor/services/aiService.js.map +1 -1
  106. package/dist/editor/services/contextService.js +0 -1
  107. package/dist/editor/services/contextService.js.map +1 -1
  108. package/dist/editor/services/systemService.d.ts +2 -1
  109. package/dist/editor/services/systemService.js +3 -0
  110. package/dist/editor/services/systemService.js.map +1 -1
  111. package/dist/editor/sidebar/ComponentPalette.js +1 -1
  112. package/dist/editor/sidebar/ComponentPalette.js.map +1 -1
  113. package/dist/editor/sidebar/EditHistory.js +2 -2
  114. package/dist/editor/sidebar/EditHistory.js.map +1 -1
  115. package/dist/editor/sidebar/GraphQL.d.ts +1 -0
  116. package/dist/editor/sidebar/GraphQL.js +8 -2
  117. package/dist/editor/sidebar/GraphQL.js.map +1 -1
  118. package/dist/editor/sidebar/MainContentTree.js +1 -1
  119. package/dist/editor/sidebar/MainContentTree.js.map +1 -1
  120. package/dist/editor/sidebar/SEOInfo.js +1 -1
  121. package/dist/editor/sidebar/SEOInfo.js.map +1 -1
  122. package/dist/editor/sidebar/ViewSelector.d.ts +4 -1
  123. package/dist/editor/sidebar/ViewSelector.js +64 -48
  124. package/dist/editor/sidebar/ViewSelector.js.map +1 -1
  125. package/dist/editor/ui/PerfectTree.js +2 -11
  126. package/dist/editor/ui/PerfectTree.js.map +1 -1
  127. package/dist/editor/ui/SimpleIconButton.d.ts +2 -0
  128. package/dist/editor/ui/SimpleIconButton.js +8 -4
  129. package/dist/editor/ui/SimpleIconButton.js.map +1 -1
  130. package/dist/index.d.ts +4 -2
  131. package/dist/index.js +3 -1
  132. package/dist/index.js.map +1 -1
  133. package/dist/page-wizard/steps/CollectStep.js +1 -1
  134. package/dist/page-wizard/steps/CollectStep.js.map +1 -1
  135. package/dist/page-wizard/steps/StructureStep.js +1 -1
  136. package/dist/page-wizard/steps/StructureStep.js.map +1 -1
  137. package/dist/page-wizard/steps/TranslateStep.js +233 -18
  138. package/dist/page-wizard/steps/TranslateStep.js.map +1 -1
  139. package/dist/revision.d.ts +2 -2
  140. package/dist/revision.js +2 -2
  141. package/dist/splash-screen/RecentPages.js +1 -13
  142. package/dist/splash-screen/RecentPages.js.map +1 -1
  143. package/dist/splash-screen/SplashScreen.js +1 -1
  144. package/dist/splash-screen/SplashScreen.js.map +1 -1
  145. package/dist/styles.css +88 -3
  146. package/dist/types.d.ts +6 -0
  147. package/package.json +2 -2
  148. package/src/components/ActionButton.tsx +3 -2
  149. package/src/components/SimpleLanguageSelector.tsx +6 -1
  150. package/src/config/config.tsx +1 -1
  151. package/src/config/types.ts +1 -1
  152. package/src/editor/ContextMenu.tsx +0 -3
  153. package/src/editor/Editor.tsx +11 -3
  154. package/src/editor/FieldListField.tsx +22 -31
  155. package/src/editor/ImageEditButton.tsx +1 -0
  156. package/src/editor/ImageEditor.tsx +1 -0
  157. package/src/editor/MainLayout.tsx +2 -2
  158. package/src/editor/Terminal.tsx +1 -1
  159. package/src/editor/Titlebar.tsx +0 -2
  160. package/src/editor/ai/AgentCostDisplay.tsx +237 -0
  161. package/src/editor/ai/Agents.tsx +69 -20
  162. package/src/editor/ai/AiPromptPopover.tsx +209 -0
  163. package/src/editor/ai/AiResponseMessage.tsx +201 -60
  164. package/src/editor/ai/AiTerminal.tsx +502 -71
  165. package/src/editor/ai/editorAiContext.ts +0 -3
  166. package/src/editor/client/EditorClient.tsx +409 -117
  167. package/src/editor/client/editContext.ts +7 -5
  168. package/src/editor/client/fieldModificationStore.ts +196 -0
  169. package/src/editor/client/itemsRepository.ts +41 -31
  170. package/src/editor/client/operations.ts +95 -76
  171. package/src/editor/commands/componentCommands.tsx +9 -3
  172. package/src/editor/commands/itemCommands.tsx +0 -1
  173. package/src/editor/component-designer/aiContext.ts +0 -2
  174. package/src/editor/field-types/DropLinkEditor.tsx +1 -1
  175. package/src/editor/field-types/MultiLineText.tsx +9 -9
  176. package/src/editor/field-types/RichTextEditorComponent.tsx +8 -8
  177. package/src/editor/field-types/SingleLineText.tsx +9 -9
  178. package/src/editor/hooks/useEditorSettings.ts +68 -0
  179. package/src/editor/menubar/ItemActionsMenu.tsx +3 -2
  180. package/src/editor/menubar/PageSelector.tsx +1 -1
  181. package/src/editor/menubar/toolbar-sections/EditControls.tsx +1 -0
  182. package/src/editor/menubar/toolbar-sections/InsertControls.tsx +1 -0
  183. package/src/editor/menubar/toolbar-sections/UtilityControls.tsx +2 -0
  184. package/src/editor/menubar/toolbar-sections/ViewportControls.tsx +2 -0
  185. package/src/editor/page-editor-chrome/FieldEditedIndicators.tsx +4 -3
  186. package/src/editor/page-editor-chrome/FrameMenu.tsx +10 -1
  187. package/src/editor/page-editor-chrome/useInlineAICompletion.tsx +0 -1
  188. package/src/editor/page-viewer/EditorForm.tsx +1 -0
  189. package/src/editor/page-viewer/PageViewer.tsx +9 -8
  190. package/src/editor/page-viewer/PageViewerFrame.tsx +7 -1
  191. package/src/editor/page-viewer/pageViewContext.ts +40 -5
  192. package/src/editor/reviews/Comment.tsx +7 -7
  193. package/src/editor/services/agentService.ts +405 -31
  194. package/src/editor/services/aiService.ts +22 -5
  195. package/src/editor/services/contextService.ts +0 -1
  196. package/src/editor/services/systemService.ts +7 -1
  197. package/src/editor/sidebar/ComponentPalette.tsx +4 -1
  198. package/src/editor/sidebar/EditHistory.tsx +2 -0
  199. package/src/editor/sidebar/GraphQL.tsx +19 -7
  200. package/src/editor/sidebar/MainContentTree.tsx +1 -1
  201. package/src/editor/sidebar/SEOInfo.tsx +1 -1
  202. package/src/editor/sidebar/ViewSelector.tsx +80 -64
  203. package/src/editor/ui/PerfectTree.tsx +2 -18
  204. package/src/editor/ui/SimpleIconButton.tsx +56 -38
  205. package/src/index.ts +4 -2
  206. package/src/page-wizard/steps/CollectStep.tsx +0 -2
  207. package/src/page-wizard/steps/StructureStep.tsx +3 -0
  208. package/src/page-wizard/steps/TranslateStep.tsx +473 -62
  209. package/src/revision.ts +2 -2
  210. package/src/splash-screen/RecentPages.tsx +0 -14
  211. package/src/splash-screen/SplashScreen.tsx +3 -2
  212. package/src/types.ts +7 -0
  213. package/dist/editor/ai/AiPopup.d.ts +0 -10
  214. package/dist/editor/ai/AiPopup.js +0 -23
  215. package/dist/editor/ai/AiPopup.js.map +0 -1
  216. package/dist/editor/ai/AiToolCall.d.ts +0 -5
  217. package/dist/editor/ai/AiToolCall.js +0 -28
  218. package/dist/editor/ai/AiToolCall.js.map +0 -1
  219. package/src/editor/ai/AiPopup.tsx +0 -59
  220. package/src/editor/ai/AiToolCall.tsx +0 -71
@@ -33,6 +33,8 @@ interface TranslatableFieldsResponse {
33
33
  interface TranslatedField {
34
34
  fieldId: string;
35
35
  translatedValue: string;
36
+ isComplete?: boolean;
37
+ missingSuggestions?: string;
36
38
  }
37
39
 
38
40
  interface TranslatedFieldsResponse {
@@ -56,6 +58,10 @@ export function TranslateStep({
56
58
  const [isLoading, setIsLoading] = useState(false);
57
59
  const [isTranslating, setIsTranslating] = useState(false);
58
60
  const [isDetectingLanguage, setIsDetectingLanguage] = useState(false);
61
+ const [translatingFields, setTranslatingFields] = useState<Set<string>>(
62
+ new Set(),
63
+ );
64
+ const [checkingFields, setCheckingFields] = useState<Set<string>>(new Set());
59
65
  const [targetLanguage, setTargetLanguage] = useState<Language | null>(
60
66
  data.targetLanguage || null,
61
67
  );
@@ -116,7 +122,7 @@ export function TranslateStep({
116
122
  }, [currentItem, editContext]);
117
123
 
118
124
  const fetchComponentNames = useCallback(
119
- async (itemIds: string[]) => {
125
+ async (itemIds: string[], sourceLanguageParam: string) => {
120
126
  if (!editContext || itemIds.length === 0) return;
121
127
 
122
128
  const names: Record<string, string> = {};
@@ -125,7 +131,7 @@ export function TranslateStep({
125
131
  try {
126
132
  const item = await editContext.itemsRepository.getItem({
127
133
  id: itemId,
128
- language: sourceLanguage,
134
+ language: sourceLanguageParam,
129
135
  version: 0,
130
136
  });
131
137
 
@@ -142,69 +148,104 @@ export function TranslateStep({
142
148
 
143
149
  setComponentNames(names);
144
150
  },
145
- [editContext, sourceLanguage],
151
+ [editContext],
146
152
  );
147
153
 
148
- const fetchTranslatableFields = useCallback(async () => {
149
- if (!currentItem || !editContext) return;
154
+ const fetchTranslatableFields = useCallback(
155
+ async (sourceLanguageParam: string) => {
156
+ if (!currentItem || !editContext) return;
150
157
 
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",
158
+ setIsLoading(true);
159
+ try {
160
+ const response = await fetch(
161
+ "/alpaca/editor/page-wizard/getTranslatableFields",
162
+ {
163
+ method: "POST",
164
+ headers: {
165
+ "Content-Type": "application/json",
166
+ },
167
+ body: JSON.stringify({
168
+ id: currentItem.id,
169
+ language: sourceLanguageParam,
170
+ version: currentItem.version,
171
+ }),
159
172
  },
160
- body: JSON.stringify({
161
- id: currentItem.id,
162
- language: sourceLanguage,
163
- version: currentItem.version,
164
- }),
165
- },
166
- );
173
+ );
174
+
175
+ if (response.ok) {
176
+ const fieldsData: TranslatableFieldsResponse = await response.json();
177
+ setTranslatableFields(fieldsData);
178
+
179
+ // Initialize translated fields structure
180
+ const initialTranslatedFields: TranslatedFieldsResponse = {};
181
+ Object.keys(fieldsData).forEach((itemId) => {
182
+ if (fieldsData[itemId]) {
183
+ initialTranslatedFields[itemId] = fieldsData[itemId].map(
184
+ (field) => ({
185
+ fieldId: field.fieldId,
186
+ translatedValue: "",
187
+ isComplete: undefined,
188
+ missingSuggestions: undefined,
189
+ }),
190
+ );
191
+ }
192
+ });
193
+ setTranslatedFields(initialTranslatedFields);
167
194
 
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
- );
195
+ // Fetch component names for all item IDs
196
+ const itemIds = Object.keys(fieldsData);
197
+ if (itemIds.length > 0) {
198
+ fetchComponentNames(itemIds, sourceLanguageParam);
182
199
  }
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);
200
+ } else {
201
+ console.error("Failed to fetch translatable fields");
190
202
  }
191
- } else {
192
- console.error("Failed to fetch translatable fields");
203
+ } catch (error) {
204
+ console.error("Error fetching translatable fields:", error);
205
+ } finally {
206
+ setIsLoading(false);
193
207
  }
194
- } catch (error) {
195
- console.error("Error fetching translatable fields:", error);
196
- } finally {
197
- setIsLoading(false);
198
- }
199
- }, [currentItem, editContext, sourceLanguage, fetchComponentNames]);
208
+ },
209
+ [currentItem, editContext, fetchComponentNames],
210
+ );
211
+
212
+ // Track the last source language we fetched for to prevent unnecessary re-fetching
213
+ const [lastFetchedSourceLanguage, setLastFetchedSourceLanguage] =
214
+ useState<string>("");
200
215
 
201
216
  useEffect(() => {
202
217
  fetchAvailableLanguages();
203
218
  }, [fetchAvailableLanguages]);
204
219
 
220
+ // Reset the last fetched source language when the current item changes
205
221
  useEffect(() => {
206
- fetchTranslatableFields();
207
- }, [fetchTranslatableFields, sourceLanguage]);
222
+ setLastFetchedSourceLanguage("");
223
+ }, [currentItem]);
224
+
225
+ useEffect(() => {
226
+ // Only fetch translatable fields if:
227
+ // 1. We have a source language
228
+ // 2. It's different from the last one we fetched for
229
+ // 3. We're not currently publishing (to avoid conflicts)
230
+ // 4. We're not currently loading or translating
231
+ if (
232
+ sourceLanguage &&
233
+ sourceLanguage !== lastFetchedSourceLanguage &&
234
+ !isPublishing &&
235
+ !isLoading &&
236
+ !isTranslating
237
+ ) {
238
+ fetchTranslatableFields(sourceLanguage);
239
+ setLastFetchedSourceLanguage(sourceLanguage);
240
+ }
241
+ }, [
242
+ fetchTranslatableFields,
243
+ sourceLanguage,
244
+ lastFetchedSourceLanguage,
245
+ isPublishing,
246
+ isLoading,
247
+ isTranslating,
248
+ ]);
208
249
 
209
250
  const detectLanguage = async () => {
210
251
  if (!editContext) {
@@ -231,7 +272,7 @@ ${documentContent.substring(0, 1000)}`;
231
272
  },
232
273
  ],
233
274
  { editContext, createAiContext: createWizardAiContext },
234
- { model: "gpt-4o-mini" },
275
+ { model: "gpt-4.1" },
235
276
  { signal: abortController.signal },
236
277
  (response: any) => {
237
278
  try {
@@ -282,12 +323,25 @@ ${documentContent.substring(0, 1000)}`;
282
323
  }
283
324
  };
284
325
 
285
- // Auto-detect language when document content is available
326
+ // Keep track of the last document content we detected for
327
+ const [lastDetectedDocument, setLastDetectedDocument] = useState<string>("");
328
+
329
+ // Auto-detect language when document content is available or changes
286
330
  useEffect(() => {
287
- if (!detectedLanguageName && !isDetectingLanguage) {
331
+ if (
332
+ documentContent &&
333
+ !isDetectingLanguage &&
334
+ documentContent !== lastDetectedDocument
335
+ ) {
336
+ // Reset detected language when document changes, then detect again
337
+ if (detectedLanguageName && lastDetectedDocument) {
338
+ setDetectedLanguageName("");
339
+ setTargetLanguage(null);
340
+ }
341
+ setLastDetectedDocument(documentContent);
288
342
  detectLanguage();
289
343
  }
290
- }, [detectedLanguageName, isDetectingLanguage]);
344
+ }, [documentContent]);
291
345
 
292
346
  const handleTranslate = async () => {
293
347
  if (!targetLanguage?.languageCode) {
@@ -349,7 +403,7 @@ Return your response as a JSON object in this exact format:
349
403
  },
350
404
  ],
351
405
  { editContext, createAiContext: createWizardAiContext },
352
- { model: "gpt-4o-mini" },
406
+ { model: "gpt-4.1", stream: false },
353
407
  { signal: abortController.signal },
354
408
  );
355
409
 
@@ -377,6 +431,9 @@ Return your response as a JSON object in this exact format:
377
431
  ) {
378
432
  updatedTranslatedFields[itemId][fieldIndex].translatedValue =
379
433
  translatedValue || "";
434
+ updatedTranslatedFields[itemId][
435
+ fieldIndex
436
+ ].missingSuggestions = undefined;
380
437
  }
381
438
  }
382
439
  });
@@ -385,6 +442,25 @@ Return your response as a JSON object in this exact format:
385
442
  console.log(
386
443
  `Translation complete: ${translations.length} fields translated`,
387
444
  );
445
+
446
+ // Check translation completeness for all translated fields
447
+ translations.forEach((translation: any) => {
448
+ const { itemId, fieldId, translatedValue } = translation;
449
+ if (translatedValue && translatedValue.trim()) {
450
+ const originalField = fieldsForTranslation.find(
451
+ (f) => f.itemId === itemId && f.fieldId === fieldId,
452
+ );
453
+ if (originalField) {
454
+ checkTranslationCompleteness(
455
+ itemId,
456
+ fieldId,
457
+ originalField.originalValue,
458
+ translatedValue,
459
+ originalField.fieldName,
460
+ );
461
+ }
462
+ }
463
+ });
388
464
  } catch (parseError) {
389
465
  console.error("Failed to parse AI response:", parseError);
390
466
  }
@@ -397,6 +473,216 @@ Return your response as a JSON object in this exact format:
397
473
  }
398
474
  };
399
475
 
476
+ const handleSingleFieldTranslation = async (
477
+ itemId: string,
478
+ fieldId: string,
479
+ field: TranslatableField,
480
+ ) => {
481
+ if (!targetLanguage?.languageCode || !editContext) {
482
+ console.warn("Missing target language or edit context");
483
+ return;
484
+ }
485
+
486
+ const fieldKey = `${itemId}-${fieldId}`;
487
+ setTranslatingFields((prev) => new Set(prev).add(fieldKey));
488
+
489
+ try {
490
+ // Create a prompt for this specific field
491
+ const prompt = `You are a translation assistant. You have been given a document in ${targetLanguage.name} and a single field that needs to be translated from ${sourceLanguage} to ${targetLanguage.name}.
492
+
493
+ Your task is to:
494
+ 1. Find content from the provided document that matches this field
495
+ 2. Return ONLY the translated text from the document - do not change, modify, or create any new text
496
+ 3. If you cannot find a suitable translation in the document for this field, return an empty string
497
+
498
+ Document content:
499
+ ${documentContent}
500
+
501
+ Field to translate:
502
+ - Field Name: ${field.fieldName}
503
+ - Value (${sourceLanguage}): ${field.value}
504
+
505
+ Return your response as a JSON object in this exact format:
506
+ {
507
+ "translatedValue": "exact text from document or empty string if no match found"
508
+ }`;
509
+
510
+ const abortController = new AbortController();
511
+
512
+ const result = await executePrompt(
513
+ [
514
+ {
515
+ content: prompt,
516
+ role: "user",
517
+ name: "user",
518
+ id: crypto.randomUUID(),
519
+ },
520
+ ],
521
+ { editContext, createAiContext: createWizardAiContext },
522
+ { model: "gpt-4.1", stream: false },
523
+ { signal: abortController.signal },
524
+ );
525
+
526
+ if (result && result.messages && result.messages.length > 0) {
527
+ const lastMessage = result.messages[result.messages.length - 1];
528
+ if (lastMessage && lastMessage.content) {
529
+ try {
530
+ const parsedResponse = JSON.parse(lastMessage.content);
531
+ const translatedValue = parsedResponse.translatedValue || "";
532
+
533
+ // Update this specific field's translation
534
+ setTranslatedFields((prev) => ({
535
+ ...prev,
536
+ [itemId]:
537
+ prev[itemId]?.map((f) =>
538
+ f.fieldId === fieldId
539
+ ? { ...f, translatedValue, missingSuggestions: undefined }
540
+ : f,
541
+ ) || [],
542
+ }));
543
+
544
+ console.log(
545
+ `Single field translation complete for ${field.fieldName}`,
546
+ );
547
+
548
+ // Check translation completeness after translation
549
+ if (translatedValue.trim()) {
550
+ checkTranslationCompleteness(
551
+ itemId,
552
+ fieldId,
553
+ field.value,
554
+ translatedValue,
555
+ field.fieldName,
556
+ );
557
+ }
558
+ } catch (parseError) {
559
+ console.error("Failed to parse AI response:", parseError);
560
+ }
561
+ }
562
+ }
563
+ } catch (error) {
564
+ console.error("Single field translation error:", error);
565
+ } finally {
566
+ setTranslatingFields((prev) => {
567
+ const newSet = new Set(prev);
568
+ newSet.delete(fieldKey);
569
+ return newSet;
570
+ });
571
+ }
572
+ };
573
+
574
+ const checkTranslationCompleteness = async (
575
+ itemId: string,
576
+ fieldId: string,
577
+ originalText: string,
578
+ translatedText: string,
579
+ fieldName: string,
580
+ ) => {
581
+ if (!editContext || !translatedText.trim()) {
582
+ return false;
583
+ }
584
+
585
+ const fieldKey = `${itemId}-${fieldId}`;
586
+ setCheckingFields((prev) => new Set(prev).add(fieldKey));
587
+
588
+ try {
589
+ const prompt = `You are a translation quality checker. Your task is to evaluate if a translation is complete and accurate.
590
+
591
+ Original text (${sourceLanguage}):
592
+ ${originalText}
593
+
594
+ Translated text (${targetLanguage?.name || "target language"}):
595
+ ${translatedText}
596
+
597
+ Field context: ${fieldName}
598
+
599
+ Is the translation complete (no missing content)? Do not assess the quality of the translation, just if it is complete. Only report missing sentences missing completely.
600
+ Do not report missing words, phrases, or content that is present in the original text. We just want to make sure we copied all content from the translation document.
601
+
602
+ If the translation is incomplete, identify specific content, phrases, or meaning that exists in the original but is missing in the translation, and suggest the exact text that should be added to make the translation complete.
603
+
604
+ Return your response as a JSON object in this exact format:
605
+ {
606
+ "isComplete": true/false,
607
+ "reason": "Brief explanation of your assessment",
608
+ "missingSuggestions": "If isComplete is false, provide specific suggestions for what needs to be added to complete the translation. Include the actual text that should be added. If isComplete is true, leave this empty."
609
+ }`;
610
+
611
+ const abortController = new AbortController();
612
+
613
+ const result = await executePrompt(
614
+ [
615
+ {
616
+ content: prompt,
617
+ role: "user",
618
+ name: "user",
619
+ id: crypto.randomUUID(),
620
+ },
621
+ ],
622
+ { editContext, createAiContext: createWizardAiContext },
623
+ { model: "gpt-4.1", stream: false },
624
+ { signal: abortController.signal },
625
+ );
626
+
627
+ if (result && result.messages && result.messages.length > 0) {
628
+ const lastMessage = result.messages[result.messages.length - 1];
629
+ if (lastMessage && lastMessage.content) {
630
+ try {
631
+ const parsedResponse = JSON.parse(lastMessage.content);
632
+ const isComplete = parsedResponse.isComplete || false;
633
+ const missingSuggestions = parsedResponse.missingSuggestions || "";
634
+
635
+ // Update the field's completeness status and missing suggestions
636
+ setTranslatedFields((prev) => ({
637
+ ...prev,
638
+ [itemId]:
639
+ prev[itemId]?.map((f) =>
640
+ f.fieldId === fieldId
641
+ ? {
642
+ ...f,
643
+ isComplete,
644
+ missingSuggestions: isComplete
645
+ ? undefined
646
+ : missingSuggestions,
647
+ }
648
+ : f,
649
+ ) || [],
650
+ }));
651
+
652
+ console.log(
653
+ `Translation completeness check for ${fieldName}: ${isComplete ? "Complete" : "Incomplete"} - ${parsedResponse.reason}`,
654
+ );
655
+
656
+ if (!isComplete && missingSuggestions) {
657
+ console.log(
658
+ `Missing content suggestions for ${fieldName}: ${missingSuggestions}`,
659
+ );
660
+ }
661
+
662
+ return isComplete;
663
+ } catch (parseError) {
664
+ console.error(
665
+ "Failed to parse completeness check response:",
666
+ parseError,
667
+ );
668
+ return false;
669
+ }
670
+ }
671
+ }
672
+
673
+ return false;
674
+ } catch (error) {
675
+ console.error("Translation completeness check error:", error);
676
+ return false;
677
+ } finally {
678
+ setCheckingFields((prev) => {
679
+ const newSet = new Set(prev);
680
+ newSet.delete(fieldKey);
681
+ return newSet;
682
+ });
683
+ }
684
+ };
685
+
400
686
  const handleFieldChange = (
401
687
  itemId: string,
402
688
  fieldId: string,
@@ -407,7 +693,12 @@ Return your response as a JSON object in this exact format:
407
693
  [itemId]:
408
694
  prev[itemId]?.map((field) =>
409
695
  field.fieldId === fieldId
410
- ? { ...field, translatedValue: value }
696
+ ? {
697
+ ...field,
698
+ translatedValue: value,
699
+ isComplete: undefined,
700
+ missingSuggestions: undefined,
701
+ }
411
702
  : field,
412
703
  ) || [],
413
704
  }));
@@ -640,8 +931,27 @@ Return your response as a JSON object in this exact format:
640
931
  icon={<Upload className="h-5 w-5" />}
641
932
  >
642
933
  {isLoading ? (
643
- <div className="py-8 text-center">Loading translatable fields...</div>
644
- ) : (
934
+ <div className="py-8 text-center">
935
+ <div className="flex items-center justify-center gap-2">
936
+ <RefreshCw className="h-4 w-4 animate-spin" />
937
+ Loading translatable fields...
938
+ </div>
939
+ </div>
940
+ ) : isTranslating ? (
941
+ <div className="py-8 text-center">
942
+ <div className="flex items-center justify-center gap-2">
943
+ <RefreshCw className="h-4 w-4 animate-spin" />
944
+ Translating fields...
945
+ </div>
946
+ </div>
947
+ ) : isPublishing ? (
948
+ <div className="py-8 text-center">
949
+ <div className="flex items-center justify-center gap-2">
950
+ <RefreshCw className="h-4 w-4 animate-spin" />
951
+ Publishing translations...
952
+ </div>
953
+ </div>
954
+ ) : translatedCount > 0 ? (
645
955
  <div className="space-y-6">
646
956
  {Object.entries(translatableFields).map(([itemId, fields]) => (
647
957
  <div key={itemId} className="rounded-lg p-4">
@@ -667,9 +977,104 @@ Return your response as a JSON object in this exact format:
667
977
  </div>
668
978
  </div>
669
979
  <div className="flex flex-col">
670
- <label className="mb-1 block text-xs font-medium">
671
- {field.fieldName}
672
- </label>
980
+ <div className="mb-1 flex items-center justify-between">
981
+ <div className="flex items-center gap-2">
982
+ <label className="block text-xs font-medium">
983
+ {field.fieldName}
984
+ </label>
985
+ {translatedField?.translatedValue && (
986
+ <div className="flex items-center">
987
+ {checkingFields.has(
988
+ `${itemId}-${field.fieldId}`,
989
+ ) ? (
990
+ <RefreshCw className="h-3 w-3 animate-spin text-gray-500" />
991
+ ) : translatedField.isComplete === true ? (
992
+ <span title="Translation is complete">
993
+ <CheckCircle className="h-3 w-3 text-green-600" />
994
+ </span>
995
+ ) : translatedField.isComplete === false ? (
996
+ <span title="Translation may be incomplete">
997
+ <AlertCircle className="h-3 w-3 text-red-600" />
998
+ </span>
999
+ ) : (
1000
+ <div className="h-3 w-3" />
1001
+ )}
1002
+ </div>
1003
+ )}
1004
+ </div>
1005
+ <div className="flex items-center gap-1">
1006
+ {translatedField?.translatedValue && (
1007
+ <button
1008
+ type="button"
1009
+ onClick={() =>
1010
+ checkTranslationCompleteness(
1011
+ itemId,
1012
+ field.fieldId,
1013
+ field.value,
1014
+ translatedField.translatedValue,
1015
+ field.fieldName,
1016
+ )
1017
+ }
1018
+ disabled={
1019
+ checkingFields.has(
1020
+ `${itemId}-${field.fieldId}`,
1021
+ ) || !translatedField.translatedValue.trim()
1022
+ }
1023
+ className="flex items-center gap-1 rounded px-2 py-1 text-xs text-gray-600 hover:bg-gray-50 hover:text-gray-800 disabled:cursor-not-allowed disabled:text-gray-400 disabled:hover:bg-transparent"
1024
+ title="Check translation completeness"
1025
+ >
1026
+ {checkingFields.has(
1027
+ `${itemId}-${field.fieldId}`,
1028
+ ) ? (
1029
+ <RefreshCw className="h-3 w-3 animate-spin" />
1030
+ ) : (
1031
+ <CheckCircle className="h-3 w-3" />
1032
+ )}
1033
+ Check
1034
+ </button>
1035
+ )}
1036
+ <button
1037
+ type="button"
1038
+ onClick={() =>
1039
+ handleSingleFieldTranslation(
1040
+ itemId,
1041
+ field.fieldId,
1042
+ field,
1043
+ )
1044
+ }
1045
+ disabled={
1046
+ !targetLanguage?.languageCode ||
1047
+ translatingFields.has(
1048
+ `${itemId}-${field.fieldId}`,
1049
+ ) ||
1050
+ isTranslating
1051
+ }
1052
+ className="flex items-center gap-1 rounded px-2 py-1 text-xs text-blue-600 hover:bg-blue-50 hover:text-blue-800 disabled:cursor-not-allowed disabled:text-gray-400 disabled:hover:bg-transparent"
1053
+ title="Retranslate this field"
1054
+ >
1055
+ {translatingFields.has(
1056
+ `${itemId}-${field.fieldId}`,
1057
+ ) ? (
1058
+ <RefreshCw className="h-3 w-3 animate-spin" />
1059
+ ) : (
1060
+ <Wand2 className="h-3 w-3" />
1061
+ )}
1062
+ Retranslate
1063
+ </button>
1064
+ </div>
1065
+ </div>
1066
+ {translatedField?.isComplete === false &&
1067
+ translatedField?.missingSuggestions && (
1068
+ <div className="mb-2 rounded border border-orange-200 bg-orange-50 p-2">
1069
+ <div className="mb-1 flex items-center gap-1 text-xs font-medium text-orange-800">
1070
+ <Sparkles className="h-3 w-3" />
1071
+ AI Suggestions for Missing Content:
1072
+ </div>
1073
+ <p className="text-xs text-orange-700 select-text">
1074
+ {translatedField.missingSuggestions}
1075
+ </p>
1076
+ </div>
1077
+ )}
673
1078
  <InputTextarea
674
1079
  value={translatedField?.translatedValue || ""}
675
1080
  onChange={(e) =>
@@ -691,6 +1096,12 @@ Return your response as a JSON object in this exact format:
691
1096
  </div>
692
1097
  ))}
693
1098
  </div>
1099
+ ) : (
1100
+ <div className="py-8 text-center text-sm text-gray-500">
1101
+ {Object.keys(translatableFields).length > 0
1102
+ ? "Click 'Translate Fields' to start the translation process."
1103
+ : "No translatable fields found."}
1104
+ </div>
694
1105
  )}
695
1106
  </WizardBox>
696
1107
 
package/src/revision.ts CHANGED
@@ -1,2 +1,2 @@
1
- export const version = "1.0.4027";
2
- export const buildDate = "2025-08-01 01:57:56";
1
+ export const version = "1.0.4031";
2
+ export const buildDate = "2025-08-06 09:06:10";