@alpaca-editor/core 1.0.4033 → 1.0.4038

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 (187) hide show
  1. package/dist/components/index.d.ts +1 -0
  2. package/dist/components/index.js +1 -0
  3. package/dist/components/index.js.map +1 -1
  4. package/dist/{editor/menubar → components/ui}/LanguageSelector.d.ts +1 -1
  5. package/dist/{editor/menubar → components/ui}/LanguageSelector.js +8 -8
  6. package/dist/components/ui/LanguageSelector.js.map +1 -0
  7. package/dist/components/ui/button.d.ts +1 -1
  8. package/dist/components/ui/dropdown-menu.d.ts +1 -1
  9. package/dist/components/ui/dropdown-menu.js +2 -2
  10. package/dist/components/ui/dropdown-menu.js.map +1 -1
  11. package/dist/components/ui/sonner.js +3 -1
  12. package/dist/components/ui/sonner.js.map +1 -1
  13. package/dist/config/config.js +5 -5
  14. package/dist/config/config.js.map +1 -1
  15. package/dist/editor/ContentTree.d.ts +2 -1
  16. package/dist/editor/ContentTree.js +33 -9
  17. package/dist/editor/ContentTree.js.map +1 -1
  18. package/dist/editor/PictureEditor.js +2 -2
  19. package/dist/editor/PictureEditor.js.map +1 -1
  20. package/dist/editor/ScrollingContentTree.d.ts +2 -1
  21. package/dist/editor/ScrollingContentTree.js +2 -2
  22. package/dist/editor/ScrollingContentTree.js.map +1 -1
  23. package/dist/editor/Terminal.d.ts +2 -0
  24. package/dist/editor/Terminal.js +2 -2
  25. package/dist/editor/Terminal.js.map +1 -1
  26. package/dist/editor/ai/AgentHistory.d.ts +11 -0
  27. package/dist/editor/ai/AgentHistory.js +12 -0
  28. package/dist/editor/ai/AgentHistory.js.map +1 -0
  29. package/dist/editor/ai/Agents.js +187 -24
  30. package/dist/editor/ai/Agents.js.map +1 -1
  31. package/dist/editor/ai/AiResponseMessage.d.ts +2 -1
  32. package/dist/editor/ai/AiResponseMessage.js +6 -6
  33. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  34. package/dist/editor/ai/AiTerminal.d.ts +1 -0
  35. package/dist/editor/ai/AiTerminal.js +330 -43
  36. package/dist/editor/ai/AiTerminal.js.map +1 -1
  37. package/dist/editor/client/itemsRepository.js +19 -7
  38. package/dist/editor/client/itemsRepository.js.map +1 -1
  39. package/dist/editor/field-types/InternalLinkFieldEditor.js +48 -1
  40. package/dist/editor/field-types/InternalLinkFieldEditor.js.map +1 -1
  41. package/dist/editor/field-types/richtext/contextMenuFactory.js +1 -1
  42. package/dist/editor/field-types/richtext/contextMenuFactory.js.map +1 -1
  43. package/dist/editor/menubar/ItemLanguageVersion.js +1 -1
  44. package/dist/editor/menubar/ItemLanguageVersion.js.map +1 -1
  45. package/dist/editor/menubar/PageSelector.js +1 -1
  46. package/dist/editor/menubar/PageSelector.js.map +1 -1
  47. package/dist/editor/page-editor-chrome/FieldActionIndicator.js +1 -1
  48. package/dist/editor/page-editor-chrome/FieldActionIndicator.js.map +1 -1
  49. package/dist/editor/page-editor-chrome/FieldEditedIndicator.js +2 -2
  50. package/dist/editor/page-editor-chrome/FieldEditedIndicator.js.map +1 -1
  51. package/dist/editor/page-editor-chrome/FrameMenu.js +2 -2
  52. package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
  53. package/dist/editor/page-editor-chrome/InlineEditor.js +9 -9
  54. package/dist/editor/page-editor-chrome/InlineEditor.js.map +1 -1
  55. package/dist/editor/page-editor-chrome/LockedFieldIndicator.js +2 -2
  56. package/dist/editor/page-editor-chrome/LockedFieldIndicator.js.map +1 -1
  57. package/dist/editor/page-editor-chrome/PageEditorChrome.js +1 -1
  58. package/dist/editor/page-editor-chrome/PageEditorChrome.js.map +1 -1
  59. package/dist/editor/page-editor-chrome/PictureEditorOverlay.js +1 -1
  60. package/dist/editor/page-editor-chrome/PictureEditorOverlay.js.map +1 -1
  61. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +1 -1
  62. package/dist/editor/page-editor-chrome/PlaceholderDropZones.js +2 -2
  63. package/dist/editor/page-editor-chrome/PlaceholderDropZones.js.map +1 -1
  64. package/dist/editor/page-editor-chrome/useInlineAICompletion.js +12 -12
  65. package/dist/editor/page-editor-chrome/useInlineAICompletion.js.map +1 -1
  66. package/dist/editor/page-viewer/EditorForm.js +1 -1
  67. package/dist/editor/page-viewer/EditorForm.js.map +1 -1
  68. package/dist/editor/page-viewer/MiniMap.js +10 -11
  69. package/dist/editor/page-viewer/MiniMap.js.map +1 -1
  70. package/dist/editor/page-viewer/PageViewer.js +2 -2
  71. package/dist/editor/page-viewer/PageViewer.js.map +1 -1
  72. package/dist/editor/page-viewer/PageViewerFrame.js +4 -4
  73. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  74. package/dist/editor/page-viewer/pageViewContext.d.ts +2 -2
  75. package/dist/editor/page-viewer/pageViewContext.js +11 -14
  76. package/dist/editor/page-viewer/pageViewContext.js.map +1 -1
  77. package/dist/editor/services/agentService.d.ts +21 -1
  78. package/dist/editor/services/agentService.js +101 -0
  79. package/dist/editor/services/agentService.js.map +1 -1
  80. package/dist/editor/services/aiService.d.ts +1 -1
  81. package/dist/editor/services/aiService.js +1 -2
  82. package/dist/editor/services/aiService.js.map +1 -1
  83. package/dist/editor/sidebar/GraphQL.js +5 -6
  84. package/dist/editor/sidebar/GraphQL.js.map +1 -1
  85. package/dist/editor/sidebar/MainContentTree.d.ts +2 -1
  86. package/dist/editor/sidebar/MainContentTree.js +9 -5
  87. package/dist/editor/sidebar/MainContentTree.js.map +1 -1
  88. package/dist/editor/ui/Icons.d.ts +5 -0
  89. package/dist/editor/ui/Icons.js +3 -0
  90. package/dist/editor/ui/Icons.js.map +1 -1
  91. package/dist/editor/ui/ItemNameDialogNew.js +26 -14
  92. package/dist/editor/ui/ItemNameDialogNew.js.map +1 -1
  93. package/dist/editor/ui/PerfectTree.js +54 -2
  94. package/dist/editor/ui/PerfectTree.js.map +1 -1
  95. package/dist/editor/views/CompareView.js +3 -3
  96. package/dist/editor/views/CompareView.js.map +1 -1
  97. package/dist/editor/views/EditView.js +1 -1
  98. package/dist/editor/views/EditView.js.map +1 -1
  99. package/dist/editor/views/ItemEditor.js +1 -1
  100. package/dist/editor/views/ItemEditor.js.map +1 -1
  101. package/dist/index.d.ts +3 -4
  102. package/dist/index.js +3 -4
  103. package/dist/index.js.map +1 -1
  104. package/dist/page-wizard/steps/ContentStep.js +5 -5
  105. package/dist/page-wizard/steps/ContentStep.js.map +1 -1
  106. package/dist/page-wizard/steps/FindItemsStep.js +1 -1
  107. package/dist/page-wizard/steps/ImagesStep.js +1 -1
  108. package/dist/page-wizard/steps/ImagesStep.js.map +1 -1
  109. package/dist/page-wizard/steps/LayoutStep.js +1 -1
  110. package/dist/page-wizard/steps/LayoutStep.js.map +1 -1
  111. package/dist/page-wizard/steps/MetaDataStep.js +1 -1
  112. package/dist/page-wizard/steps/MetaDataStep.js.map +1 -1
  113. package/dist/page-wizard/steps/SelectStep.js +1 -1
  114. package/dist/page-wizard/steps/StructureStep.js +28 -11
  115. package/dist/page-wizard/steps/StructureStep.js.map +1 -1
  116. package/dist/page-wizard/steps/TranslateStep.d.ts +1 -0
  117. package/dist/page-wizard/steps/TranslateStep.js +67 -73
  118. package/dist/page-wizard/steps/TranslateStep.js.map +1 -1
  119. package/dist/revision.d.ts +2 -2
  120. package/dist/revision.js +2 -2
  121. package/dist/splash-screen/NewPage.js +1 -1
  122. package/dist/splash-screen/NewPage.js.map +1 -1
  123. package/dist/splash-screen/OpenPage.js +1 -1
  124. package/dist/splash-screen/OpenPage.js.map +1 -1
  125. package/dist/styles.css +71 -8
  126. package/package.json +1 -1
  127. package/src/components/index.ts +1 -0
  128. package/src/{editor/menubar → components/ui}/LanguageSelector.tsx +12 -12
  129. package/src/components/ui/dropdown-menu.tsx +3 -1
  130. package/src/components/ui/sonner.tsx +5 -1
  131. package/src/config/config.tsx +4 -3
  132. package/src/editor/ContentTree.tsx +41 -12
  133. package/src/editor/PictureEditor.tsx +2 -2
  134. package/src/editor/ScrollingContentTree.tsx +3 -0
  135. package/src/editor/Terminal.tsx +16 -7
  136. package/src/editor/ai/AgentHistory.tsx +85 -0
  137. package/src/editor/ai/Agents.tsx +256 -88
  138. package/src/editor/ai/AiResponseMessage.tsx +25 -11
  139. package/src/editor/ai/AiTerminal.tsx +571 -73
  140. package/src/editor/client/itemsRepository.ts +29 -12
  141. package/src/editor/field-types/InternalLinkFieldEditor.tsx +52 -1
  142. package/src/editor/field-types/richtext/components/SimpleRichTextEditor.css +64 -0
  143. package/src/editor/field-types/richtext/contextMenuFactory.tsx +7 -8
  144. package/src/editor/menubar/ItemLanguageVersion.tsx +1 -1
  145. package/src/editor/menubar/PageSelector.tsx +1 -0
  146. package/src/editor/page-editor-chrome/FieldActionIndicator.tsx +1 -1
  147. package/src/editor/page-editor-chrome/FieldEditedIndicator.tsx +3 -3
  148. package/src/editor/page-editor-chrome/FrameMenu.tsx +2 -2
  149. package/src/editor/page-editor-chrome/InlineEditor.tsx +9 -12
  150. package/src/editor/page-editor-chrome/LockedFieldIndicator.tsx +3 -3
  151. package/src/editor/page-editor-chrome/PageEditorChrome.tsx +3 -3
  152. package/src/editor/page-editor-chrome/PictureEditorOverlay.tsx +1 -1
  153. package/src/editor/page-editor-chrome/PlaceholderDropZone.tsx +1 -1
  154. package/src/editor/page-editor-chrome/PlaceholderDropZones.tsx +2 -2
  155. package/src/editor/page-editor-chrome/useInlineAICompletion.tsx +12 -18
  156. package/src/editor/page-viewer/EditorForm.tsx +1 -1
  157. package/src/editor/page-viewer/MiniMap.tsx +10 -11
  158. package/src/editor/page-viewer/PageViewer.tsx +8 -3
  159. package/src/editor/page-viewer/PageViewerFrame.tsx +4 -4
  160. package/src/editor/page-viewer/pageViewContext.ts +71 -66
  161. package/src/editor/services/agentService.ts +129 -1
  162. package/src/editor/services/aiService.ts +2 -4
  163. package/src/editor/sidebar/GraphQL.tsx +16 -15
  164. package/src/editor/sidebar/MainContentTree.tsx +12 -4
  165. package/src/editor/ui/Icons.tsx +35 -0
  166. package/src/editor/ui/ItemNameDialogNew.tsx +29 -13
  167. package/src/editor/ui/PerfectTree.tsx +70 -4
  168. package/src/editor/views/CompareView.tsx +3 -3
  169. package/src/editor/views/EditView.tsx +1 -1
  170. package/src/editor/views/ItemEditor.tsx +1 -1
  171. package/src/index.ts +10 -4
  172. package/src/page-wizard/steps/ContentStep.tsx +5 -5
  173. package/src/page-wizard/steps/FindItemsStep.tsx +1 -1
  174. package/src/page-wizard/steps/ImagesStep.tsx +1 -1
  175. package/src/page-wizard/steps/LayoutStep.tsx +1 -1
  176. package/src/page-wizard/steps/MetaDataStep.tsx +1 -1
  177. package/src/page-wizard/steps/SelectStep.tsx +1 -1
  178. package/src/page-wizard/steps/StructureStep.tsx +41 -17
  179. package/src/page-wizard/steps/TranslateStep.tsx +326 -222
  180. package/src/revision.ts +2 -2
  181. package/src/splash-screen/NewPage.tsx +1 -0
  182. package/src/splash-screen/OpenPage.tsx +1 -0
  183. package/dist/components/SimpleLanguageSelector.d.ts +0 -10
  184. package/dist/components/SimpleLanguageSelector.js +0 -59
  185. package/dist/components/SimpleLanguageSelector.js.map +0 -1
  186. package/dist/editor/menubar/LanguageSelector.js.map +0 -1
  187. package/src/components/SimpleLanguageSelector.tsx +0 -113
