@alpaca-editor/core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. package/.prettierrc +3 -0
  2. package/eslint.config.mjs +4 -0
  3. package/images/bg-shape-black.webp +0 -0
  4. package/package.json +52 -0
  5. package/src/client-components/api.ts +6 -0
  6. package/src/client-components/index.ts +19 -0
  7. package/src/components/ActionButton.tsx +43 -0
  8. package/src/components/Error.tsx +57 -0
  9. package/src/config/config.tsx +737 -0
  10. package/src/config/types.ts +263 -0
  11. package/src/editor/ComponentInfo.tsx +77 -0
  12. package/src/editor/ConfirmationDialog.tsx +103 -0
  13. package/src/editor/ContentTree.tsx +654 -0
  14. package/src/editor/ContextMenu.tsx +155 -0
  15. package/src/editor/Editor.tsx +91 -0
  16. package/src/editor/EditorWarning.tsx +34 -0
  17. package/src/editor/EditorWarnings.tsx +33 -0
  18. package/src/editor/FieldEditorPopup.tsx +65 -0
  19. package/src/editor/FieldHistory.tsx +74 -0
  20. package/src/editor/FieldList.tsx +190 -0
  21. package/src/editor/FieldListField.tsx +387 -0
  22. package/src/editor/FieldListFieldWithFallbacks.tsx +211 -0
  23. package/src/editor/FloatingToolbar.tsx +163 -0
  24. package/src/editor/ImageEditor.tsx +129 -0
  25. package/src/editor/InsertMenu.tsx +332 -0
  26. package/src/editor/ItemInfo.tsx +90 -0
  27. package/src/editor/LinkEditorDialog.tsx +192 -0
  28. package/src/editor/MainLayout.tsx +94 -0
  29. package/src/editor/NewEditorClient.tsx +11 -0
  30. package/src/editor/PictureCropper.tsx +505 -0
  31. package/src/editor/PictureEditor.tsx +206 -0
  32. package/src/editor/PictureEditorDialog.tsx +381 -0
  33. package/src/editor/PublishDialog.ignore +74 -0
  34. package/src/editor/ScrollingContentTree.tsx +47 -0
  35. package/src/editor/Terminal.tsx +215 -0
  36. package/src/editor/Titlebar.tsx +23 -0
  37. package/src/editor/ai/AiPopup.tsx +59 -0
  38. package/src/editor/ai/AiResponseMessage.tsx +82 -0
  39. package/src/editor/ai/AiTerminal.tsx +450 -0
  40. package/src/editor/ai/AiToolCall.tsx +46 -0
  41. package/src/editor/ai/EditorAiTerminal.tsx +20 -0
  42. package/src/editor/ai/editorAiContext.ts +18 -0
  43. package/src/editor/client/DialogContext.tsx +49 -0
  44. package/src/editor/client/EditorClient.tsx +1831 -0
  45. package/src/editor/client/GenericDialog.tsx +50 -0
  46. package/src/editor/client/editContext.ts +330 -0
  47. package/src/editor/client/helpers.ts +44 -0
  48. package/src/editor/client/itemsRepository.ts +391 -0
  49. package/src/editor/client/operations.ts +610 -0
  50. package/src/editor/client/pageModelBuilder.ts +182 -0
  51. package/src/editor/commands/commands.ts +23 -0
  52. package/src/editor/commands/componentCommands.tsx +408 -0
  53. package/src/editor/commands/createVersionCommand.ts +33 -0
  54. package/src/editor/commands/deleteVersionCommand.ts +71 -0
  55. package/src/editor/commands/itemCommands.tsx +186 -0
  56. package/src/editor/commands/localizeItem/LocalizeItemDialog.tsx +201 -0
  57. package/src/editor/commands/undo.ts +39 -0
  58. package/src/editor/component-designer/ComponentDesigner.tsx +70 -0
  59. package/src/editor/component-designer/ComponentDesignerAiTerminal.tsx +11 -0
  60. package/src/editor/component-designer/ComponentDesignerMenu.tsx +91 -0
  61. package/src/editor/component-designer/ComponentEditor.tsx +97 -0
  62. package/src/editor/component-designer/ComponentRenderingCodeEditor.tsx +31 -0
  63. package/src/editor/component-designer/ComponentRenderingEditor.tsx +104 -0
  64. package/src/editor/component-designer/ComponentsDropdown.tsx +39 -0
  65. package/src/editor/component-designer/PlaceholdersEditor.tsx +183 -0
  66. package/src/editor/component-designer/RenderingsDropdown.tsx +36 -0
  67. package/src/editor/component-designer/TemplateEditor.tsx +236 -0
  68. package/src/editor/component-designer/aiContext.ts +23 -0
  69. package/src/editor/componentTreeHelper.tsx +114 -0
  70. package/src/editor/control-center/ControlCenterMenu.tsx +71 -0
  71. package/src/editor/control-center/IndexOverview.tsx +50 -0
  72. package/src/editor/control-center/IndexSettings.tsx +266 -0
  73. package/src/editor/control-center/Status.tsx +7 -0
  74. package/src/editor/editor-warnings/ItemLocked.tsx +63 -0
  75. package/src/editor/editor-warnings/NoLanguageWriteAccess.tsx +22 -0
  76. package/src/editor/editor-warnings/NoWorkflowWriteAccess.tsx +23 -0
  77. package/src/editor/editor-warnings/NoWriteAccess.tsx +15 -0
  78. package/src/editor/editor-warnings/ValidationErrors.tsx +54 -0
  79. package/src/editor/field-types/AttachmentEditor.tsx +9 -0
  80. package/src/editor/field-types/CheckboxEditor.tsx +47 -0
  81. package/src/editor/field-types/DropLinkEditor.tsx +75 -0
  82. package/src/editor/field-types/DropListEditor.tsx +84 -0
  83. package/src/editor/field-types/ImageFieldEditor.tsx +65 -0
  84. package/src/editor/field-types/InternalLinkFieldEditor.tsx +112 -0
  85. package/src/editor/field-types/LinkFieldEditor.tsx +85 -0
  86. package/src/editor/field-types/MultiLineText.tsx +63 -0
  87. package/src/editor/field-types/PictureFieldEditor.tsx +121 -0
  88. package/src/editor/field-types/RawEditor.tsx +53 -0
  89. package/src/editor/field-types/ReactQuill.tsx +580 -0
  90. package/src/editor/field-types/RichTextEditor.tsx +22 -0
  91. package/src/editor/field-types/RichTextEditorComponent.tsx +108 -0
  92. package/src/editor/field-types/SingleLineText.tsx +150 -0
  93. package/src/editor/field-types/TreeListEditor.tsx +261 -0
  94. package/src/editor/fieldTypes.ts +140 -0
  95. package/src/editor/media-selector/AiImageSearch.tsx +186 -0
  96. package/src/editor/media-selector/AiImageSearchPrompt.tsx +95 -0
  97. package/src/editor/media-selector/MediaSelector.tsx +42 -0
  98. package/src/editor/media-selector/Preview.tsx +14 -0
  99. package/src/editor/media-selector/Thumbnails.tsx +48 -0
  100. package/src/editor/media-selector/TreeSelector.tsx +292 -0
  101. package/src/editor/media-selector/UploadZone.tsx +137 -0
  102. package/src/editor/menubar/ActionsMenu.tsx +47 -0
  103. package/src/editor/menubar/ActiveUsers.tsx +17 -0
  104. package/src/editor/menubar/ApproveAndPublish.tsx +18 -0
  105. package/src/editor/menubar/BrowseHistory.tsx +37 -0
  106. package/src/editor/menubar/ItemLanguageVersion.tsx +52 -0
  107. package/src/editor/menubar/LanguageSelector.tsx +152 -0
  108. package/src/editor/menubar/Menu.tsx +83 -0
  109. package/src/editor/menubar/NavButtons.tsx +74 -0
  110. package/src/editor/menubar/PageSelector.tsx +139 -0
  111. package/src/editor/menubar/PageViewerControls.tsx +99 -0
  112. package/src/editor/menubar/Separator.tsx +12 -0
  113. package/src/editor/menubar/SiteInfo.tsx +53 -0
  114. package/src/editor/menubar/User.tsx +27 -0
  115. package/src/editor/menubar/VersionSelector.tsx +143 -0
  116. package/src/editor/page-editor-chrome/CommentHighlighting.tsx +287 -0
  117. package/src/editor/page-editor-chrome/CommentHighlightings.tsx +35 -0
  118. package/src/editor/page-editor-chrome/FieldActionIndicator.tsx +44 -0
  119. package/src/editor/page-editor-chrome/FieldActionIndicators.tsx +23 -0
  120. package/src/editor/page-editor-chrome/FieldEditedIndicator.tsx +64 -0
  121. package/src/editor/page-editor-chrome/FieldEditedIndicators.tsx +35 -0
  122. package/src/editor/page-editor-chrome/FrameMenu.tsx +263 -0
  123. package/src/editor/page-editor-chrome/FrameMenus.tsx +48 -0
  124. package/src/editor/page-editor-chrome/InlineEditor.tsx +147 -0
  125. package/src/editor/page-editor-chrome/LockedFieldIndicator.tsx +61 -0
  126. package/src/editor/page-editor-chrome/NoLayout.tsx +36 -0
  127. package/src/editor/page-editor-chrome/PageEditorChrome.tsx +119 -0
  128. package/src/editor/page-editor-chrome/PictureEditorOverlay.tsx +154 -0
  129. package/src/editor/page-editor-chrome/PlaceholderDropZone.tsx +171 -0
  130. package/src/editor/page-editor-chrome/PlaceholderDropZones.tsx +233 -0
  131. package/src/editor/page-viewer/DeviceToolbar.tsx +70 -0
  132. package/src/editor/page-viewer/EditorForm.tsx +247 -0
  133. package/src/editor/page-viewer/MiniMap.tsx +351 -0
  134. package/src/editor/page-viewer/PageViewer.tsx +127 -0
  135. package/src/editor/page-viewer/PageViewerFrame.tsx +1030 -0
  136. package/src/editor/page-viewer/pageViewContext.ts +186 -0
  137. package/src/editor/pageModel.ts +191 -0
  138. package/src/editor/picture-shared.tsx +53 -0
  139. package/src/editor/reviews/Comment.tsx +265 -0
  140. package/src/editor/reviews/Comments.tsx +50 -0
  141. package/src/editor/reviews/PreviewInfo.tsx +35 -0
  142. package/src/editor/reviews/Reviews.tsx +280 -0
  143. package/src/editor/reviews/reviewCommands.tsx +47 -0
  144. package/src/editor/reviews/useReviews.tsx +70 -0
  145. package/src/editor/services/aiService.ts +155 -0
  146. package/src/editor/services/componentDesignerService.ts +151 -0
  147. package/src/editor/services/contentService.ts +159 -0
  148. package/src/editor/services/editService.ts +462 -0
  149. package/src/editor/services/indexService.ts +24 -0
  150. package/src/editor/services/reviewsService.ts +45 -0
  151. package/src/editor/services/serviceHelper.ts +95 -0
  152. package/src/editor/services/systemService.ts +5 -0
  153. package/src/editor/services/translationService.ts +21 -0
  154. package/src/editor/services-server/api.ts +150 -0
  155. package/src/editor/services-server/graphQL.ts +106 -0
  156. package/src/editor/sidebar/ComponentPalette.tsx +146 -0
  157. package/src/editor/sidebar/ComponentTree.tsx +512 -0
  158. package/src/editor/sidebar/ComponentTree2.tsxx +490 -0
  159. package/src/editor/sidebar/Debug.tsx +105 -0
  160. package/src/editor/sidebar/DictionaryEditor.tsx +261 -0
  161. package/src/editor/sidebar/EditHistory.tsx +134 -0
  162. package/src/editor/sidebar/GraphQL.tsx +164 -0
  163. package/src/editor/sidebar/Insert.tsx +35 -0
  164. package/src/editor/sidebar/MainContentTree.tsx +95 -0
  165. package/src/editor/sidebar/Performance.tsx +53 -0
  166. package/src/editor/sidebar/Sessions.tsx +35 -0
  167. package/src/editor/sidebar/Sidebar.tsx +20 -0
  168. package/src/editor/sidebar/SidebarView.tsx +150 -0
  169. package/src/editor/sidebar/Translations.tsx +276 -0
  170. package/src/editor/sidebar/Validation.tsx +102 -0
  171. package/src/editor/sidebar/ViewSelector.tsx +49 -0
  172. package/src/editor/sidebar/Workbox.tsx +209 -0
  173. package/src/editor/ui/CenteredMessage.tsx +7 -0
  174. package/src/editor/ui/CopyToClipboardButton.tsx +23 -0
  175. package/src/editor/ui/DialogButtons.tsx +11 -0
  176. package/src/editor/ui/Icons.tsx +585 -0
  177. package/src/editor/ui/ItemNameDialog.tsx +94 -0
  178. package/src/editor/ui/ItemNameDialogNew.tsx +118 -0
  179. package/src/editor/ui/ItemSearch.tsx +173 -0
  180. package/src/editor/ui/PerfectTree.tsx +550 -0
  181. package/src/editor/ui/Section.tsx +35 -0
  182. package/src/editor/ui/SimpleIconButton.tsx +43 -0
  183. package/src/editor/ui/SimpleMenu.tsx +48 -0
  184. package/src/editor/ui/SimpleTable.tsx +63 -0
  185. package/src/editor/ui/SimpleTabs.tsx +55 -0
  186. package/src/editor/ui/SimpleToolbar.tsx +7 -0
  187. package/src/editor/ui/Spinner.tsx +7 -0
  188. package/src/editor/ui/Splitter.tsx +247 -0
  189. package/src/editor/ui/StackedPanels.tsx +134 -0
  190. package/src/editor/ui/Toolbar.tsx +7 -0
  191. package/src/editor/utils/id-helper.ts +3 -0
  192. package/src/editor/utils/insertOptions.ts +69 -0
  193. package/src/editor/utils/itemutils.ts +29 -0
  194. package/src/editor/utils/useMemoDebug.ts +28 -0
  195. package/src/editor/utils.ts +435 -0
  196. package/src/editor/views/CompareView.tsx +256 -0
  197. package/src/editor/views/EditView.tsx +27 -0
  198. package/src/editor/views/ItemEditor.tsx +58 -0
  199. package/src/editor/views/SingleEditView.tsx +44 -0
  200. package/src/fonts/Geist-Black.woff2 +0 -0
  201. package/src/fonts/Geist-Bold.woff2 +0 -0
  202. package/src/fonts/Geist-ExtraBold.woff2 +0 -0
  203. package/src/fonts/Geist-ExtraLight.woff2 +0 -0
  204. package/src/fonts/Geist-Light.woff2 +0 -0
  205. package/src/fonts/Geist-Medium.woff2 +0 -0
  206. package/src/fonts/Geist-Regular.woff2 +0 -0
  207. package/src/fonts/Geist-SemiBold.woff2 +0 -0
  208. package/src/fonts/Geist-Thin.woff2 +0 -0
  209. package/src/fonts/Geist[wght].woff2 +0 -0
  210. package/src/index.ts +7 -0
  211. package/src/page-wizard/PageWizard.tsx +163 -0
  212. package/src/page-wizard/SelectWizard.tsx +109 -0
  213. package/src/page-wizard/WizardSteps.tsx +207 -0
  214. package/src/page-wizard/service.ts +35 -0
  215. package/src/page-wizard/startPageWizardCommand.ts +27 -0
  216. package/src/page-wizard/steps/BuildPageStep.tsx +266 -0
  217. package/src/page-wizard/steps/CollectStep.tsx +233 -0
  218. package/src/page-wizard/steps/ComponentTypesSelector.tsx +443 -0
  219. package/src/page-wizard/steps/Components.tsx +193 -0
  220. package/src/page-wizard/steps/CreatePage.tsx +285 -0
  221. package/src/page-wizard/steps/CreatePageAndLayoutStep.tsx +384 -0
  222. package/src/page-wizard/steps/EditButton.tsx +34 -0
  223. package/src/page-wizard/steps/FieldEditor.tsx +102 -0
  224. package/src/page-wizard/steps/Generate.tsx +32 -0
  225. package/src/page-wizard/steps/ImagesStep.tsx +318 -0
  226. package/src/page-wizard/steps/LayoutStep.tsx +228 -0
  227. package/src/page-wizard/steps/SelectStep.tsx +256 -0
  228. package/src/page-wizard/steps/schema.ts +180 -0
  229. package/src/page-wizard/steps/usePageCreator.ts +279 -0
  230. package/src/splash-screen/NewPage.tsx +232 -0
  231. package/src/splash-screen/SectionHeadline.tsx +21 -0
  232. package/src/splash-screen/SplashScreen.tsx +156 -0
  233. package/src/tour/Tour.tsx +558 -0
  234. package/src/tour/default-tour.tsx +300 -0
  235. package/src/tour/preview-tour.tsx +127 -0
  236. package/src/types.ts +302 -0
  237. package/styles.css +476 -0
  238. package/tsconfig.build.json +21 -0
  239. package/tsconfig.json +11 -0
