@monaco-ai-editor/react 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,870 @@
1
+ // src/components/EditorPanel.tsx
2
+ import { useRef, useEffect, useState, useMemo } from "react";
3
+ import Editor, { loader } from "@monaco-editor/react";
4
+ import {
5
+ EditorController,
6
+ registerSqlCompletion,
7
+ registerAiInlineCompletion,
8
+ registerAiCompletionCommand
9
+ } from "@monaco-ai-editor/core";
10
+
11
+ // src/hooks/useOptionalCoordinator.ts
12
+ import { useContext } from "react";
13
+
14
+ // src/context/internal.ts
15
+ import { createContext } from "react";
16
+ var EditorCoordinatorContext = createContext(null);
17
+
18
+ // src/hooks/useOptionalCoordinator.ts
19
+ function useOptionalCoordinator() {
20
+ return useContext(EditorCoordinatorContext);
21
+ }
22
+
23
+ // src/components/EditorPanel.tsx
24
+ import { jsx, jsxs } from "react/jsx-runtime";
25
+ var EditorPanel = ({
26
+ value,
27
+ onChange,
28
+ language,
29
+ theme,
30
+ vsPath,
31
+ sqlSchema,
32
+ builtinSqlExcludeLabels,
33
+ apiConfig: apiConfigProp,
34
+ completionProviders,
35
+ onMount,
36
+ minimap,
37
+ editorOptions,
38
+ onAiError,
39
+ className,
40
+ loading
41
+ }) => {
42
+ const coordinator = useOptionalCoordinator();
43
+ const effectiveLanguage = language ?? coordinator?.editorState.language ?? "typescript";
44
+ const effectiveTheme = theme ?? coordinator?.editorState.theme ?? "vs-dark";
45
+ const effectiveApiConfig = apiConfigProp ?? coordinator?.apiConfig ?? null;
46
+ const effectiveSqlSchema = sqlSchema ?? coordinator?.sqlSchema ?? {};
47
+ const effectiveProviders = completionProviders ?? coordinator?.completionProviders ?? [];
48
+ const controllerRef = useRef(null);
49
+ const apiConfigRef = useRef(effectiveApiConfig);
50
+ const pendingRef = useRef(null);
51
+ const [aiLoading, setAiLoading] = useState(false);
52
+ const [aiError, setAiError] = useState(null);
53
+ const errorTimerRef = useRef(null);
54
+ useEffect(() => {
55
+ if (vsPath) {
56
+ loader.config({ paths: { vs: vsPath } });
57
+ }
58
+ }, [vsPath]);
59
+ useEffect(() => {
60
+ apiConfigRef.current = effectiveApiConfig;
61
+ }, [effectiveApiConfig]);
62
+ const showAiError = (msg) => {
63
+ if (errorTimerRef.current) clearTimeout(errorTimerRef.current);
64
+ setAiError(msg);
65
+ onAiError?.(msg);
66
+ errorTimerRef.current = setTimeout(() => setAiError(null), 4e3);
67
+ };
68
+ const schemaRef = useRef(effectiveSqlSchema);
69
+ useEffect(() => {
70
+ schemaRef.current = effectiveSqlSchema;
71
+ }, [effectiveSqlSchema]);
72
+ const excludeLabelsRef = useRef(
73
+ new Set((builtinSqlExcludeLabels ?? []).map((l) => l.toUpperCase()))
74
+ );
75
+ useEffect(() => {
76
+ excludeLabelsRef.current = new Set((builtinSqlExcludeLabels ?? []).map((l) => l.toUpperCase()));
77
+ }, [builtinSqlExcludeLabels]);
78
+ const handleBeforeMount = (monaco) => {
79
+ window.__monaco_ai_monaco = monaco;
80
+ };
81
+ const handleMount = (editor, monaco) => {
82
+ registerSqlCompletion(monaco, schemaRef, excludeLabelsRef);
83
+ registerAiInlineCompletion(monaco, pendingRef);
84
+ for (const provider of effectiveProviders) {
85
+ const langs = provider.languages ?? ["*"];
86
+ for (const lang of langs) {
87
+ monaco.languages.registerCompletionItemProvider(lang, {
88
+ triggerCharacters: provider.triggerCharacters,
89
+ provideCompletionItems: async (model, position, context) => {
90
+ const ctx = {
91
+ monaco,
92
+ model,
93
+ position,
94
+ lineText: model.getLineContent(position.lineNumber),
95
+ currentWord: model.getWordUntilPosition(position).word,
96
+ fullText: model.getValue(),
97
+ triggerCharacter: context.triggerCharacter
98
+ };
99
+ if (provider.shouldTrigger && !provider.shouldTrigger(ctx)) {
100
+ return { suggestions: [] };
101
+ }
102
+ return { suggestions: await provider.provide(ctx) };
103
+ }
104
+ });
105
+ }
106
+ }
107
+ registerAiCompletionCommand(
108
+ editor,
109
+ () => apiConfigRef.current,
110
+ pendingRef,
111
+ setAiLoading,
112
+ showAiError
113
+ );
114
+ const controller = createControllerFromEditor(editor, monaco);
115
+ controllerRef.current = controller;
116
+ if (coordinator) {
117
+ coordinator.setEditor(controller);
118
+ coordinator.bus.registerEditor(controller);
119
+ editor.onDidChangeCursorPosition((e) => {
120
+ coordinator.patchEditorState({
121
+ cursorLine: e.position.lineNumber,
122
+ cursorColumn: e.position.column
123
+ });
124
+ });
125
+ editor.onDidChangeModelContent(() => {
126
+ coordinator.patchEditorState({ value: editor.getValue() });
127
+ });
128
+ }
129
+ onMount?.(controller);
130
+ };
131
+ useEffect(() => {
132
+ return () => {
133
+ if (errorTimerRef.current) clearTimeout(errorTimerRef.current);
134
+ if (coordinator && controllerRef.current) {
135
+ coordinator.setEditor(null);
136
+ }
137
+ };
138
+ }, []);
139
+ const mergedOptions = useMemo(
140
+ () => {
141
+ const defaultMinimap = {
142
+ enabled: true,
143
+ side: "right",
144
+ showSlider: "mouseover",
145
+ renderCharacters: true,
146
+ maxColumn: 120
147
+ };
148
+ const minimapFromProp = typeof minimap === "boolean" ? { enabled: minimap } : minimap;
149
+ const mergedMinimap = {
150
+ ...defaultMinimap,
151
+ ...minimapFromProp,
152
+ ...editorOptions?.minimap
153
+ };
154
+ return {
155
+ fontSize: 14,
156
+ scrollBeyondLastLine: false,
157
+ automaticLayout: true,
158
+ // 让 suggest/hover/find 等溢出浮层以 fixed 定位渲染到 body 层级,
159
+ // 避免被祖先的 overflow:hidden 裁剪(如 tooltip 贴边被遮挡)
160
+ fixedOverflowWidgets: true,
161
+ lineNumbers: "on",
162
+ renderLineHighlight: "all",
163
+ smoothScrolling: true,
164
+ cursorBlinking: "smooth",
165
+ bracketPairColorization: { enabled: true },
166
+ tabSize: 2,
167
+ wordWrap: "on",
168
+ folding: true,
169
+ formatOnPaste: true,
170
+ formatOnType: false,
171
+ suggestOnTriggerCharacters: true,
172
+ quickSuggestions: { other: true, comments: false, strings: false },
173
+ acceptSuggestionOnCommitCharacter: true,
174
+ acceptSuggestionOnEnter: "on",
175
+ inlineSuggest: { enabled: true },
176
+ tabCompletion: "on",
177
+ ...editorOptions,
178
+ minimap: mergedMinimap
179
+ };
180
+ },
181
+ [minimap, editorOptions]
182
+ );
183
+ return /* @__PURE__ */ jsxs("div", { className, style: { position: "relative", flex: 1, height: "100%" }, children: [
184
+ aiLoading && /* @__PURE__ */ jsxs("div", { className: "mae-ai-loading", style: loadingStyle, children: [
185
+ /* @__PURE__ */ jsx("span", { className: "mae-spinner", style: spinnerStyle }),
186
+ " AI \u8865\u5168\u4E2D\u2026"
187
+ ] }),
188
+ aiError && /* @__PURE__ */ jsx("div", { onClick: () => setAiError(null), style: errorStyle, title: "\u70B9\u51FB\u5173\u95ED", children: aiError }),
189
+ /* @__PURE__ */ jsx(
190
+ Editor,
191
+ {
192
+ height: "100%",
193
+ language: effectiveLanguage,
194
+ theme: effectiveTheme,
195
+ value,
196
+ loading,
197
+ onChange: (val) => onChange?.(val ?? ""),
198
+ beforeMount: handleBeforeMount,
199
+ onMount: handleMount,
200
+ options: mergedOptions
201
+ }
202
+ )
203
+ ] });
204
+ };
205
+ function createControllerFromEditor(editor, monaco) {
206
+ const fake = Object.create(EditorController.prototype);
207
+ fake.editor = editor;
208
+ fake.monaco = monaco;
209
+ fake.disposables = [];
210
+ fake.pendingCompletionRef = { current: null };
211
+ fake.customProviders = [];
212
+ fake.listeners = {};
213
+ return fake;
214
+ }
215
+ var loadingStyle = {
216
+ position: "absolute",
217
+ bottom: 32,
218
+ right: 16,
219
+ zIndex: 10,
220
+ display: "flex",
221
+ alignItems: "center",
222
+ gap: 6,
223
+ borderRadius: 9999,
224
+ border: "1px solid rgba(59,130,246,0.3)",
225
+ background: "rgba(17,24,39,0.9)",
226
+ padding: "4px 12px",
227
+ fontSize: 12,
228
+ color: "#60a5fa"
229
+ };
230
+ var spinnerStyle = {
231
+ display: "inline-block",
232
+ width: 12,
233
+ height: 12,
234
+ border: "2px solid rgba(96,165,250,0.3)",
235
+ borderTopColor: "#60a5fa",
236
+ borderRadius: "50%",
237
+ animation: "mae-spin 0.8s linear infinite"
238
+ };
239
+ var errorStyle = {
240
+ position: "absolute",
241
+ bottom: 32,
242
+ right: 16,
243
+ zIndex: 10,
244
+ maxWidth: 320,
245
+ cursor: "pointer",
246
+ borderRadius: 8,
247
+ border: "1px solid rgba(239,68,68,0.4)",
248
+ background: "rgba(17,24,39,0.95)",
249
+ padding: "8px 12px",
250
+ fontSize: 12,
251
+ color: "#f87171",
252
+ lineHeight: 1.5
253
+ };
254
+
255
+ // src/components/AiChatPanel.tsx
256
+ import {
257
+ useState as useState2,
258
+ useRef as useRef2,
259
+ useEffect as useEffect2,
260
+ useCallback
261
+ } from "react";
262
+ import { DiffEditor } from "@monaco-editor/react";
263
+ import {
264
+ AiChatController
265
+ } from "@monaco-ai-editor/core";
266
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
267
+ function parseContent(raw, isStreaming) {
268
+ const parts = [];
269
+ const blockRe = /```(\w*)\n?([\s\S]*?)```/g;
270
+ let last = 0;
271
+ let m;
272
+ while ((m = blockRe.exec(raw)) !== null) {
273
+ if (m.index > last) parts.push({ type: "text", content: raw.slice(last, m.index) });
274
+ parts.push({ type: "code", lang: m[1] || "code", content: m[2].trimEnd() });
275
+ last = m.index + m[0].length;
276
+ }
277
+ const remaining = raw.slice(last);
278
+ if (remaining) {
279
+ const idx = remaining.indexOf("```");
280
+ if (idx !== -1) {
281
+ if (idx > 0) parts.push({ type: "text", content: remaining.slice(0, idx) });
282
+ const block = remaining.slice(idx + 3);
283
+ const lm = block.match(/^(\w*)\n?/);
284
+ parts.push({ type: "code", lang: lm?.[1] ?? "", content: block.slice(lm?.[0]?.length ?? 0) + (isStreaming ? "\u258B" : "") });
285
+ } else {
286
+ parts.push({ type: "text", content: remaining + (isStreaming ? "\u258B" : "") });
287
+ }
288
+ } else if (isStreaming && raw.length > 0 && !raw.endsWith("```")) {
289
+ parts.push({ type: "text", content: "\u258B" });
290
+ }
291
+ return parts;
292
+ }
293
+ var MessageContent = ({ content, isStreaming, onInsertCode, onShowDiff }) => {
294
+ const parts = parseContent(content, isStreaming);
295
+ if (isStreaming && content === "") return /* @__PURE__ */ jsx2("span", { style: { animation: "pulse 1s infinite", color: "#9ca3af" }, children: "\u258B" });
296
+ return /* @__PURE__ */ jsx2(Fragment, { children: parts.map(
297
+ (p, i) => p.type === "code" ? /* @__PURE__ */ jsxs2("div", { style: { margin: "8px 0", borderRadius: 8, overflow: "hidden", border: "1px solid rgb(75,85,99)" }, children: [
298
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", padding: "4px 12px", background: "rgb(55,65,71)" }, children: [
299
+ /* @__PURE__ */ jsx2("span", { style: { fontSize: 11, color: "rgb(156,163,175)" }, children: p.lang }),
300
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 8 }, children: [
301
+ /* @__PURE__ */ jsx2("button", { onClick: () => navigator.clipboard.writeText(p.content), style: codeBtnStyle, children: "\u590D\u5236" }),
302
+ onShowDiff && /* @__PURE__ */ jsx2("button", { onClick: () => onShowDiff(p.content, p.lang), style: { ...codeBtnStyle, color: "#fbbf24" }, children: "\u5DEE\u5F02" }),
303
+ onInsertCode && /* @__PURE__ */ jsx2("button", { onClick: () => onInsertCode(p.content), style: { ...codeBtnStyle, color: "#60a5fa" }, children: "\u63D2\u5165" })
304
+ ] })
305
+ ] }),
306
+ /* @__PURE__ */ jsx2("pre", { style: { margin: 0, padding: 12, overflowX: "auto", background: "rgb(3,7,18)", fontSize: 12, lineHeight: 1.6, color: "rgb(229,231,235)" }, children: /* @__PURE__ */ jsx2("code", { children: p.content }) })
307
+ ] }, i) : /* @__PURE__ */ jsx2("span", { style: { whiteSpace: "pre-wrap", lineHeight: 1.6 }, children: p.content }, i)
308
+ ) });
309
+ };
310
+ var codeBtnStyle = {
311
+ fontSize: 11,
312
+ color: "rgb(156,163,175)",
313
+ background: "transparent",
314
+ border: "none",
315
+ cursor: "pointer",
316
+ padding: 0
317
+ };
318
+ var MIN_WIDTH = 260;
319
+ var MAX_WIDTH = 720;
320
+ var AiChatPanel = (props) => {
321
+ const coordinator = useOptionalCoordinator();
322
+ const [width, setWidth] = useState2(props.defaultWidth ?? 320);
323
+ const [messages, setMessages] = useState2([]);
324
+ const [input, setInput] = useState2("");
325
+ const [loading, setLoading] = useState2(false);
326
+ const [showSettings, setShowSettings] = useState2(false);
327
+ const [configDraft, setConfigDraft] = useState2(props.apiConfig ?? coordinator?.apiConfig ?? { baseUrl: "", apiKey: "", model: "" });
328
+ const [diffState, setDiffState] = useState2(null);
329
+ const messagesEndRef = useRef2(null);
330
+ const abortRef = useRef2(null);
331
+ const ctrlRef = useRef2(null);
332
+ const isDragging = useRef2(false);
333
+ const dragStartX = useRef2(0);
334
+ const dragStartW = useRef2(0);
335
+ const editorContent = props.editorContent ?? coordinator?.editor?.getValue() ?? "";
336
+ const language = props.language ?? coordinator?.editorState.language;
337
+ const effectiveApiConfig = props.apiConfig ?? coordinator?.apiConfig ?? configDraft;
338
+ useEffect2(() => {
339
+ const ctrl = new AiChatController(effectiveApiConfig);
340
+ ctrlRef.current = ctrl;
341
+ const offAdd = ctrl.on("messageAdded", (msg) => setMessages((prev) => [...prev, msg]));
342
+ const offUpd = ctrl.on("messageUpdated", (msg) => setMessages((prev) => prev.map((m) => m.id === msg.id ? msg : m)));
343
+ const offClear = ctrl.on("cleared", () => setMessages([]));
344
+ const offLoad = ctrl.on("loadingChange", ({ loading: l }) => setLoading(l));
345
+ if (coordinator) coordinator.setChat(ctrl);
346
+ return () => {
347
+ offAdd();
348
+ offUpd();
349
+ offClear();
350
+ offLoad();
351
+ ctrl.dispose();
352
+ };
353
+ }, []);
354
+ useEffect2(() => {
355
+ if (ctrlRef.current) ctrlRef.current.setApiConfig(effectiveApiConfig);
356
+ setConfigDraft(effectiveApiConfig);
357
+ }, [effectiveApiConfig]);
358
+ useEffect2(() => {
359
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
360
+ }, [messages]);
361
+ const handleSend = useCallback(() => {
362
+ ctrlRef.current?.send(input, { editorContent, language });
363
+ setInput("");
364
+ }, [input, editorContent, language]);
365
+ const handleKeyDown = (e) => {
366
+ if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
367
+ e.preventDefault();
368
+ handleSend();
369
+ }
370
+ };
371
+ const handleStop = () => ctrlRef.current?.stop();
372
+ const handleClear = () => ctrlRef.current?.clear();
373
+ const handleSaveConfig = () => {
374
+ if (coordinator?.onApiConfigChange) {
375
+ coordinator.onApiConfigChange(configDraft);
376
+ }
377
+ if (props.onApiConfigChange) {
378
+ props.onApiConfigChange(configDraft);
379
+ }
380
+ setShowSettings(false);
381
+ };
382
+ const handleAddEditorContext = () => {
383
+ if (!editorContent) return;
384
+ const snippet = `\u4EE5\u4E0B\u662F\u5F53\u524D\u7F16\u8F91\u5668\u4E2D\u7684${language ? ` ${language} ` : ""}\u4EE3\u7801\uFF1A
385
+ \`\`\`${language}
386
+ ${editorContent}
387
+ \`\`\``;
388
+ setInput((prev) => prev.trim() ? `${prev}
389
+
390
+ ${snippet}` : snippet);
391
+ };
392
+ const handleInsert = props.onInsertCode ? props.onInsertCode : (code) => coordinator?.bus?.insertToEditor(code);
393
+ const handleReplace = props.onReplaceCode ? props.onReplaceCode : (code) => coordinator?.bus?.replaceEditor(code);
394
+ const handleShowDiff = useCallback((aiCode, codeLang) => {
395
+ setDiffState({ original: editorContent ?? "", modified: aiCode, language: codeLang || language || "plaintext" });
396
+ }, [editorContent, language]);
397
+ const handleApplyDiff = () => {
398
+ if (!diffState) return;
399
+ if (handleReplace) handleReplace(diffState.modified);
400
+ else if (handleInsert) handleInsert(diffState.modified);
401
+ setDiffState(null);
402
+ };
403
+ const handleResizeMouseDown = (e) => {
404
+ e.preventDefault();
405
+ isDragging.current = true;
406
+ dragStartX.current = e.clientX;
407
+ dragStartW.current = width;
408
+ const onMove = (ev) => {
409
+ const next = Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, dragStartW.current + (dragStartX.current - ev.clientX)));
410
+ setWidth(next);
411
+ };
412
+ const onUp = () => {
413
+ isDragging.current = false;
414
+ document.removeEventListener("mousemove", onMove);
415
+ document.removeEventListener("mouseup", onUp);
416
+ document.body.style.cursor = "";
417
+ };
418
+ document.body.style.cursor = "col-resize";
419
+ document.addEventListener("mousemove", onMove);
420
+ document.addEventListener("mouseup", onUp);
421
+ };
422
+ return /* @__PURE__ */ jsxs2("div", { style: { position: "relative", display: "flex", flexDirection: "column", borderLeft: "1px solid rgb(55,65,81)", background: "rgb(17,24,39)", width, height: "100%" }, children: [
423
+ /* @__PURE__ */ jsx2("div", { onMouseDown: handleResizeMouseDown, style: { position: "absolute", left: 0, top: 0, width: 4, height: "100%", cursor: "col-resize", zIndex: 10 } }),
424
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", padding: "8px 12px", borderBottom: "1px solid rgb(55,65,81)" }, children: [
425
+ /* @__PURE__ */ jsxs2("svg", { style: { width: 16, height: 16, color: "#60a5fa", marginRight: 8 }, viewBox: "0 0 20 20", fill: "currentColor", children: [
426
+ /* @__PURE__ */ jsx2("path", { d: "M2 5a2 2 0 012-2h7a2 2 0 012 2v4a2 2 0 01-2 2H9l-3 3v-3H4a2 2 0 01-2-2V5z" }),
427
+ /* @__PURE__ */ jsx2("path", { d: "M15 7v2a4 4 0 01-4 4H9.828l-1.766 1.767c.28.149.599.233.938.233h2l3 3v-3h2a2 2 0 002-2V9a2 2 0 00-2-2h-1z" })
428
+ ] }),
429
+ /* @__PURE__ */ jsx2("span", { style: { flex: 1, fontSize: 13, fontWeight: 500, color: "#fff" }, children: "AI \u5BF9\u8BDD" }),
430
+ /* @__PURE__ */ jsx2("button", { onClick: () => {
431
+ setConfigDraft(effectiveApiConfig);
432
+ setShowSettings((v) => !v);
433
+ }, style: { ...iconBtnStyle, color: showSettings ? "#60a5fa" : "#9ca3af" }, title: "\u8BBE\u7F6E", children: /* @__PURE__ */ jsxs2("svg", { style: { width: 16, height: 16 }, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, children: [
434
+ /* @__PURE__ */ jsx2("path", { d: "M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" }),
435
+ /* @__PURE__ */ jsx2("circle", { cx: "12", cy: "12", r: "3" })
436
+ ] }) }),
437
+ /* @__PURE__ */ jsx2("button", { onClick: handleClear, style: iconBtnStyle, title: "\u6E05\u7A7A", children: /* @__PURE__ */ jsx2("svg", { style: { width: 16, height: 16 }, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx2("path", { d: "M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" }) }) }),
438
+ props.onClose && /* @__PURE__ */ jsx2("button", { onClick: props.onClose, style: iconBtnStyle, title: "\u5173\u95ED", children: /* @__PURE__ */ jsx2("svg", { style: { width: 16, height: 16 }, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx2("path", { d: "M6 18L18 6M6 6l12 12" }) }) })
439
+ ] }),
440
+ showSettings && /* @__PURE__ */ jsxs2("div", { style: { padding: 12, borderBottom: "1px solid rgb(55,65,81)", background: "rgb(31,41,55)" }, children: [
441
+ /* @__PURE__ */ jsx2("div", { style: { fontSize: 11, fontWeight: 600, color: "#d1d5db", marginBottom: 12 }, children: "API \u914D\u7F6E" }),
442
+ ["baseUrl", "apiKey", "model"].map((field) => /* @__PURE__ */ jsxs2("div", { children: [
443
+ /* @__PURE__ */ jsx2("label", { style: { display: "block", fontSize: 11, color: "#9ca3af", marginBottom: 4 }, children: field === "baseUrl" ? "Base URL" : field === "apiKey" ? "API Key" : "\u6A21\u578B" }),
444
+ /* @__PURE__ */ jsx2(
445
+ "input",
446
+ {
447
+ type: field === "apiKey" ? "password" : "text",
448
+ value: configDraft[field],
449
+ onChange: (e) => setConfigDraft((p) => ({ ...p, [field]: e.target.value })),
450
+ placeholder: field === "baseUrl" ? "https://api.openai.com/v1" : field === "model" ? "gpt-4o-mini" : "",
451
+ style: inputStyle
452
+ }
453
+ )
454
+ ] }, field)),
455
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 8, marginTop: 12 }, children: [
456
+ /* @__PURE__ */ jsx2("button", { onClick: handleSaveConfig, style: { flex: 1, padding: "6px 0", fontSize: 12, background: "rgb(37,99,235)", color: "#fff", border: "none", borderRadius: 4, cursor: "pointer" }, children: "\u4FDD\u5B58" }),
457
+ /* @__PURE__ */ jsx2("button", { onClick: () => setShowSettings(false), style: { flex: 1, padding: "6px 0", fontSize: 12, background: "rgb(75,85,99)", color: "#fff", border: "none", borderRadius: 4, cursor: "pointer" }, children: "\u53D6\u6D88" })
458
+ ] })
459
+ ] }),
460
+ diffState && /* @__PURE__ */ jsxs2("div", { style: { flex: 1, display: "flex", flexDirection: "column", background: "rgb(17,24,39)" }, children: [
461
+ /* @__PURE__ */ jsxs2("div", { style: { padding: "4px 8px", borderBottom: "1px solid rgb(55,65,81)", background: "rgb(31,41,55)", display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
462
+ /* @__PURE__ */ jsx2("span", { style: { fontSize: 12, color: "#fff" }, children: "Diff \u5BF9\u6BD4" }),
463
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 8 }, children: [
464
+ /* @__PURE__ */ jsx2("button", { onClick: handleApplyDiff, style: { fontSize: 11, color: "#60a5fa", background: "transparent", border: "none", cursor: "pointer" }, children: "\u5E94\u7528\u66F4\u6539" }),
465
+ /* @__PURE__ */ jsx2("button", { onClick: () => setDiffState(null), style: { fontSize: 11, color: "#9ca3af", background: "transparent", border: "none", cursor: "pointer" }, children: "\u5173\u95ED" })
466
+ ] })
467
+ ] }),
468
+ /* @__PURE__ */ jsx2(
469
+ DiffEditor,
470
+ {
471
+ height: "100%",
472
+ language: diffState.language,
473
+ original: diffState.original,
474
+ modified: diffState.modified,
475
+ theme: "vs-dark"
476
+ }
477
+ )
478
+ ] }),
479
+ !diffState && /* @__PURE__ */ jsxs2("div", { style: { flex: 1, overflowY: "auto", padding: 12 }, children: [
480
+ messages.length === 0 && /* @__PURE__ */ jsxs2("div", { style: { display: "flex", height: "100%", flexDirection: "column", alignItems: "center", justifyContent: "center", textAlign: "center" }, children: [
481
+ !effectiveApiConfig.apiKey.trim() && /* @__PURE__ */ jsxs2("div", { style: { width: "100%", marginBottom: 16, padding: "8px 12px", borderRadius: 8, border: "1px solid rgba(250,204,21,0.4)", background: "rgba(250,204,21,0.1)", textAlign: "left" }, children: [
482
+ /* @__PURE__ */ jsx2("p", { style: { marginBottom: 4, fontSize: 11, fontWeight: 600, color: "#fbbf24" }, children: "\u26A0\uFE0F \u672A\u914D\u7F6E API Key" }),
483
+ /* @__PURE__ */ jsx2("p", { style: { fontSize: 11, color: "rgba(251,191,36,0.8)", lineHeight: 1.5 }, children: "\u8BF7\u70B9\u51FB\u53F3\u4E0A\u89D2 \u2699\uFE0F \u6309\u94AE\u586B\u5199\u914D\u7F6E\u540E\u5F00\u59CB\u5BF9\u8BDD" })
484
+ ] }),
485
+ /* @__PURE__ */ jsx2("svg", { style: { width: 40, height: 40, color: "rgb(75,85,99)", marginBottom: 12 }, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx2("path", { d: "M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z" }) }),
486
+ !loading && /* @__PURE__ */ jsx2("p", { style: { fontSize: 12, color: "#6b7280" }, children: "\u5F00\u59CB\u4F60\u7684 AI \u7F16\u7A0B\u5BF9\u8BDD\u5427" })
487
+ ] }),
488
+ messages.map((msg) => /* @__PURE__ */ jsx2("div", { style: { marginBottom: 12, display: "flex", flexDirection: "column", alignItems: msg.role === "user" ? "flex-end" : "flex-start" }, children: /* @__PURE__ */ jsx2("div", { style: { maxWidth: "90%", padding: "8px 12px", borderRadius: 8, fontSize: 12, color: "#e5e7eb", background: msg.role === "user" ? "rgb(55,65,81)" : "rgb(31,41,55)", lineHeight: 1.5 }, children: msg.role === "assistant" ? /* @__PURE__ */ jsx2(MessageContent, { content: msg.content, isStreaming: msg.isStreaming, onInsertCode: handleInsert, onShowDiff: handleShowDiff }) : /* @__PURE__ */ jsx2("span", { style: { whiteSpace: "pre-wrap" }, children: msg.content }) }) }, msg.id)),
489
+ /* @__PURE__ */ jsx2("div", { ref: messagesEndRef })
490
+ ] }),
491
+ !diffState && /* @__PURE__ */ jsxs2("div", { style: { padding: "8px 12px", borderTop: "1px solid rgb(55,65,81)" }, children: [
492
+ /* @__PURE__ */ jsx2("div", { style: { display: "flex", gap: 4, marginBottom: 4 }, children: /* @__PURE__ */ jsx2("button", { onClick: handleAddEditorContext, style: { fontSize: 11, color: "#9ca3af", background: "transparent", border: "none", cursor: "pointer" }, title: "\u5F15\u7528\u7F16\u8F91\u5668\u4EE3\u7801", children: "+ \u5F15\u7528\u4EE3\u7801" }) }),
493
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", gap: 8 }, children: [
494
+ /* @__PURE__ */ jsx2(
495
+ "textarea",
496
+ {
497
+ value: input,
498
+ onChange: (e) => setInput(e.target.value),
499
+ onKeyDown: handleKeyDown,
500
+ placeholder: "\u8F93\u5165\u6D88\u606F\uFF0CCtrl+Enter \u53D1\u9001...",
501
+ style: { flex: 1, padding: "6px 8px", fontSize: 12, background: "rgb(31,41,55)", color: "#e5e7eb", border: "1px solid rgb(75,85,99)", borderRadius: 4, outline: "none", resize: "none", minHeight: 32 },
502
+ rows: 2
503
+ }
504
+ ),
505
+ loading ? /* @__PURE__ */ jsx2("button", { onClick: handleStop, style: { padding: "6px 12px", fontSize: 12, background: "rgb(220,38,38)", color: "#fff", border: "none", borderRadius: 4, cursor: "pointer", whiteSpace: "nowrap" }, children: "\u505C\u6B62" }) : /* @__PURE__ */ jsx2("button", { onClick: handleSend, style: { padding: "6px 12px", fontSize: 12, background: "rgb(37,99,235)", color: "#fff", border: "none", borderRadius: 4, cursor: "pointer", whiteSpace: "nowrap" }, children: "\u53D1\u9001" })
506
+ ] })
507
+ ] })
508
+ ] });
509
+ };
510
+ var iconBtnStyle = {
511
+ padding: 4,
512
+ color: "#9ca3af",
513
+ background: "transparent",
514
+ border: "none",
515
+ cursor: "pointer",
516
+ display: "flex",
517
+ borderRadius: 4
518
+ };
519
+ var inputStyle = {
520
+ width: "100%",
521
+ padding: "4px 8px",
522
+ fontSize: 12,
523
+ background: "rgb(55,65,81)",
524
+ color: "rgb(229,231,235)",
525
+ border: "1px solid rgb(75,85,99)",
526
+ borderRadius: 4,
527
+ outline: "none",
528
+ marginBottom: 8,
529
+ boxSizing: "border-box"
530
+ };
531
+
532
+ // src/components/Toolbar.tsx
533
+ import { LANGUAGES, THEMES } from "@monaco-ai-editor/core";
534
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
535
+ var Toolbar = ({
536
+ language,
537
+ theme,
538
+ onLanguageChange,
539
+ onThemeChange,
540
+ onFormat,
541
+ onToggleAiChat,
542
+ aiChatVisible,
543
+ className
544
+ }) => {
545
+ return /* @__PURE__ */ jsxs3(
546
+ "div",
547
+ {
548
+ className,
549
+ style: toolbarStyle,
550
+ children: [
551
+ /* @__PURE__ */ jsx3("div", { style: labelStyle, children: "\u8BED\u8A00" }),
552
+ /* @__PURE__ */ jsx3(
553
+ "select",
554
+ {
555
+ value: language ?? "typescript",
556
+ onChange: (e) => onLanguageChange?.(e.target.value),
557
+ style: selectStyle,
558
+ children: LANGUAGES.map((lang) => /* @__PURE__ */ jsx3("option", { value: lang, children: lang }, lang))
559
+ }
560
+ ),
561
+ /* @__PURE__ */ jsx3("div", { style: { ...labelStyle, marginLeft: 16 }, children: "\u4E3B\u9898" }),
562
+ /* @__PURE__ */ jsx3(
563
+ "select",
564
+ {
565
+ value: theme ?? "vs-dark",
566
+ onChange: (e) => onThemeChange?.(e.target.value),
567
+ style: selectStyle,
568
+ children: THEMES.map((t) => /* @__PURE__ */ jsx3("option", { value: t.value, children: t.label }, t.value))
569
+ }
570
+ ),
571
+ /* @__PURE__ */ jsx3("div", { style: { flex: 1 } }),
572
+ onToggleAiChat && /* @__PURE__ */ jsxs3(
573
+ "button",
574
+ {
575
+ onClick: onToggleAiChat,
576
+ style: {
577
+ ...buttonStyle,
578
+ background: aiChatVisible ? "rgb(37,99,235)" : "rgb(55,65,81)"
579
+ },
580
+ children: [
581
+ /* @__PURE__ */ jsxs3(
582
+ "svg",
583
+ {
584
+ style: { width: 16, height: 16, marginRight: 4 },
585
+ viewBox: "0 0 20 20",
586
+ fill: "currentColor",
587
+ children: [
588
+ /* @__PURE__ */ jsx3("path", { d: "M2 5a2 2 0 012-2h7a2 2 0 012 2v4a2 2 0 01-2 2H9l-3 3v-3H4a2 2 0 01-2-2V5z" }),
589
+ /* @__PURE__ */ jsx3("path", { d: "M15 7v2a4 4 0 01-4 4H9.828l-1.766 1.767c.28.149.599.233.938.233h2l3 3v-3h2a2 2 0 002-2V9a2 2 0 00-2-2h-1z" })
590
+ ]
591
+ }
592
+ ),
593
+ "AI"
594
+ ]
595
+ }
596
+ ),
597
+ onFormat && /* @__PURE__ */ jsx3("button", { onClick: onFormat, style: { ...buttonStyle, marginLeft: 8 }, children: "\u683C\u5F0F\u5316" })
598
+ ]
599
+ }
600
+ );
601
+ };
602
+ var toolbarStyle = {
603
+ display: "flex",
604
+ alignItems: "center",
605
+ padding: "8px 16px",
606
+ background: "rgb(31,41,55)",
607
+ borderBottom: "1px solid rgb(55,65,81)"
608
+ };
609
+ var labelStyle = {
610
+ fontSize: 13,
611
+ color: "rgb(156,163,175)"
612
+ };
613
+ var selectStyle = {
614
+ marginLeft: 8,
615
+ padding: "4px 8px",
616
+ fontSize: 13,
617
+ background: "rgb(55,65,81)",
618
+ color: "rgb(229,231,235)",
619
+ border: "1px solid rgb(75,85,99)",
620
+ borderRadius: 4,
621
+ outline: "none"
622
+ };
623
+ var buttonStyle = {
624
+ padding: "4px 12px",
625
+ fontSize: 13,
626
+ color: "#fff",
627
+ background: "rgb(55,65,81)",
628
+ border: "none",
629
+ borderRadius: 4,
630
+ cursor: "pointer",
631
+ display: "flex",
632
+ alignItems: "center"
633
+ };
634
+
635
+ // src/components/StatusBar.tsx
636
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
637
+ var StatusBar = ({
638
+ language,
639
+ lineCount = 0,
640
+ charCount = 0,
641
+ cursorLine = 1,
642
+ cursorColumn = 1,
643
+ className
644
+ }) => {
645
+ return /* @__PURE__ */ jsxs4("div", { className, style: statusBarStyle, children: [
646
+ /* @__PURE__ */ jsxs4("span", { style: itemStyle, children: [
647
+ "Ln ",
648
+ cursorLine,
649
+ ", Col ",
650
+ cursorColumn
651
+ ] }),
652
+ /* @__PURE__ */ jsx4("span", { style: dividerStyle, children: "|" }),
653
+ /* @__PURE__ */ jsxs4("span", { style: itemStyle, children: [
654
+ lineCount,
655
+ " \u884C"
656
+ ] }),
657
+ /* @__PURE__ */ jsx4("span", { style: dividerStyle, children: "|" }),
658
+ /* @__PURE__ */ jsxs4("span", { style: itemStyle, children: [
659
+ charCount,
660
+ " \u5B57\u7B26"
661
+ ] }),
662
+ /* @__PURE__ */ jsx4("div", { style: { flex: 1 } }),
663
+ /* @__PURE__ */ jsx4("span", { style: itemStyle, children: language?.toUpperCase() ?? "" }),
664
+ /* @__PURE__ */ jsx4("span", { style: dividerStyle, children: "|" }),
665
+ /* @__PURE__ */ jsx4("span", { style: itemStyle, children: "UTF-8" })
666
+ ] });
667
+ };
668
+ var statusBarStyle = {
669
+ display: "flex",
670
+ alignItems: "center",
671
+ gap: 12,
672
+ padding: "0 16px",
673
+ height: 22,
674
+ fontSize: 11,
675
+ color: "#fff",
676
+ background: "rgb(29,78,216)"
677
+ };
678
+ var itemStyle = {
679
+ display: "flex",
680
+ alignItems: "center"
681
+ };
682
+ var dividerStyle = {
683
+ color: "rgba(255,255,255,0.5)"
684
+ };
685
+
686
+ // src/components/MonacoAiEditor.tsx
687
+ import { useCallback as useCallback3, useState as useState4 } from "react";
688
+
689
+ // src/context/EditorCoordinator.tsx
690
+ import { useContext as useContext2, useCallback as useCallback2, useMemo as useMemo2, useRef as useRef3, useState as useState3 } from "react";
691
+ import { EditorBus } from "@monaco-ai-editor/core";
692
+ import { jsx as jsx5 } from "react/jsx-runtime";
693
+ function EditorCoordinator({
694
+ children,
695
+ language: initialLang,
696
+ theme: initialTheme,
697
+ apiConfig: initialApiConfig,
698
+ sqlSchema,
699
+ completionProviders = [],
700
+ onApiConfigChange
701
+ }) {
702
+ const busRef = useRef3(new EditorBus());
703
+ const [editor, setEditor] = useState3(null);
704
+ const [chat, setChat] = useState3(null);
705
+ const [editorState, setEditorState] = useState3({
706
+ value: "",
707
+ language: initialLang ?? "typescript",
708
+ theme: initialTheme ?? "vs-dark",
709
+ cursorLine: 1,
710
+ cursorColumn: 1
711
+ });
712
+ const patchEditorState = useCallback2((patch) => {
713
+ setEditorState((prev) => ({ ...prev, ...patch }));
714
+ }, []);
715
+ const value = useMemo2(
716
+ () => ({
717
+ editor,
718
+ chat,
719
+ editorState,
720
+ patchEditorState,
721
+ setEditor,
722
+ setChat,
723
+ bus: busRef.current,
724
+ apiConfig: initialApiConfig ?? null,
725
+ sqlSchema: sqlSchema ?? {},
726
+ completionProviders,
727
+ onApiConfigChange
728
+ }),
729
+ [editor, chat, editorState, patchEditorState, initialApiConfig, sqlSchema, completionProviders, onApiConfigChange]
730
+ );
731
+ return /* @__PURE__ */ jsx5(EditorCoordinatorContext.Provider, { value, children });
732
+ }
733
+ function useEditorCoordinator() {
734
+ const ctx = useContext2(EditorCoordinatorContext);
735
+ if (!ctx) throw new Error("useEditorCoordinator must be used within <EditorCoordinator>");
736
+ return ctx;
737
+ }
738
+
739
+ // src/components/MonacoAiEditor.tsx
740
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
741
+ var MonacoAiEditor = ({
742
+ language = "typescript",
743
+ theme = "vs-dark",
744
+ apiConfig,
745
+ onApiConfigChange,
746
+ sqlSchema,
747
+ completionProviders,
748
+ showAiChat: showAiChatProp = true,
749
+ ...rest
750
+ }) => {
751
+ return /* @__PURE__ */ jsx6(
752
+ EditorCoordinator,
753
+ {
754
+ language,
755
+ theme,
756
+ apiConfig,
757
+ sqlSchema,
758
+ completionProviders,
759
+ onApiConfigChange,
760
+ children: /* @__PURE__ */ jsx6(MonacoAiEditorInner, { ...rest, showAiChat: showAiChatProp })
761
+ }
762
+ );
763
+ };
764
+ var MonacoAiEditorInner = ({ value, onChange, vsPath, showToolbar = true, showStatusBar = true, showAiChat = true, className }) => {
765
+ const coordinator = useEditorCoordinator();
766
+ const [aiVisible, setAiVisible] = useState4(showAiChat);
767
+ const onLangChange = useCallback3((lang) => {
768
+ coordinator.editor?.setLanguage(lang);
769
+ coordinator.patchEditorState({ language: lang });
770
+ }, [coordinator]);
771
+ const onThemeChange = useCallback3((theme) => {
772
+ coordinator.editor?.setTheme(theme);
773
+ coordinator.patchEditorState({ theme });
774
+ }, [coordinator]);
775
+ const onFormat = useCallback3(() => coordinator.editor?.format(), [coordinator]);
776
+ const onEditorMount = useCallback3((controller) => {
777
+ coordinator.setEditor(controller);
778
+ }, [coordinator]);
779
+ return /* @__PURE__ */ jsxs5(
780
+ "div",
781
+ {
782
+ className,
783
+ style: { display: "flex", flexDirection: "column", height: "100%", background: "rgb(17,24,39)" },
784
+ children: [
785
+ showToolbar && /* @__PURE__ */ jsx6(
786
+ Toolbar,
787
+ {
788
+ language: coordinator.editorState.language,
789
+ theme: coordinator.editorState.theme,
790
+ onLanguageChange: onLangChange,
791
+ onThemeChange,
792
+ onFormat,
793
+ onToggleAiChat: () => setAiVisible((v) => !v),
794
+ aiChatVisible: aiVisible
795
+ }
796
+ ),
797
+ /* @__PURE__ */ jsxs5("div", { style: { flex: 1, display: "flex", overflow: "hidden" }, children: [
798
+ /* @__PURE__ */ jsx6(
799
+ EditorPanel,
800
+ {
801
+ value,
802
+ onChange: (v) => {
803
+ onChange?.(v);
804
+ coordinator.patchEditorState({ value: v });
805
+ },
806
+ language: coordinator.editorState.language,
807
+ theme: coordinator.editorState.theme,
808
+ vsPath,
809
+ onMount: onEditorMount
810
+ }
811
+ ),
812
+ aiVisible && /* @__PURE__ */ jsx6(AiChatPanel, { onClose: () => setAiVisible(false) })
813
+ ] }),
814
+ showStatusBar && /* @__PURE__ */ jsx6(
815
+ StatusBar,
816
+ {
817
+ language: coordinator.editorState.language,
818
+ lineCount: coordinator.editorState.value.split("\n").length,
819
+ charCount: coordinator.editorState.value.length,
820
+ cursorLine: coordinator.editorState.cursorLine,
821
+ cursorColumn: coordinator.editorState.cursorColumn
822
+ }
823
+ )
824
+ ]
825
+ }
826
+ );
827
+ };
828
+
829
+ // src/utils/configureMonaco.ts
830
+ import { loader as loader2 } from "@monaco-editor/react";
831
+ import { LOCALES } from "@monaco-ai-editor/core";
832
+ function configureMonaco(options = {}) {
833
+ const { locale = LOCALES.ZH_CN, vsPath } = options;
834
+ const baseUrl = (import.meta.env?.BASE_URL ?? "/").replace(/\/$/, "");
835
+ const effectiveVsPath = vsPath ?? `${baseUrl}/vs`;
836
+ const config = {
837
+ paths: { vs: effectiveVsPath },
838
+ "vs/nls": {
839
+ availableLanguages: { "*": locale }
840
+ }
841
+ };
842
+ loader2.config(config);
843
+ }
844
+
845
+ // src/index.ts
846
+ import {
847
+ LANGUAGES as LANGUAGES2,
848
+ THEMES as THEMES2,
849
+ LOCALES as LOCALES2,
850
+ SQL_KEYWORDS,
851
+ SQL_FUNCTIONS,
852
+ SQL_DATA_TYPES
853
+ } from "@monaco-ai-editor/core";
854
+ export {
855
+ AiChatPanel,
856
+ EditorCoordinator,
857
+ EditorPanel,
858
+ LANGUAGES2 as LANGUAGES,
859
+ LOCALES2 as LOCALES,
860
+ MonacoAiEditor,
861
+ SQL_DATA_TYPES,
862
+ SQL_FUNCTIONS,
863
+ SQL_KEYWORDS,
864
+ StatusBar,
865
+ THEMES2 as THEMES,
866
+ Toolbar,
867
+ configureMonaco,
868
+ useEditorCoordinator
869
+ };
870
+ //# sourceMappingURL=index.js.map