@@ -1,14 +1,13 @@
1
1
  import { StepComponentProps } from "../../config/types";
2
2
  import { useEffect, useState, useCallback } from "react";
3
3
  import { useEditContext } from "../../editor/client/editContext";
4
- import { ItemDescriptor, Language } from "../../editor/pageModel";
4
+ import { Language } from "../../editor/pageModel";
5
5
  import { ActionButton } from "../../components/ActionButton";
6
- import { Input } from "../../components/ui/input";
7
6
  import { InputTextarea } from "primereact/inputtextarea";
8
7
  import { executePrompt } from "../../editor/services/aiService";
9
8
  import { createWizardAiContext } from "../service";
10
- import { SimpleLanguageSelector } from "../../components/SimpleLanguageSelector";
11
- import { getLanguagesAndVersions } from "../../editor/services/contentService";
9
+ import { LanguageSelector } from "../../components/ui/LanguageSelector";
10
+
12
11
  import {
13
12
  Languages,
14
13
  Upload,
@@ -17,13 +16,19 @@ import {
17
16
  AlertCircle,
18
17
  Sparkles,
19
18
  RefreshCw,
19
+ Edit,
20
+ Save,
21
+ X,
20
22
  } from "lucide-react";
21
23
  import { WizardBox } from "../WizardBox";
24
+ import { SimpleRichTextEditor } from "../../editor/field-types/richtext/components/SimpleRichTextEditor";
25
+ import "../../editor/field-types/richtext/components/SimpleRichTextEditor.css";
22
26
 
23
27
  interface TranslatableField {
24
28
  fieldId: string;
25
29
  fieldName: string;
26
30
  value: string;
31
+ fieldType: string;
27
32
  }
28
33
 
29
34
  interface TranslatableFieldsResponse {
@@ -68,12 +73,11 @@ export function TranslateStep({
68
73
  const [detectedLanguageName, setDetectedLanguageName] = useState<string>(
69
74
  data.detectedLanguageName || "",
70
75
  );
71
- const [availableSourceLanguages, setAvailableSourceLanguages] = useState<
72
- Language[]
73
- >([]);
76
+
74
77
  const [componentNames, setComponentNames] = useState<Record<string, string>>(
75
78
  {},
76
79
  );
80
+ const [editingFields, setEditingFields] = useState<Set<string>>(new Set());
77
81
 
78
82
  // Get the current item from data (set by previous wizard steps)
79
83
  const currentItem = data.currentItem || parentItem;
@@ -86,41 +90,12 @@ export function TranslateStep({
86
90
  // Get document content from previous wizard step
87
91
  const documentContent = data.document;
88
92
 
89
- const [isPublishing, setIsPublishing] = useState(false);
90
- const [publishResults, setPublishResults] = useState<{
93
+ const [isSaving, setIsSaving] = useState(false);
94
+ const [saveResults, setSaveResults] = useState<{
91
95
  success: string[];
92
96
  errors: string[];
93
97
  }>({ success: [], errors: [] });
94
98
 
95
- const fetchAvailableLanguages = useCallback(async () => {
96
- if (!currentItem || !editContext) return;
97
-
98
- try {
99
- const response = await getLanguagesAndVersions({
100
- id: currentItem.id,
101
- language: currentItem.language,
102
- version: currentItem.version,
103
- });
104
-
105
- if (response.data?.languages) {
106
- // Filter to only languages that have versions (versions > 0)
107
- const languagesWithVersions = response.data.languages
108
- .filter((lang) => lang.versions > 0)
109
- .map((lang) => ({
110
- languageCode: lang.languageCode,
111
- name: lang.name,
112
- icon: lang.icon,
113
- versions: lang.versions,
114
- }));
115
- setAvailableSourceLanguages(languagesWithVersions);
116
- }
117
- } catch (error) {
118
- console.error("Error fetching available languages:", error);
119
- // Fallback to all languages if there's an error
120
- setAvailableSourceLanguages(editContext?.itemLanguages || []);
121
- }
122
- }, [currentItem, editContext]);
123
-
124
99
  const fetchComponentNames = useCallback(
125
100
  async (itemIds: string[], sourceLanguageParam: string) => {
126
101
  if (!editContext || itemIds.length === 0) return;
@@ -213,10 +188,6 @@ export function TranslateStep({
213
188
  const [lastFetchedSourceLanguage, setLastFetchedSourceLanguage] =
214
189
  useState<string>("");
215
190
 
216
- useEffect(() => {
217
- fetchAvailableLanguages();
218
- }, [fetchAvailableLanguages]);
219
-
220
191
  // Reset the last fetched source language when the current item changes
221
192
  useEffect(() => {
222
193
  setLastFetchedSourceLanguage("");
@@ -226,12 +197,12 @@ export function TranslateStep({
226
197
  // Only fetch translatable fields if:
227
198
  // 1. We have a source language
228
199
  // 2. It's different from the last one we fetched for
229
- // 3. We're not currently publishing (to avoid conflicts)
200
+ // 3. We're not currently saving (to avoid conflicts)
230
201
  // 4. We're not currently loading or translating
231
202
  if (
232
203
  sourceLanguage &&
233
204
  sourceLanguage !== lastFetchedSourceLanguage &&
234
- !isPublishing &&
205
+ !isSaving &&
235
206
  !isLoading &&
236
207
  !isTranslating
237
208
  ) {
@@ -242,7 +213,7 @@ export function TranslateStep({
242
213
  fetchTranslatableFields,
243
214
  sourceLanguage,
244
215
  lastFetchedSourceLanguage,
245
- isPublishing,
216
+ isSaving,
246
217
  isLoading,
247
218
  isTranslating,
248
219
  ]);
@@ -272,7 +243,7 @@ ${documentContent.substring(0, 1000)}`;
272
243
  },
273
244
  ],
274
245
  { editContext, createAiContext: createWizardAiContext },
275
- { model: "gpt-4.1" },
246
+ { model: step.fields.aiModel || undefined },
276
247
  { signal: abortController.signal },
277
248
  (response: any) => {
278
249
  try {
@@ -403,7 +374,7 @@ Return your response as a JSON object in this exact format:
403
374
  },
404
375
  ],
405
376
  { editContext, createAiContext: createWizardAiContext },
406
- { model: "gpt-4.1", stream: false },
377
+ { model: step.fields.aiModel || undefined, stream: false },
407
378
  { signal: abortController.signal },
408
379
  );
409
380
 
@@ -519,7 +490,7 @@ Return your response as a JSON object in this exact format:
519
490
  },
520
491
  ],
521
492
  { editContext, createAiContext: createWizardAiContext },
522
- { model: "gpt-4.1", stream: false },
493
+ { model: step.fields.aiModel || undefined, stream: false },
523
494
  { signal: abortController.signal },
524
495
  );
525
496
 
@@ -620,7 +591,7 @@ Return your response as a JSON object in this exact format:
620
591
  },
621
592
  ],
622
593
  { editContext, createAiContext: createWizardAiContext },
623
- { model: "gpt-4.1", stream: false },
594
+ { model: step.fields.aiModel || undefined, stream: false },
624
595
  { signal: abortController.signal },
625
596
  );
626
597
 
@@ -683,6 +654,26 @@ Return your response as a JSON object in this exact format:
683
654
  }
684
655
  };
685
656
 
657
+ const isRichTextField = (fieldType: string): boolean => {
658
+ return (
659
+ fieldType?.toLowerCase() === "rich text" ||
660
+ fieldType?.toLowerCase() === "html"
661
+ );
662
+ };
663
+
664
+ const toggleFieldEdit = (itemId: string, fieldId: string) => {
665
+ const fieldKey = `${itemId}-${fieldId}`;
666
+ setEditingFields((prev) => {
667
+ const newSet = new Set(prev);
668
+ if (newSet.has(fieldKey)) {
669
+ newSet.delete(fieldKey);
670
+ } else {
671
+ newSet.add(fieldKey);
672
+ }
673
+ return newSet;
674
+ });
675
+ };
676
+
686
677
  const handleFieldChange = (
687
678
  itemId: string,
688
679
  fieldId: string,
@@ -704,14 +695,14 @@ Return your response as a JSON object in this exact format:
704
695
  }));
705
696
  };
706
697
 
707
- const handleConfirmTranslation = async () => {
698
+ const handleSaveTranslations = async () => {
708
699
  if (!editContext || !currentItem || !targetLanguage?.languageCode) {
709
- console.error("Missing required data for publishing translations");
700
+ console.error("Missing required data for saving translations");
710
701
  return;
711
702
  }
712
703
 
713
- setIsPublishing(true);
714
- setPublishResults({ success: [], errors: [] });
704
+ setIsSaving(true);
705
+ setSaveResults({ success: [], errors: [] });
715
706
 
716
707
  try {
717
708
  // Get all items that have translations
@@ -721,7 +712,7 @@ Return your response as a JSON object in this exact format:
721
712
  );
722
713
 
723
714
  console.log(
724
- `Publishing translations for ${itemsToTranslate.length} items to language: ${targetLanguage.languageCode}`,
715
+ `Saving translations for ${itemsToTranslate.length} items to language: ${targetLanguage.languageCode}`,
725
716
  );
726
717
 
727
718
  for (const [itemId, translatedFieldsList] of itemsToTranslate) {
@@ -765,7 +756,7 @@ Return your response as a JSON object in this exact format:
765
756
  });
766
757
  }
767
758
 
768
- setPublishResults((prev) => ({
759
+ setSaveResults((prev) => ({
769
760
  ...prev,
770
761
  success: [
771
762
  ...prev.success,
@@ -774,7 +765,7 @@ Return your response as a JSON object in this exact format:
774
765
  }));
775
766
  } catch (error) {
776
767
  console.error(`Error translating item ${itemId}:`, error);
777
- setPublishResults((prev) => ({
768
+ setSaveResults((prev) => ({
778
769
  ...prev,
779
770
  errors: [
780
771
  ...prev.errors,
@@ -784,10 +775,10 @@ Return your response as a JSON object in this exact format:
784
775
  }
785
776
  }
786
777
 
787
- console.log("Translation publishing completed");
778
+ console.log("Translation saving completed");
788
779
  } catch (error) {
789
- console.error("Error during translation publishing:", error);
790
- setPublishResults((prev) => ({
780
+ console.error("Error during translation saving:", error);
781
+ setSaveResults((prev) => ({
791
782
  ...prev,
792
783
  errors: [
793
784
  ...prev.errors,
@@ -795,34 +786,34 @@ Return your response as a JSON object in this exact format:
795
786
  ],
796
787
  }));
797
788
  } finally {
798
- setIsPublishing(false);
789
+ setIsSaving(false);
799
790
  }
800
791
  };
801
792
 
802
- // Check if step is completed (has translations and they've been published successfully)
793
+ // Check if step is completed (has translations and they've been saved successfully)
803
794
  useEffect(() => {
804
795
  const hasTranslations = Object.values(translatedFields).some((fields) =>
805
796
  fields.some((field) => field.translatedValue.trim() !== ""),
806
797
  );
807
- const hasSuccessfulPublish = publishResults.success.length > 0;
798
+ const hasSuccessfulSave = saveResults.success.length > 0;
808
799
 
809
- // Step is completed if we have translations and at least some have been published successfully
810
- setStepCompleted(hasTranslations && hasSuccessfulPublish);
811
- }, [translatedFields, publishResults, setStepCompleted]);
800
+ // Step is completed if we have translations and at least some have been saved successfully
801
+ setStepCompleted(hasTranslations && hasSuccessfulSave);
802
+ }, [translatedFields, saveResults, setStepCompleted]);
812
803
 
813
- // Save translated fields and publish results to wizard data
804
+ // Save translated fields and save results to wizard data
814
805
  useEffect(() => {
815
806
  setData((prev) => ({
816
807
  ...prev,
817
808
  translatedFields,
818
- publishResults,
809
+ saveResults,
819
810
  targetLanguage,
820
811
  detectedLanguageName,
821
812
  sourceLanguage,
822
813
  }));
823
814
  }, [
824
815
  translatedFields,
825
- publishResults,
816
+ saveResults,
826
817
  targetLanguage,
827
818
  detectedLanguageName,
828
819
  sourceLanguage,
@@ -852,13 +843,14 @@ Return your response as a JSON object in this exact format:
852
843
  <label className="mb-2 block text-sm font-medium">
853
844
  Source Language
854
845
  </label>
855
- <SimpleLanguageSelector
846
+ <LanguageSelector
856
847
  selectedLanguage={sourceLanguage}
857
848
  onLanguageSelected={(language: Language) =>
858
849
  setSourceLanguage(language.languageCode)
859
850
  }
860
851
  disabled={false}
861
- availableLanguages={availableSourceLanguages}
852
+ showAllLanguagesSwitch={false}
853
+ showAllLanguages={true}
862
854
  />
863
855
  <p className="mt-1 text-xs text-gray-600">
864
856
  Select the language of the content to translate from
@@ -883,15 +875,14 @@ Return your response as a JSON object in this exact format:
883
875
  )}
884
876
  </div>
885
877
  <div className="relative">
886
- <SimpleLanguageSelector
878
+ <LanguageSelector
887
879
  selectedLanguage={targetLanguage?.languageCode || ""}
888
880
  onLanguageSelected={(language: Language) =>
889
881
  setTargetLanguage(language)
890
882
  }
891
883
  disabled={false}
892
- className={
893
- detectedLanguageName ? "border-green-200 bg-green-50" : ""
894
- }
884
+ showAllLanguagesSwitch={false}
885
+ showAllLanguages={true}
895
886
  />
896
887
  {detectedLanguageName && (
897
888
  <button
@@ -944,13 +935,43 @@ Return your response as a JSON object in this exact format:
944
935
  Translating fields...
945
936
  </div>
946
937
  </div>
947
- ) : isPublishing ? (
938
+ ) : isSaving ? (
948
939
  <div className="py-8 text-center">
949
940
  <div className="flex items-center justify-center gap-2">
950
941
  <RefreshCw className="h-4 w-4 animate-spin" />
951
- Publishing translations...
942
+ Saving translations...
952
943
  </div>
953
944
  </div>
945
+ ) : saveResults.success.length > 0 || saveResults.errors.length > 0 ? (
946
+ <div className="space-y-4">
947
+ {saveResults.success.length > 0 && (
948
+ <div className="rounded-lg border border-green-200 bg-green-50 p-4">
949
+ <div className="mb-2 flex items-center gap-2 font-medium text-green-800">
950
+ <CheckCircle className="h-4 w-4" />
951
+ Successfully Saved ({saveResults.success.length} items)
952
+ </div>
953
+ <ul className="list-inside list-disc text-sm text-green-700">
954
+ {saveResults.success.map((item, index) => (
955
+ <li key={index}>{item}</li>
956
+ ))}
957
+ </ul>
958
+ </div>
959
+ )}
960
+
961
+ {saveResults.errors.length > 0 && (
962
+ <div className="rounded-lg border border-red-200 bg-red-50 p-4">
963
+ <div className="mb-2 flex items-center gap-2 font-medium text-red-800">
964
+ <AlertCircle className="h-4 w-4" />
965
+ Errors ({saveResults.errors.length})
966
+ </div>
967
+ <ul className="list-inside list-disc text-sm text-red-700">
968
+ {saveResults.errors.map((error, index) => (
969
+ <li key={index}>{error}</li>
970
+ ))}
971
+ </ul>
972
+ </div>
973
+ )}
974
+ </div>
954
975
  ) : translatedCount > 0 ? (
955
976
  <div className="space-y-6">
956
977
  {Object.entries(translatableFields).map(([itemId, fields]) => (
@@ -964,20 +985,18 @@ Return your response as a JSON object in this exact format:
964
985
  (f) => f.fieldId === field.fieldId,
965
986
  );
966
987
  return (
967
- <div
968
- key={field.fieldId}
969
- className="grid grid-cols-2 gap-4"
970
- >
971
- <div className="flex flex-col">
972
- <label className="mb-1 block text-xs font-medium">
973
- {field.fieldName}
974
- </label>
975
- <div className="min-h-[100px] flex-1 rounded border bg-gray-50 p-3 text-xs">
976
- {field.value}
988
+ <div key={field.fieldId}>
989
+ {/* Headers Row */}
990
+ <div className="grid grid-cols-2 gap-4">
991
+ {/* Source Field Header */}
992
+ <div>
993
+ <label className="block text-xs font-medium">
994
+ {field.fieldName} ({field.fieldType})
995
+ </label>
977
996
  </div>
978
- </div>
979
- <div className="flex flex-col">
980
- <div className="mb-1 flex items-center justify-between">
997
+
998
+ {/* Translation Field Header */}
999
+ <div className="flex items-center justify-between">
981
1000
  <div className="flex items-center gap-2">
982
1001
  <label className="block text-xs font-medium">
983
1002
  {field.fieldName}
@@ -1003,91 +1022,205 @@ Return your response as a JSON object in this exact format:
1003
1022
  )}
1004
1023
  </div>
1005
1024
  <div className="flex items-center gap-1">
1006
- {translatedField?.translatedValue && (
1025
+ {!editingFields.has(
1026
+ `${itemId}-${field.fieldId}`,
1027
+ ) &&
1028
+ translatedField?.translatedValue && (
1029
+ <button
1030
+ type="button"
1031
+ onClick={() =>
1032
+ checkTranslationCompleteness(
1033
+ itemId,
1034
+ field.fieldId,
1035
+ field.value,
1036
+ translatedField.translatedValue,
1037
+ field.fieldName,
1038
+ )
1039
+ }
1040
+ disabled={
1041
+ checkingFields.has(
1042
+ `${itemId}-${field.fieldId}`,
1043
+ ) ||
1044
+ !translatedField.translatedValue.trim()
1045
+ }
1046
+ 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"
1047
+ title="Check translation completeness"
1048
+ >
1049
+ {checkingFields.has(
1050
+ `${itemId}-${field.fieldId}`,
1051
+ ) ? (
1052
+ <RefreshCw className="h-3 w-3 animate-spin" />
1053
+ ) : (
1054
+ <CheckCircle className="h-3 w-3" />
1055
+ )}
1056
+ Check
1057
+ </button>
1058
+ )}
1059
+ {!editingFields.has(
1060
+ `${itemId}-${field.fieldId}`,
1061
+ ) && (
1007
1062
  <button
1008
1063
  type="button"
1009
1064
  onClick={() =>
1010
- checkTranslationCompleteness(
1065
+ handleSingleFieldTranslation(
1011
1066
  itemId,
1012
1067
  field.fieldId,
1013
- field.value,
1014
- translatedField.translatedValue,
1015
- field.fieldName,
1068
+ field,
1016
1069
  )
1017
1070
  }
1018
1071
  disabled={
1019
- checkingFields.has(
1072
+ !targetLanguage?.languageCode ||
1073
+ translatingFields.has(
1020
1074
  `${itemId}-${field.fieldId}`,
1021
- ) || !translatedField.translatedValue.trim()
1075
+ ) ||
1076
+ isTranslating
1022
1077
  }
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"
1078
+ 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"
1079
+ title="Retranslate this field"
1025
1080
  >
1026
- {checkingFields.has(
1081
+ {translatingFields.has(
1027
1082
  `${itemId}-${field.fieldId}`,
1028
1083
  ) ? (
1029
1084
  <RefreshCw className="h-3 w-3 animate-spin" />
1030
1085
  ) : (
1031
- <CheckCircle className="h-3 w-3" />
1086
+ <Wand2 className="h-3 w-3" />
1032
1087
  )}
1033
- Check
1088
+ Retranslate
1089
+ </button>
1090
+ )}
1091
+ {editingFields.has(
1092
+ `${itemId}-${field.fieldId}`,
1093
+ ) ? (
1094
+ <>
1095
+ <button
1096
+ type="button"
1097
+ onClick={() =>
1098
+ toggleFieldEdit(itemId, field.fieldId)
1099
+ }
1100
+ className="flex items-center gap-1 rounded px-2 py-1 text-xs text-green-600 hover:bg-green-50 hover:text-green-800"
1101
+ title="Save changes"
1102
+ >
1103
+ <Save className="h-3 w-3" strokeWidth={1} />
1104
+ Save
1105
+ </button>
1106
+ <button
1107
+ type="button"
1108
+ onClick={() =>
1109
+ toggleFieldEdit(itemId, field.fieldId)
1110
+ }
1111
+ className="flex items-center gap-1 rounded px-2 py-1 text-xs text-gray-600 hover:bg-gray-50 hover:text-gray-800"
1112
+ title="Cancel edit"
1113
+ >
1114
+ <X className="h-3 w-3" strokeWidth={1} />
1115
+ Cancel
1116
+ </button>
1117
+ </>
1118
+ ) : (
1119
+ <button
1120
+ type="button"
1121
+ onClick={() =>
1122
+ toggleFieldEdit(itemId, field.fieldId)
1123
+ }
1124
+ className="flex items-center gap-1 rounded px-2 py-1 text-xs text-gray-600 hover:bg-gray-50 hover:text-gray-800"
1125
+ title="Edit translation"
1126
+ >
1127
+ <Edit className="h-3 w-3" strokeWidth={1} />
1128
+ Edit
1034
1129
  </button>
1035
1130
  )}
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
1131
  </div>
1065
1132
  </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:
1133
+ </div>
1134
+
1135
+ {/* Content Row */}
1136
+ <div className="grid grid-cols-2 gap-4">
1137
+ {/* Source Field Content */}
1138
+ <div className="rounded border bg-gray-50 p-1 text-xs">
1139
+ {isRichTextField(field.fieldType) ? (
1140
+ <div
1141
+ className="rich-text-content"
1142
+ dangerouslySetInnerHTML={{
1143
+ __html: field.value,
1144
+ }}
1145
+ />
1146
+ ) : (
1147
+ field.value
1148
+ )}
1149
+ </div>
1150
+
1151
+ {/* Translation Field Content */}
1152
+ <div className="space-y-2">
1153
+ {translatedField?.isComplete === false &&
1154
+ translatedField?.missingSuggestions && (
1155
+ <div className="rounded border border-orange-200 bg-orange-50 p-2">
1156
+ <div className="mb-1 flex items-center gap-1 text-xs font-medium text-orange-800">
1157
+ <Sparkles className="h-3 w-3" />
1158
+ AI Suggestions for Missing Content:
1159
+ </div>
1160
+ <p className="text-xs text-orange-700 select-text">
1161
+ {translatedField.missingSuggestions}
1162
+ </p>
1072
1163
  </div>
1073
- <p className="text-xs text-orange-700 select-text">
1074
- {translatedField.missingSuggestions}
1075
- </p>
1164
+ )}
1165
+
1166
+ {/* Conditionally render editor or readonly view */}
1167
+ {editingFields.has(`${itemId}-${field.fieldId}`) ? (
1168
+ <div>
1169
+ {isRichTextField(field.fieldType) ? (
1170
+ <SimpleRichTextEditor
1171
+ value={
1172
+ translatedField?.translatedValue || ""
1173
+ }
1174
+ onChange={(value: string) =>
1175
+ handleFieldChange(
1176
+ itemId,
1177
+ field.fieldId,
1178
+ value,
1179
+ )
1180
+ }
1181
+ readOnly={false}
1182
+ placeholder="Enter translation here..."
1183
+ className="min-h-[70px] text-xs"
1184
+ />
1185
+ ) : (
1186
+ <InputTextarea
1187
+ value={
1188
+ translatedField?.translatedValue || ""
1189
+ }
1190
+ onChange={(e) =>
1191
+ handleFieldChange(
1192
+ itemId,
1193
+ field.fieldId,
1194
+ e.target.value,
1195
+ )
1196
+ }
1197
+ placeholder="Enter translation here..."
1198
+ rows={4}
1199
+ className="h-full min-h-[70px] w-full text-xs"
1200
+ />
1201
+ )}
1202
+ </div>
1203
+ ) : (
1204
+ <div className="h-full min-h-[70px] rounded border bg-gray-50 p-1 text-xs">
1205
+ {translatedField?.translatedValue ? (
1206
+ isRichTextField(field.fieldType) ? (
1207
+ <div
1208
+ className="rich-text-content"
1209
+ dangerouslySetInnerHTML={{
1210
+ __html: translatedField.translatedValue,
1211
+ }}
1212
+ />
1213
+ ) : (
1214
+ translatedField.translatedValue
1215
+ )
1216
+ ) : (
1217
+ <span className="text-gray-400">
1218
+ Translation will appear here...
1219
+ </span>
1220
+ )}
1076
1221
  </div>
1077
1222
  )}
1078
- <InputTextarea
1079
- value={translatedField?.translatedValue || ""}
1080
- onChange={(e) =>
1081
- handleFieldChange(
1082
- itemId,
1083
- field.fieldId,
1084
- e.target.value,
1085
- )
1086
- }
1087
- placeholder="Translation will appear here..."
1088
- rows={4}
1089
- className="min-h-[100px] w-full flex-1 text-xs"
1090
- />
1223
+ </div>
1091
1224
  </div>
1092
1225
  </div>
1093
1226
  );
@@ -1105,79 +1238,50 @@ Return your response as a JSON object in this exact format:
1105
1238
  )}
1106
1239
  </WizardBox>
1107
1240
 
1108
- {translatedCount > 0 && (
1109
- <WizardBox
1110
- title="Publish Translations"
1111
- description="Create language versions and apply translations"
1112
- icon={<CheckCircle className="h-5 w-5" />}
1113
- >
1114
- <div className="space-y-4">
1115
- <div className="rounded-lg border border-blue-200 bg-blue-50 p-4">
1116
- <h4 className="mb-2 font-medium text-blue-900">
1117
- Ready to Publish
1118
- </h4>
1119
- <p className="text-sm text-blue-800">
1120
- {translatedCount} fields ready to be published to{" "}
1121
- <strong>{targetLanguage?.name}</strong> language versions
1122
- {detectedLanguageName &&
1123
- targetLanguage?.name
1124
- ?.toLowerCase()
1125
- .includes(detectedLanguageName.toLowerCase()) && (
1126
- <span className="text-green-700"> (auto-detected)</span>
1127
- )}
1128
- . This will create new language versions for all translated
1129
- items and update their field values.
1130
- </p>
1131
- </div>
1132
-
1133
- {(publishResults.success.length > 0 ||
1134
- publishResults.errors.length > 0) && (
1135
- <div className="space-y-2">
1136
- {publishResults.success.length > 0 && (
1137
- <div className="rounded-lg border border-green-200 bg-green-50 p-3">
1138
- <div className="mb-1 flex items-center gap-2 font-medium text-green-800">
1139
- <CheckCircle className="h-4 w-4" />
1140
- Successfully Published ({publishResults.success.length})
1141
- </div>
1142
- <ul className="list-inside list-disc text-sm text-green-700">
1143
- {publishResults.success.map((item, index) => (
1144
- <li key={index}>{item}</li>
1145
- ))}
1146
- </ul>
1147
- </div>
1148
- )}
1149
-
1150
- {publishResults.errors.length > 0 && (
1151
- <div className="rounded-lg border border-red-200 bg-red-50 p-3">
1152
- <div className="mb-1 flex items-center gap-2 font-medium text-red-800">
1153
- <AlertCircle className="h-4 w-4" />
1154
- Errors ({publishResults.errors.length})
1155
- </div>
1156
- <ul className="list-inside list-disc text-sm text-red-700">
1157
- {publishResults.errors.map((error, index) => (
1158
- <li key={index}>{error}</li>
1159
- ))}
1160
- </ul>
1161
- </div>
1162
- )}
1241
+ {translatedCount > 0 &&
1242
+ saveResults.success.length === 0 &&
1243
+ saveResults.errors.length === 0 && (
1244
+ <WizardBox
1245
+ title="Save Translations"
1246
+ description="Create language versions and apply translations"
1247
+ icon={<CheckCircle className="h-5 w-5" />}
1248
+ >
1249
+ <div className="space-y-4">
1250
+ <div className="rounded-lg border border-blue-200 bg-blue-50 p-4">
1251
+ <h4 className="mb-2 font-medium text-blue-900">
1252
+ Ready to Save
1253
+ </h4>
1254
+ <p className="text-sm text-blue-800">
1255
+ {translatedCount} fields ready to be saved to{" "}
1256
+ <strong>{targetLanguage?.name}</strong> language versions
1257
+ {detectedLanguageName &&
1258
+ targetLanguage?.name
1259
+ ?.toLowerCase()
1260
+ .includes(detectedLanguageName.toLowerCase()) && (
1261
+ <span className="text-green-700"> (auto-detected)</span>
1262
+ )}
1263
+ . This will create new language versions for all translated
1264
+ items and update their field values.
1265
+ </p>
1163
1266
  </div>
1164
- )}
1165
1267
 
1166
- <ActionButton
1167
- onClick={handleConfirmTranslation}
1168
- isLoading={isPublishing}
1169
- disabled={translatedCount === 0 || !targetLanguage?.languageCode}
1170
- className="w-full"
1171
- variant="outline"
1172
- >
1173
- <CheckCircle className="mr-2 h-4 w-4" />
1174
- {isPublishing
1175
- ? "Publishing Translations..."
1176
- : `Publish ${translatedCount} Translations to ${targetLanguage?.name || "Target Language"}`}
1177
- </ActionButton>
1178
- </div>
1179
- </WizardBox>
1180
- )}
1268
+ <ActionButton
1269
+ onClick={handleSaveTranslations}
1270
+ isLoading={isSaving}
1271
+ disabled={
1272
+ translatedCount === 0 || !targetLanguage?.languageCode
1273
+ }
1274
+ className="w-full"
1275
+ variant="outline"
1276
+ >
1277
+ <CheckCircle className="mr-2 h-4 w-4" />
1278
+ {isSaving
1279
+ ? "Saving Translations..."
1280
+ : `Save ${translatedCount} Translations to ${targetLanguage?.name || "Target Language"}`}
1281
+ </ActionButton>
1282
+ </div>
1283
+ </WizardBox>
1284
+ )}
1181
1285
  </div>
1182
1286
  );
1183
1287
  }