@@ -0,0 +1,450 @@
1
+ "use client";
2
+
3
+ import { useEffect, useRef, useState } from "react";
4
+
5
+ import { TerminalService } from "primereact/terminalservice";
6
+
7
+ import { Terminal } from "../Terminal";
8
+ import { useEditContext } from "../client/editContext";
9
+ import { Dropdown } from "primereact/dropdown";
10
+
11
+ import Cookies from "universal-cookie";
12
+ import { WizardIcon } from "../ui/Icons";
13
+ import { AiResponseMessage } from "./AiResponseMessage";
14
+ import { AiProfile, loadAiProfiles } from "../services/aiService";
15
+ import {
16
+ AddComponentOperation,
17
+ EditFieldOperation,
18
+ EditOperation,
19
+ } from "../../types";
20
+ import { SimpleIconButton } from "../ui/SimpleIconButton";
21
+
22
+ type Message = {
23
+ content: string;
24
+ name: string;
25
+ role: string;
26
+ };
27
+
28
+ type Response = {
29
+ responseText: string;
30
+ editOperations: EditOperation[];
31
+ numInputTokens: number;
32
+ numOutputTokens: number;
33
+ numCachedTokens: number;
34
+ toolCalls?: ToolCall[];
35
+ state: string;
36
+ };
37
+
38
+ type ToolCall = {
39
+ function: {
40
+ name: string;
41
+ arguments: string;
42
+ };
43
+ };
44
+
45
+ export type AiContext = {
46
+ promptData: any;
47
+ endpoint: string;
48
+ callback?: (response: Response) => void;
49
+ };
50
+
51
+ export type AiTerminalOptions = {
52
+ initialPrompt?: string;
53
+ hiddenSystemPrompt?: string;
54
+ };
55
+
56
+ export function AiTerminal({
57
+ closeButton,
58
+ createAiContext,
59
+ defaultProfile,
60
+ options,
61
+ }: {
62
+ closeButton?: React.ReactNode;
63
+ createAiContext: ({ editContext }: { editContext: any }) => AiContext;
64
+ defaultProfile?: string;
65
+ options?: AiTerminalOptions;
66
+ }) {
67
+ const editContext = useEditContext();
68
+ const [showPredefined, setShowPredefined] = useState(false);
69
+
70
+ if (!editContext) return null;
71
+
72
+ const [messages, setMessages] = useState<Message[]>([]);
73
+ const [response, setResponse] = useState<Response>();
74
+ const [model, setModel] = useState<string>();
75
+ const [prompt, setPrompt] = useState("");
76
+ const [profiles, setProfiles] = useState<AiProfile[]>([]);
77
+ const [activeProfile, setActiveProfile] = useState<AiProfile>();
78
+ const [initialPromptExecuted, setInitialPromptExecuted] = useState(false);
79
+ const selection = editContext.selection;
80
+ const terminalRef = useRef<{ submit: () => void }>(null);
81
+
82
+ useEffect(() => {
83
+ if (options?.initialPrompt && !initialPromptExecuted && model) {
84
+ // Set the initial prompt text into the terminal's state.
85
+ console.log("setting prompt", options.initialPrompt);
86
+ setPrompt(options.initialPrompt);
87
+ setInitialPromptExecuted(true);
88
+ // Wait for the Terminal to update, then programmatically submit.
89
+ setTimeout(() => {
90
+ terminalRef.current?.submit();
91
+ }, 100);
92
+ }
93
+ }, [options?.initialPrompt, initialPromptExecuted, model]);
94
+
95
+ useEffect(() => {
96
+ async function fetchProfiles() {
97
+ if (!editContext?.currentItemDescriptor) return;
98
+ const profiles = await loadAiProfiles(editContext.currentItemDescriptor);
99
+ setProfiles(profiles);
100
+ if (!activeProfile)
101
+ setActiveProfile(
102
+ profiles.find((x) => x.name == defaultProfile) || profiles[0]
103
+ );
104
+ }
105
+ fetchProfiles();
106
+ }, [editContext?.currentItemDescriptor]);
107
+
108
+ useEffect(() => {
109
+ if (activeProfile?.defaultModel) setModel(activeProfile.defaultModel);
110
+ }, [activeProfile]);
111
+
112
+ const messagesRef = useRef(messages);
113
+ useEffect(() => {
114
+ messagesRef.current = messages;
115
+ }, [messages]);
116
+
117
+ async function commandHandler(
118
+ text: string,
119
+ callback: (text: React.ReactNode, finished: boolean) => void
120
+ ) {
121
+ const newMessages = [
122
+ ...messagesRef.current,
123
+ { content: text, role: "user", name: "user" },
124
+ ];
125
+
126
+ const context = createAiContext({ editContext });
127
+
128
+ let lastOpIndex = 0;
129
+ const selectedText = editContext?.selectedRange?.text || null;
130
+ if (!activeProfile || !model) return;
131
+
132
+ const messages = [
133
+ ...(options?.hiddenSystemPrompt
134
+ ? [
135
+ {
136
+ role: "system",
137
+ name: "system",
138
+ content: options.hiddenSystemPrompt,
139
+ },
140
+ ]
141
+ : []),
142
+ ...newMessages,
143
+ ];
144
+
145
+ console.log(messages);
146
+
147
+ const response = await executePrompt(
148
+ activeProfile.id,
149
+ messages,
150
+ selection,
151
+ editContext!.sessionId,
152
+ model,
153
+ selectedText,
154
+ context,
155
+ (response: any) => {
156
+ setResponse(response);
157
+ handleResponse(response, callback, false);
158
+ if (response.editOperations.length) {
159
+ if (!editContext) return;
160
+
161
+ const newOps = response.editOperations.slice(lastOpIndex);
162
+
163
+ if (newOps.length === 0) return;
164
+
165
+ const isEditTextFieldOp = (op: EditOperation) => {
166
+ if (op.type !== "edit-field") return false;
167
+ const editFieldOp = op as EditFieldOperation;
168
+ return (
169
+ editFieldOp.fieldType &&
170
+ editFieldOp.fieldType.indexOf("text") !== -1
171
+ );
172
+ };
173
+
174
+ const isEditTextField = isEditTextFieldOp(newOps[newOps.length - 1]);
175
+
176
+ if (isEditTextField) lastOpIndex = response.editOperations.length - 1;
177
+ else lastOpIndex = response.editOperations.length;
178
+
179
+ newOps.forEach((op: EditOperation) => {
180
+ if (isEditTextFieldOp(op)) {
181
+ const editFieldOp = op as EditFieldOperation;
182
+
183
+ if (editFieldOp.item) {
184
+ editContext.itemsRepository.updateFieldValue(
185
+ editFieldOp,
186
+ editFieldOp.user ?? { name: "unknown", ai: false },
187
+ false,
188
+ editFieldOp.value
189
+ );
190
+
191
+ editContext.select([editFieldOp.item.id]);
192
+ editContext.setScrollIntoView(editFieldOp.item.id);
193
+
194
+ editContext?.setFocusedField(editFieldOp, false);
195
+ }
196
+ } else {
197
+ const addOp = op as AddComponentOperation;
198
+ if (op.type === "add-component" && addOp.componentId) {
199
+ const newComponentId = addOp.componentId;
200
+ editContext.select([newComponentId]);
201
+ editContext.setScrollIntoView(newComponentId);
202
+ }
203
+ }
204
+ });
205
+ }
206
+ }
207
+ );
208
+
209
+ if (response) {
210
+ handleResponse(response, callback, true);
211
+ if (context.callback) context.callback(response);
212
+ editContext?.requestRefresh("immediate");
213
+ }
214
+
215
+ if (response?.responseText)
216
+ newMessages.push({
217
+ content: response.responseText,
218
+ role: "assistant",
219
+ name: "assistant",
220
+ });
221
+
222
+ setResponse(response ? response : undefined);
223
+ setMessages(newMessages);
224
+ }
225
+
226
+ useEffect(() => {
227
+ TerminalService.on("command", commandHandler);
228
+
229
+ return () => {
230
+ TerminalService.off("command", commandHandler);
231
+ };
232
+ }, []);
233
+
234
+ return (
235
+ <div
236
+ className="h-full flex flex-col relative flex-1 pl-1.5"
237
+ data-testid="ai-terminal"
238
+ >
239
+ <div className="flex-1 relative">
240
+ <div className="inset-0 absolute tour-ai-terminal">
241
+ <Terminal
242
+ disabled={!model}
243
+ ref={terminalRef}
244
+ onReset={() => {
245
+ setMessages([]);
246
+ setResponse(undefined);
247
+ }}
248
+ infobar={
249
+ response?.numInputTokens && (
250
+ <div
251
+ className="text-gray-400 text-right"
252
+ style={{ fontSize: "10px" }}
253
+ >
254
+ Tokens in: {response?.numInputTokens?.toLocaleString()} out:{" "}
255
+ {response?.numOutputTokens?.toLocaleString()}{" "}
256
+ {response?.numCachedTokens
257
+ ? `cached: ${response?.numCachedTokens?.toLocaleString()} `
258
+ : ""}
259
+ {response?.state}
260
+ </div>
261
+ )
262
+ }
263
+ prompt={prompt}
264
+ setPrompt={setPrompt}
265
+ statusbar=<div className="flex items-center justify-between gap-1 flex-1">
266
+ <a
267
+ className="text-xs text-blue-300 cursor-pointer flex items-center gap-1 ml-1"
268
+ onClick={() => {
269
+ setShowPredefined(!showPredefined);
270
+ }}
271
+ >
272
+ <WizardIcon className="w-5 h-5" />
273
+ Predefined prompts
274
+ </a>
275
+ {editContext.selection?.length > 0 && (
276
+ <div className="text-xs text-red-400 flex items-center mr-2">
277
+ {editContext.selection.length} items selected
278
+ <SimpleIconButton
279
+ icon="pi pi-times"
280
+ label="Clear selection"
281
+ onClick={() => {
282
+ editContext.select([]);
283
+ }}
284
+ />
285
+ </div>
286
+ )}
287
+ {showPredefined && (
288
+ <div className="absolute right-0 left-0 bottom-8 text-sm overflow-y-auto bg-white p-3 pb-1 flex flex-col gap-1 ">
289
+ {activeProfile &&
290
+ activeProfile.prompts.map((p, index) => (
291
+ <div
292
+ key={index}
293
+ className="p-1.5 mb-1 border border-gray-200 rounded-lg cursor-pointer text-gray-700 text-xs"
294
+ onClick={() => {
295
+ setPrompt(p.prompt);
296
+ setShowPredefined(false);
297
+ }}
298
+ >
299
+ {p.title}
300
+ </div>
301
+ ))}
302
+ </div>
303
+ )}
304
+ </div>
305
+ toolbar=<div className="flex items-stretch gap-1">
306
+ <Dropdown
307
+ className="text-sm"
308
+ value={activeProfile}
309
+ onChange={(e) => setActiveProfile(e.value)}
310
+ optionLabel="name"
311
+ options={profiles}
312
+ />
313
+ {activeProfile && (
314
+ <Dropdown
315
+ className="text-sm"
316
+ value={model}
317
+ onChange={(e) => setModel(e.value)}
318
+ options={activeProfile.models}
319
+ />
320
+ )}
321
+ {closeButton}
322
+ </div>
323
+ commandHandler={(v, callback) => {
324
+ commandHandler(v, callback);
325
+ }}
326
+ />
327
+ </div>
328
+ {activeProfile?.errorMessage && (
329
+ <div className="text-red-500 text-sm p-2 inset-0 absolute grid items-center justify-center">
330
+ {activeProfile?.errorMessage}
331
+ </div>
332
+ )}
333
+ {!model && (
334
+ <div className="inset-0 absolute grid items-center justify-center text-gray-400 text-sm">
335
+ {!activeProfile ? "No profile selected" : "No model selected"}
336
+ </div>
337
+ )}
338
+ </div>
339
+ </div>
340
+ );
341
+ }
342
+
343
+ async function executePrompt(
344
+ profileId: string,
345
+ messages: Message[],
346
+ selection: string[],
347
+ session: string | null,
348
+ model: string | null,
349
+ selectedText: string | null,
350
+ context: AiContext,
351
+ callback: (response: any) => void
352
+ ): Promise<Response | null> {
353
+ const response = await fetch(context.endpoint, {
354
+ method: "POST",
355
+ body: JSON.stringify({
356
+ ...context.promptData,
357
+ profileId,
358
+ messages,
359
+ selection,
360
+ selectedText,
361
+ model,
362
+ sessionId: session,
363
+ }),
364
+ credentials: "include",
365
+ headers: {
366
+ "Content-Type": "application/json",
367
+ Cookie: new Cookies().getAll(),
368
+ },
369
+ });
370
+
371
+ if (!response?.body) return null;
372
+
373
+ const reader = response.body.getReader();
374
+ const decoder = new TextDecoder();
375
+ let buffer = "";
376
+
377
+ let result = null;
378
+
379
+ while (true) {
380
+ const { done, value } = await reader.read();
381
+
382
+ if (done) {
383
+ break;
384
+ }
385
+
386
+ buffer += decoder.decode(value, { stream: true }); // 'stream: true' ensures that any incomplete multi-byte characters aren't malformed.
387
+
388
+ // Split the buffer by newline and keep the last partial line for the next iteration.
389
+ const lines = buffer.split("\n");
390
+
391
+ if (lines.length > 0) {
392
+ buffer = lines.pop() || ""; // Incomplete line (if any) is kept for the next iteration.
393
+
394
+ for (let line of lines) {
395
+ if (line.trim() === "") continue; // Skip empty lines if any.
396
+
397
+ try {
398
+ const jsonData = JSON.parse(line);
399
+ callback(jsonData);
400
+ result = jsonData;
401
+ } catch (e) {
402
+ console.error("Error parsing line:" + line, e);
403
+ }
404
+ }
405
+ }
406
+ }
407
+
408
+ // If there's any remaining content in the buffer after processing all chunks, try to process it.
409
+ if (buffer.trim() !== "") {
410
+ try {
411
+ const jsonData = JSON.parse(buffer);
412
+ result = jsonData;
413
+ } catch (e) {
414
+ console.error("Error parsing the final buffer content:", e);
415
+ }
416
+ }
417
+
418
+ return result;
419
+ }
420
+
421
+ function handleResponse(
422
+ response: any,
423
+ terminalCallback: (text: React.ReactNode, finished: boolean) => void,
424
+ isFinished: boolean
425
+ ) {
426
+ const toolcalls: [] =
427
+ response?.toolCalls?.map((tool_call: any) => {
428
+ return {
429
+ func: tool_call.function?.name,
430
+ arguments: tool_call.function?.arguments,
431
+ result: tool_call.result,
432
+ error: tool_call.error,
433
+ };
434
+ }) || [];
435
+
436
+ const responseText = response.responseText
437
+ ?.trim()
438
+ .replace(/\n/g, "<br/>")
439
+ ?.replace(/\*\*(.*?)\*\*/g, "<b>$1</b>");
440
+
441
+ terminalCallback(
442
+ <AiResponseMessage
443
+ responseText={responseText}
444
+ toolcalls={toolcalls}
445
+ editOperations={response.editOperations}
446
+ finished={isFinished}
447
+ />,
448
+ isFinished
449
+ );
450
+ }
@@ -0,0 +1,46 @@
1
+ import { useState } from "react";
2
+
3
+ import { JsonView, defaultStyles } from "react-json-view-lite";
4
+
5
+ export type ToolCall = {
6
+ func: string;
7
+ arguments: string;
8
+ result: string;
9
+ error?: string;
10
+ };
11
+
12
+ export function AiToolCall({ toolCall }: { toolCall: ToolCall }) {
13
+ const [expanded, setExpaded] = useState(false);
14
+
15
+ return (
16
+ <div className="mt-2">
17
+ <div
18
+ onClick={() => setExpaded(!expanded)}
19
+ className="flex items-center cursor-pointer"
20
+ >
21
+ <i className="pi pi-angle-right" /> {toolCall.func}
22
+ </div>
23
+ {expanded && (
24
+ <div className="ml-4">
25
+ <div className="italic">Args:</div>
26
+ <div>{renderJsonOrText(toolCall.arguments)}</div>
27
+ <div className="italic mt-1">Result:</div>
28
+ <div className="mb-4">
29
+ {toolCall.error && (
30
+ <div className="text-red">{toolCall.error}</div>
31
+ )}
32
+ {renderJsonOrText(toolCall.result)}
33
+ </div>
34
+ </div>
35
+ )}
36
+ </div>
37
+ );
38
+ }
39
+
40
+ function renderJsonOrText(json: string) {
41
+ try {
42
+ return <JsonView data={JSON.parse(json)} style={defaultStyles} />;
43
+ } catch (e) {
44
+ return <div>{json}</div>;
45
+ }
46
+ }
@@ -0,0 +1,20 @@
1
+ import { AiTerminalOptions } from "./AiTerminal";
2
+ import { AiTerminal } from "./AiTerminal";
3
+ import { createEditorAiContext } from "./editorAiContext";
4
+
5
+ export function EditorAiTerminal({
6
+ closeButton,
7
+ options,
8
+ }: {
9
+ closeButton?: React.ReactNode;
10
+ options?: AiTerminalOptions;
11
+ }) {
12
+ return (
13
+ <AiTerminal
14
+ options={options}
15
+ closeButton={closeButton}
16
+ createAiContext={createEditorAiContext}
17
+ defaultProfile="Editor"
18
+ />
19
+ );
20
+ }
@@ -0,0 +1,18 @@
1
+ import { EditContextType } from "../client/editContext";
2
+
3
+ export function createEditorAiContext({
4
+ editContext,
5
+ }: {
6
+ editContext: EditContextType;
7
+ }) {
8
+ const aiPromptUrl = editContext.configuration.services.aiService.promptUrl;
9
+
10
+ return {
11
+ endpoint: aiPromptUrl,
12
+ promptData: {
13
+ itemid: editContext.currentItemDescriptor?.id,
14
+ language: editContext.currentItemDescriptor?.language,
15
+ version: editContext.currentItemDescriptor?.version,
16
+ },
17
+ };
18
+ }
@@ -0,0 +1,49 @@
1
+ // DialogContext.tsx
2
+ import React, { createContext, useContext, useState, ReactNode } from "react";
3
+
4
+ export type DialogProps<T> = {
5
+ onClose: (result: T | null) => void;
6
+ };
7
+
8
+ export type OpenDialog = <T, P>(
9
+ Component: React.ComponentType<P & DialogProps<T>>,
10
+ props: P
11
+ ) => Promise<T | null>;
12
+
13
+ export interface DialogContextValue {
14
+ openDialog: OpenDialog;
15
+ }
16
+
17
+ const DialogContext = createContext<DialogContextValue | undefined>(undefined);
18
+
19
+ export const DialogProvider: React.FC<{ children: ReactNode }> = ({
20
+ children,
21
+ }) => {
22
+ const [dialog, setDialog] = useState<ReactNode>(null);
23
+
24
+ const openDialog: OpenDialog = (Component, props) => {
25
+ return new Promise((resolve) => {
26
+ const handleClose = (result: any) => {
27
+ setDialog(null);
28
+ resolve(result);
29
+ };
30
+
31
+ setDialog(<Component {...(props as any)} onClose={handleClose} />);
32
+ });
33
+ };
34
+
35
+ return (
36
+ <DialogContext.Provider value={{ openDialog }}>
37
+ {children}
38
+ {dialog}
39
+ </DialogContext.Provider>
40
+ );
41
+ };
42
+
43
+ export const useDialog = (): DialogContextValue => {
44
+ const context = useContext(DialogContext);
45
+ if (!context) {
46
+ throw new Error("useDialog must be used within a DialogProvider");
47
+ }
48
+ return context;
49
+ };