@monaco-ai-editor/core 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,1032 @@
1
+ // src/constants.ts
2
+ var SQL_KEYWORDS = [
3
+ "SELECT",
4
+ "FROM",
5
+ "WHERE",
6
+ "INSERT",
7
+ "INTO",
8
+ "VALUES",
9
+ "UPDATE",
10
+ "SET",
11
+ "DELETE",
12
+ "CREATE",
13
+ "TABLE",
14
+ "DROP",
15
+ "ALTER",
16
+ "ADD",
17
+ "COLUMN",
18
+ "INDEX",
19
+ "VIEW",
20
+ "DATABASE",
21
+ "SCHEMA",
22
+ "TRUNCATE",
23
+ "RENAME",
24
+ "AS",
25
+ "ON",
26
+ "JOIN",
27
+ "INNER",
28
+ "LEFT",
29
+ "RIGHT",
30
+ "FULL",
31
+ "OUTER",
32
+ "CROSS",
33
+ "NATURAL",
34
+ "USING",
35
+ "AND",
36
+ "OR",
37
+ "NOT",
38
+ "IN",
39
+ "EXISTS",
40
+ "BETWEEN",
41
+ "LIKE",
42
+ "IS",
43
+ "NULL",
44
+ "TRUE",
45
+ "FALSE",
46
+ "CASE",
47
+ "WHEN",
48
+ "THEN",
49
+ "ELSE",
50
+ "END",
51
+ "IF",
52
+ "UNION",
53
+ "ALL",
54
+ "DISTINCT",
55
+ "ORDER",
56
+ "BY",
57
+ "ASC",
58
+ "DESC",
59
+ "GROUP",
60
+ "HAVING",
61
+ "LIMIT",
62
+ "OFFSET",
63
+ "FETCH",
64
+ "NEXT",
65
+ "ROWS",
66
+ "ONLY",
67
+ "WITH",
68
+ "RECURSIVE",
69
+ "PRIMARY",
70
+ "KEY",
71
+ "FOREIGN",
72
+ "REFERENCES",
73
+ "UNIQUE",
74
+ "NOT NULL",
75
+ "DEFAULT",
76
+ "CHECK",
77
+ "CONSTRAINT",
78
+ "AUTO_INCREMENT",
79
+ "IDENTITY",
80
+ "SEQUENCE",
81
+ "GRANT",
82
+ "REVOKE",
83
+ "COMMIT",
84
+ "ROLLBACK",
85
+ "TRANSACTION",
86
+ "BEGIN",
87
+ "SAVEPOINT",
88
+ "EXPLAIN"
89
+ ];
90
+ var SQL_FUNCTIONS = [
91
+ "COUNT",
92
+ "SUM",
93
+ "AVG",
94
+ "MIN",
95
+ "MAX",
96
+ "COALESCE",
97
+ "NULLIF",
98
+ "IFNULL",
99
+ "NVL",
100
+ "CONCAT",
101
+ "LENGTH",
102
+ "SUBSTR",
103
+ "SUBSTRING",
104
+ "TRIM",
105
+ "LTRIM",
106
+ "RTRIM",
107
+ "UPPER",
108
+ "LOWER",
109
+ "REPLACE",
110
+ "INSTR",
111
+ "LPAD",
112
+ "RPAD",
113
+ "CHAR_LENGTH",
114
+ "POSITION",
115
+ "NOW",
116
+ "CURDATE",
117
+ "CURTIME",
118
+ "DATE",
119
+ "TIME",
120
+ "YEAR",
121
+ "MONTH",
122
+ "DAY",
123
+ "HOUR",
124
+ "MINUTE",
125
+ "SECOND",
126
+ "DATEDIFF",
127
+ "DATE_ADD",
128
+ "DATE_SUB",
129
+ "DATEADD",
130
+ "DATESUB",
131
+ "EXTRACT",
132
+ "TO_DATE",
133
+ "TO_CHAR",
134
+ "DATE_FORMAT",
135
+ "TIMESTAMPDIFF",
136
+ "ROUND",
137
+ "CEIL",
138
+ "FLOOR",
139
+ "ABS",
140
+ "MOD",
141
+ "POWER",
142
+ "SQRT",
143
+ "SIGN",
144
+ "RAND",
145
+ "CAST",
146
+ "CONVERT",
147
+ "ROW_NUMBER",
148
+ "RANK",
149
+ "DENSE_RANK",
150
+ "NTILE",
151
+ "LAG",
152
+ "LEAD",
153
+ "FIRST_VALUE",
154
+ "LAST_VALUE",
155
+ "OVER",
156
+ "PARTITION",
157
+ "LISTAGG",
158
+ "GROUP_CONCAT",
159
+ "STRING_AGG",
160
+ "JSON_OBJECT",
161
+ "JSON_ARRAY",
162
+ "JSON_EXTRACT",
163
+ "ISNULL"
164
+ ];
165
+ var SQL_DATA_TYPES = [
166
+ "INT",
167
+ "INTEGER",
168
+ "BIGINT",
169
+ "SMALLINT",
170
+ "TINYINT",
171
+ "DECIMAL",
172
+ "NUMERIC",
173
+ "FLOAT",
174
+ "DOUBLE",
175
+ "REAL",
176
+ "CHAR",
177
+ "VARCHAR",
178
+ "TEXT",
179
+ "NCHAR",
180
+ "NVARCHAR",
181
+ "NTEXT",
182
+ "DATE",
183
+ "TIME",
184
+ "DATETIME",
185
+ "TIMESTAMP",
186
+ "BOOLEAN",
187
+ "BOOL",
188
+ "BLOB",
189
+ "CLOB",
190
+ "BINARY",
191
+ "VARBINARY",
192
+ "JSON",
193
+ "UUID",
194
+ "SERIAL"
195
+ ];
196
+ var LANGUAGES = [
197
+ "javascript",
198
+ "typescript",
199
+ "python",
200
+ "json",
201
+ "css",
202
+ "html",
203
+ "markdown",
204
+ "rust",
205
+ "go",
206
+ "sql"
207
+ ];
208
+ var LOCALES = {
209
+ ZH_CN: "zh-cn",
210
+ EN: "en",
211
+ DE: "de",
212
+ ES: "es",
213
+ FR: "fr",
214
+ IT: "it",
215
+ JA: "ja",
216
+ KO: "ko",
217
+ RU: "ru"
218
+ };
219
+ var THEMES = [
220
+ { value: "vs-dark", label: "Dark" },
221
+ { value: "light", label: "Light" },
222
+ { value: "hc-black", label: "High Contrast" }
223
+ ];
224
+ var AI_INLINE_LANGUAGES = [
225
+ "typescript",
226
+ "javascript",
227
+ "python",
228
+ "sql",
229
+ "java",
230
+ "cpp",
231
+ "csharp",
232
+ "go",
233
+ "rust"
234
+ ];
235
+
236
+ // src/bus/EventEmitter.ts
237
+ var EventEmitter = class {
238
+ constructor() {
239
+ this.listeners = {};
240
+ }
241
+ on(event, listener) {
242
+ if (!this.listeners[event]) this.listeners[event] = /* @__PURE__ */ new Set();
243
+ this.listeners[event].add(listener);
244
+ return () => this.off(event, listener);
245
+ }
246
+ off(event, listener) {
247
+ this.listeners[event]?.delete(listener);
248
+ }
249
+ emit(event, payload) {
250
+ this.listeners[event]?.forEach((listener) => {
251
+ try {
252
+ listener(payload);
253
+ } catch (err) {
254
+ console.error(`[EventEmitter] listener for "${String(event)}" threw:`, err);
255
+ }
256
+ });
257
+ }
258
+ removeAllListeners() {
259
+ this.listeners = {};
260
+ }
261
+ };
262
+
263
+ // src/completion/sqlCompletion.ts
264
+ function parseDynamicItems(sql) {
265
+ const tables = [];
266
+ const variables = [];
267
+ let m;
268
+ const tableRe = /\b(?:FROM|JOIN|UPDATE|INTO|TABLE)\s+([`"']?\w+[`"']?)/gi;
269
+ while ((m = tableRe.exec(sql)) !== null) {
270
+ const name = m[1].replace(/[`"']/g, "");
271
+ if (!tables.includes(name)) tables.push(name);
272
+ }
273
+ const cteRe = /\bWITH\s+(\w+)\s+AS\s*\(/gi;
274
+ while ((m = cteRe.exec(sql)) !== null) {
275
+ if (!tables.includes(m[1])) tables.push(m[1]);
276
+ }
277
+ const declareRe = /\bDECLARE\s+(@?\w+)/gi;
278
+ while ((m = declareRe.exec(sql)) !== null) {
279
+ if (!variables.includes(m[1])) variables.push(m[1]);
280
+ }
281
+ const setVarRe = /\bSET\s+(@\w+)\s*=/gi;
282
+ while ((m = setVarRe.exec(sql)) !== null) {
283
+ if (!variables.includes(m[1])) variables.push(m[1]);
284
+ }
285
+ const atVarRe = /@(\w+)/g;
286
+ while ((m = atVarRe.exec(sql)) !== null) {
287
+ const v = `@${m[1]}`;
288
+ if (!variables.includes(v)) variables.push(v);
289
+ }
290
+ return { tables, variables };
291
+ }
292
+ function registerSqlCompletion(monaco, schemaRef, excludeLabelsRef) {
293
+ return monaco.languages.registerCompletionItemProvider("sql", {
294
+ triggerCharacters: ["."],
295
+ provideCompletionItems(model, position, context) {
296
+ const word = model.getWordUntilPosition(position);
297
+ const range = {
298
+ startLineNumber: position.lineNumber,
299
+ endLineNumber: position.lineNumber,
300
+ startColumn: word.startColumn,
301
+ endColumn: word.endColumn
302
+ };
303
+ if (context.triggerCharacter === ".") {
304
+ const lineText = model.getLineContent(position.lineNumber);
305
+ const textBeforeDot = lineText.substring(0, position.column - 2);
306
+ const tableMatch = textBeforeDot.match(/(\w+)$/);
307
+ if (tableMatch) {
308
+ const typed = tableMatch[1].toUpperCase();
309
+ const schema2 = schemaRef.current;
310
+ const tableKey = Object.keys(schema2).find((k) => k.toUpperCase() === typed);
311
+ if (tableKey) {
312
+ const colRange = {
313
+ startLineNumber: position.lineNumber,
314
+ endLineNumber: position.lineNumber,
315
+ startColumn: position.column,
316
+ endColumn: position.column
317
+ };
318
+ return {
319
+ suggestions: schema2[tableKey].map((col) => ({
320
+ label: col,
321
+ kind: monaco.languages.CompletionItemKind.Field,
322
+ insertText: col,
323
+ range: colRange,
324
+ detail: `${tableKey} \xB7 \u5217`,
325
+ documentation: `\u8868 ${tableKey} \u7684\u5B57\u6BB5`
326
+ }))
327
+ };
328
+ }
329
+ }
330
+ return { suggestions: [] };
331
+ }
332
+ const fullText = model.getValue();
333
+ const { tables: dynamicTables, variables } = parseDynamicItems(fullText);
334
+ const schema = schemaRef.current;
335
+ const schemaTables = Object.keys(schema);
336
+ const allTables = [.../* @__PURE__ */ new Set([...schemaTables, ...dynamicTables])];
337
+ const exclude = excludeLabelsRef?.current;
338
+ const isExcluded = (label) => exclude?.has(label.toUpperCase()) ?? false;
339
+ const keywords = SQL_KEYWORDS.filter((kw) => !isExcluded(kw)).map((kw) => ({
340
+ label: kw,
341
+ kind: monaco.languages.CompletionItemKind.Keyword,
342
+ insertText: kw,
343
+ range,
344
+ detail: "SQL \u5173\u952E\u5B57"
345
+ }));
346
+ const functions = SQL_FUNCTIONS.filter((fn) => !isExcluded(fn)).map((fn) => ({
347
+ label: fn,
348
+ kind: monaco.languages.CompletionItemKind.Function,
349
+ insertText: `${fn}($1)`,
350
+ insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
351
+ range,
352
+ detail: "SQL \u51FD\u6570"
353
+ }));
354
+ const dataTypes = SQL_DATA_TYPES.map((dt) => ({
355
+ label: dt,
356
+ kind: monaco.languages.CompletionItemKind.TypeParameter,
357
+ insertText: dt,
358
+ range,
359
+ detail: "SQL \u6570\u636E\u7C7B\u578B"
360
+ }));
361
+ const tableItems = allTables.map((t) => ({
362
+ label: t,
363
+ kind: monaco.languages.CompletionItemKind.Class,
364
+ insertText: t,
365
+ range,
366
+ detail: schemaTables.includes(t) ? "\u8868\u540D (Schema)" : "\u8868\u540D (\u6587\u6863)"
367
+ }));
368
+ const variableItems = variables.map((v) => ({
369
+ label: v,
370
+ kind: monaco.languages.CompletionItemKind.Variable,
371
+ insertText: v,
372
+ range,
373
+ detail: "SQL \u53D8\u91CF"
374
+ }));
375
+ return {
376
+ suggestions: [...keywords, ...functions, ...dataTypes, ...tableItems, ...variableItems]
377
+ };
378
+ }
379
+ });
380
+ }
381
+
382
+ // src/ai/aiCompletion.ts
383
+ function stripCodeFence(text) {
384
+ return text.replace(/^```[a-z]*\n?/, "").replace(/\n?```$/, "");
385
+ }
386
+ var SPINNER_FRAMES = ["\u28FE", "\u28FD", "\u28FB", "\u28BF", "\u287F", "\u28DF", "\u28EF", "\u28F7"];
387
+ function ensureSpinnerStyle() {
388
+ const id = "mae-ai-completion-spinner-style";
389
+ if (typeof document === "undefined" || document.getElementById(id)) return;
390
+ const style = document.createElement("style");
391
+ style.id = id;
392
+ const frameCss = SPINNER_FRAMES.map((ch, i) => `.mae-ai-spinner-f${i}::after { content: " ${ch}"; }`).join("\n");
393
+ style.textContent = `
394
+ @keyframes mae-ai-completion-glow {
395
+ 0%, 100% { text-shadow: 0 0 3px rgba(96, 165, 250, 0.35); }
396
+ 50% { text-shadow: 0 0 9px rgba(96, 165, 250, 0.95), 0 0 18px rgba(59, 130, 246, 0.35); }
397
+ }
398
+ ${frameCss}
399
+ [class^="mae-ai-spinner-f"]::after {
400
+ color: #60a5fa;
401
+ font-family: "Segoe UI Symbol", "Noto Sans Symbols", "Apple Symbols", monospace;
402
+ display: inline-block;
403
+ margin-left: 2px;
404
+ animation: mae-ai-completion-glow 1s ease-in-out infinite;
405
+ }`;
406
+ document.head.appendChild(style);
407
+ }
408
+ function createCursorSpinner(editor, line, col) {
409
+ ensureSpinnerStyle();
410
+ let frameIdx = 0;
411
+ const makeDecor = () => [{
412
+ range: { startLineNumber: line, startColumn: col, endLineNumber: line, endColumn: col },
413
+ options: { afterContentClassName: `mae-ai-spinner-f${frameIdx}` }
414
+ }];
415
+ const collection = editor.createDecorationsCollection(makeDecor());
416
+ const timer = setInterval(() => {
417
+ frameIdx = (frameIdx + 1) % SPINNER_FRAMES.length;
418
+ collection.set(makeDecor());
419
+ }, 80);
420
+ return () => {
421
+ clearInterval(timer);
422
+ collection.clear();
423
+ };
424
+ }
425
+ async function fetchAiCompletionNonStream(config, context, prefix, signal) {
426
+ const response = await fetch(`${config.baseUrl}/chat/completions`, {
427
+ method: "POST",
428
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${config.apiKey}` },
429
+ body: JSON.stringify({
430
+ model: config.model,
431
+ messages: [
432
+ {
433
+ role: "system",
434
+ content: "\u4F60\u662F\u4E00\u4E2A\u4EE3\u7801\u8865\u5168\u52A9\u624B\u3002\u57FA\u4E8E\u7528\u6237\u63D0\u4F9B\u7684\u4EE3\u7801\u4E0A\u4E0B\u6587\u548C\u524D\u7F00\uFF0C\u751F\u6210\u77ED\u5C0F\u7CBE\u608D\u7684\u4EE3\u7801\u8865\u5168\u3002\u53EA\u8FD4\u56DE\u8865\u5168\u7684\u4EE3\u7801\u7247\u6BB5\uFF0C\u4E0D\u8981\u89E3\u91CA\u3002"
435
+ },
436
+ {
437
+ role: "user",
438
+ content: `\u4EE3\u7801\u4E0A\u4E0B\u6587\uFF1A
439
+ ${context}
440
+
441
+ \u524D\u7F00\uFF1A${prefix}
442
+
443
+ \u8BF7\u7EE7\u7EED\u8865\u5168\u8FD9\u6BB5\u4EE3\u7801\uFF0C\u53EA\u8FD4\u56DE\u8865\u5168\u90E8\u5206\u3002`
444
+ }
445
+ ],
446
+ max_tokens: 1024,
447
+ stream: false
448
+ }),
449
+ signal
450
+ });
451
+ if (!response.ok) {
452
+ throw new Error(`HTTP ${response.status}: ${await response.text().catch(() => "")}`);
453
+ }
454
+ const data = await response.json();
455
+ return stripCodeFence(data.choices?.[0]?.message?.content?.trim() ?? "");
456
+ }
457
+ async function fetchAiCompletionStream(config, context, prefix, onChunk, signal) {
458
+ const response = await fetch(`${config.baseUrl}/chat/completions`, {
459
+ method: "POST",
460
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${config.apiKey}` },
461
+ body: JSON.stringify({
462
+ model: config.model,
463
+ messages: [
464
+ {
465
+ role: "system",
466
+ content: "\u4F60\u662F\u4E00\u4E2A\u4EE3\u7801\u8865\u5168\u52A9\u624B\u3002\u57FA\u4E8E\u7528\u6237\u63D0\u4F9B\u7684\u4EE3\u7801\u4E0A\u4E0B\u6587\u548C\u524D\u7F00\uFF0C\u751F\u6210\u77ED\u5C0F\u7CBE\u608D\u7684\u4EE3\u7801\u8865\u5168\u3002\u53EA\u8FD4\u56DE\u8865\u5168\u7684\u4EE3\u7801\u7247\u6BB5\uFF0C\u4E0D\u8981\u89E3\u91CA\u3002"
467
+ },
468
+ {
469
+ role: "user",
470
+ content: `\u4EE3\u7801\u4E0A\u4E0B\u6587\uFF1A
471
+ ${context}
472
+
473
+ \u524D\u7F00\uFF1A${prefix}
474
+
475
+ \u8BF7\u7EE7\u7EED\u8865\u5168\u8FD9\u6BB5\u4EE3\u7801\uFF0C\u53EA\u8FD4\u56DE\u8865\u5168\u90E8\u5206\u3002`
476
+ }
477
+ ],
478
+ max_tokens: 1024,
479
+ stream: true
480
+ }),
481
+ signal
482
+ });
483
+ if (!response.ok) {
484
+ throw new Error(`HTTP ${response.status}: ${await response.text().catch(() => "")}`);
485
+ }
486
+ if (!response.body) throw new Error("Response body is null");
487
+ const reader = response.body.getReader();
488
+ const decoder = new TextDecoder();
489
+ let buffer = "";
490
+ let accumulated = "";
491
+ while (true) {
492
+ const { done, value } = await reader.read();
493
+ if (done) break;
494
+ buffer += decoder.decode(value, { stream: true });
495
+ const lines = buffer.split("\n");
496
+ buffer = lines.pop() ?? "";
497
+ for (const line of lines) {
498
+ const trimmed = line.trim();
499
+ if (!trimmed.startsWith("data:")) continue;
500
+ const data = trimmed.slice(5).trim();
501
+ if (data === "[DONE]") return;
502
+ try {
503
+ const parsed = JSON.parse(data);
504
+ const delta = parsed.choices?.[0]?.delta?.content ?? "";
505
+ if (delta) {
506
+ accumulated += delta;
507
+ onChunk(accumulated);
508
+ }
509
+ } catch {
510
+ }
511
+ }
512
+ }
513
+ }
514
+ function registerAiInlineCompletion(monaco, pendingRef) {
515
+ AI_INLINE_LANGUAGES.forEach((lang) => {
516
+ monaco.languages.registerInlineCompletionsProvider(lang, {
517
+ provideInlineCompletions: (_model, position) => {
518
+ const pending = pendingRef.current;
519
+ if (!pending) return { items: [] };
520
+ if (pending.lineNumber !== position.lineNumber || pending.column !== position.column) {
521
+ pendingRef.current = null;
522
+ return { items: [] };
523
+ }
524
+ return {
525
+ items: [
526
+ {
527
+ insertText: pending.insertText,
528
+ range: {
529
+ startLineNumber: position.lineNumber,
530
+ endLineNumber: position.lineNumber,
531
+ startColumn: position.column,
532
+ endColumn: position.column
533
+ }
534
+ }
535
+ ]
536
+ };
537
+ },
538
+ disposeInlineCompletions: () => {
539
+ }
540
+ });
541
+ });
542
+ }
543
+ function registerAiCompletionCommand(editor, getApiConfig, pendingRef, onLoadingChange, onError) {
544
+ let currentAbortController = null;
545
+ const isAbortError = (err) => err instanceof Error && (err.name === "AbortError" || err.message?.includes("aborted"));
546
+ const triggerAiCompletion = async () => {
547
+ const apiConfig = getApiConfig();
548
+ if (!apiConfig || !apiConfig.apiKey.trim()) {
549
+ onError?.("\u672A\u914D\u7F6E API Key\uFF0C\u8BF7\u5148\u914D\u7F6E AI \u670D\u52A1\u518D\u4F7F\u7528\u8865\u5168\u529F\u80FD\u3002");
550
+ return;
551
+ }
552
+ const position = editor.getPosition();
553
+ if (!position) return;
554
+ const model = editor.getModel();
555
+ if (!model) return;
556
+ const lineText = model.getLineContent(position.lineNumber);
557
+ const textBeforePosition = lineText.substring(0, position.column - 1);
558
+ if (!textBeforePosition.trim()) return;
559
+ const startLine = Math.max(0, position.lineNumber - 5);
560
+ const contextLines = [];
561
+ for (let i = startLine; i < position.lineNumber; i++) {
562
+ contextLines.push(model.getLineContent(i + 1));
563
+ }
564
+ const contextCode = contextLines.join("\n");
565
+ currentAbortController?.abort();
566
+ currentAbortController = new AbortController();
567
+ pendingRef.current = null;
568
+ const stopSpinner = createCursorSpinner(editor, position.lineNumber, position.column);
569
+ onLoadingChange?.(true);
570
+ const useStream = apiConfig.streamMode !== false;
571
+ try {
572
+ if (useStream) {
573
+ let triggerTimer = null;
574
+ const scheduleTrigger = () => {
575
+ if (triggerTimer !== null) clearTimeout(triggerTimer);
576
+ triggerTimer = setTimeout(() => {
577
+ triggerTimer = null;
578
+ if (pendingRef.current) {
579
+ editor.trigger("ai", "editor.action.inlineSuggest.trigger", {});
580
+ }
581
+ }, 80);
582
+ };
583
+ await fetchAiCompletionStream(
584
+ apiConfig,
585
+ contextCode,
586
+ textBeforePosition,
587
+ (accumulated) => {
588
+ let insertText = stripCodeFence(accumulated);
589
+ if (insertText.startsWith(textBeforePosition)) {
590
+ insertText = insertText.slice(textBeforePosition.length);
591
+ }
592
+ if (!insertText.trim()) return;
593
+ pendingRef.current = {
594
+ lineNumber: position.lineNumber,
595
+ column: position.column,
596
+ insertText
597
+ };
598
+ scheduleTrigger();
599
+ },
600
+ currentAbortController.signal
601
+ );
602
+ if (triggerTimer !== null) clearTimeout(triggerTimer);
603
+ if (pendingRef.current) {
604
+ editor.trigger("ai", "editor.action.inlineSuggest.trigger", {});
605
+ }
606
+ } else {
607
+ const completion = await fetchAiCompletionNonStream(
608
+ apiConfig,
609
+ contextCode,
610
+ textBeforePosition,
611
+ currentAbortController.signal
612
+ );
613
+ if (!completion) return;
614
+ let insertText = completion;
615
+ if (insertText.startsWith(textBeforePosition)) {
616
+ insertText = insertText.slice(textBeforePosition.length);
617
+ }
618
+ if (!insertText.trim()) return;
619
+ pendingRef.current = {
620
+ lineNumber: position.lineNumber,
621
+ column: position.column,
622
+ insertText
623
+ };
624
+ editor.trigger("ai", "editor.action.inlineSuggest.trigger", {});
625
+ }
626
+ } catch (err) {
627
+ if (isAbortError(err)) return;
628
+ const msg = err instanceof Error ? err.message : String(err);
629
+ onError?.(`AI \u8865\u5168\u5931\u8D25\uFF1A${msg}`);
630
+ console.error("[AI Completion] Error:", err);
631
+ } finally {
632
+ stopSpinner();
633
+ onLoadingChange?.(false);
634
+ }
635
+ };
636
+ return editor.onKeyDown((e) => {
637
+ if ((e.ctrlKey || e.metaKey) && e.altKey && e.code === "KeyL") {
638
+ e.preventDefault();
639
+ void triggerAiCompletion();
640
+ }
641
+ });
642
+ }
643
+
644
+ // src/controllers/EditorController.ts
645
+ var EditorController = class extends EventEmitter {
646
+ constructor(options) {
647
+ super();
648
+ this.options = options;
649
+ this.editor = null;
650
+ this.sqlProviderRegistered = false;
651
+ this.aiProviderRegistered = false;
652
+ this.pendingCompletionRef = { current: null };
653
+ this.customProviders = [];
654
+ this.disposables = [];
655
+ this.completed = false;
656
+ this.error = null;
657
+ this.monaco = options.monaco;
658
+ this.customProviders = options.completionProviders ?? [];
659
+ this.init();
660
+ }
661
+ init() {
662
+ const { language, theme, value, sqlSchema, apiConfig, editorOptions } = this.options;
663
+ if (!this.sqlProviderRegistered) {
664
+ const schemaRef = { current: sqlSchema ?? {} };
665
+ registerSqlCompletion(this.monaco, schemaRef);
666
+ this.sqlProviderRegistered = true;
667
+ }
668
+ if (!this.aiProviderRegistered) {
669
+ registerAiInlineCompletion(this.monaco, this.pendingCompletionRef);
670
+ this.aiProviderRegistered = true;
671
+ }
672
+ this.registerCustomCompletionProviders();
673
+ this.editor = this.monaco.editor.create(this.options.container, {
674
+ value: value ?? "",
675
+ language: language ?? "typescript",
676
+ theme: theme ?? "vs-dark",
677
+ fontSize: 14,
678
+ minimap: { enabled: true },
679
+ scrollBeyondLastLine: false,
680
+ automaticLayout: true,
681
+ lineNumbers: "on",
682
+ renderLineHighlight: "all",
683
+ smoothScrolling: true,
684
+ cursorBlinking: "smooth",
685
+ bracketPairColorization: { enabled: true },
686
+ tabSize: 2,
687
+ wordWrap: "on",
688
+ folding: true,
689
+ formatOnPaste: true,
690
+ suggestOnTriggerCharacters: true,
691
+ quickSuggestions: { other: true, comments: false, strings: false },
692
+ acceptSuggestionOnCommitCharacter: true,
693
+ acceptSuggestionOnEnter: "on",
694
+ inlineSuggest: { enabled: true },
695
+ tabCompletion: "on",
696
+ ...editorOptions
697
+ });
698
+ this.bindEditorEvents();
699
+ this.emit("ready", { editor: this.editor });
700
+ }
701
+ bindEditorEvents() {
702
+ const editor = this.editor;
703
+ const d1 = editor.onDidChangeModelContent(() => {
704
+ this.emit("change", { value: editor.getValue() });
705
+ });
706
+ const d2 = editor.onDidChangeCursorPosition((e) => {
707
+ this.emit("cursor", {
708
+ line: e.position.lineNumber,
709
+ column: e.position.column
710
+ });
711
+ });
712
+ const d3 = editor.onDidFocusEditorText(() => this.emit("focus", void 0));
713
+ const d4 = editor.onDidBlurEditorText(() => this.emit("blur", void 0));
714
+ this.disposables.push(d1, d2, d3, d4);
715
+ }
716
+ registerCustomCompletionProviders() {
717
+ if (this.customProviders.length === 0) return;
718
+ for (const provider of this.customProviders) {
719
+ const langs = provider.languages ?? ["*"];
720
+ for (const lang of langs) {
721
+ this.monaco.languages.registerCompletionItemProvider(lang, {
722
+ triggerCharacters: provider.triggerCharacters,
723
+ provideCompletionItems: async (model, position, context) => {
724
+ const ctx = {
725
+ monaco: this.monaco,
726
+ model,
727
+ position,
728
+ lineText: model.getLineContent(position.lineNumber),
729
+ currentWord: model.getWordUntilPosition(position).word,
730
+ fullText: model.getValue(),
731
+ triggerCharacter: context.triggerCharacter
732
+ };
733
+ if (provider.shouldTrigger && !provider.shouldTrigger(ctx)) {
734
+ return { suggestions: [] };
735
+ }
736
+ const suggestions = await provider.provide(ctx);
737
+ return { suggestions };
738
+ }
739
+ });
740
+ }
741
+ }
742
+ }
743
+ // ─── 公开 API ──────────────────────────────────────────────────────────
744
+ getEditor() {
745
+ return this.editor;
746
+ }
747
+ getValue() {
748
+ return this.editor?.getValue() ?? "";
749
+ }
750
+ setValue(value) {
751
+ this.editor?.setValue(value);
752
+ }
753
+ insertText(text, range) {
754
+ const editor = this.editor;
755
+ if (!editor) return;
756
+ const targetRange = range ?? editor.getSelection();
757
+ if (targetRange) {
758
+ editor.executeEdits("controller-insert", [{ range: targetRange, text }]);
759
+ editor.focus();
760
+ }
761
+ }
762
+ format() {
763
+ this.editor?.getAction("editor.action.formatDocument")?.run();
764
+ }
765
+ focus() {
766
+ this.editor?.focus();
767
+ }
768
+ setLanguage(language) {
769
+ const model = this.editor?.getModel();
770
+ if (model) {
771
+ this.monaco.editor.setModelLanguage(model, language);
772
+ this.emit("languageChange", { language });
773
+ }
774
+ }
775
+ setTheme(theme) {
776
+ this.monaco.editor.setTheme(theme);
777
+ }
778
+ getCursorPosition() {
779
+ const pos = this.editor?.getPosition();
780
+ return pos ? { line: pos.lineNumber, column: pos.column } : { line: 1, column: 1 };
781
+ }
782
+ getSelection() {
783
+ return this.editor?.getSelection() ?? null;
784
+ }
785
+ /**
786
+ * 触发 AI 行内补全(Ctrl+Alt+L 快捷键)。
787
+ * 需要提前设置 apiConfig(通过 setApiConfig 或构造函数传入)。
788
+ */
789
+ setApiConfig(config) {
790
+ this.options.apiConfig = config;
791
+ }
792
+ getPendingCompletionRef() {
793
+ return this.pendingCompletionRef;
794
+ }
795
+ dispose() {
796
+ for (const d of this.disposables) d.dispose();
797
+ this.disposables = [];
798
+ this.editor?.dispose();
799
+ this.editor = null;
800
+ this.removeAllListeners();
801
+ }
802
+ };
803
+
804
+ // src/controllers/AiChatController.ts
805
+ function generateUUID() {
806
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
807
+ return crypto.randomUUID();
808
+ }
809
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
810
+ const r = Math.random() * 16 | 0;
811
+ const v = c === "x" ? r : r & 3 | 8;
812
+ return v.toString(16);
813
+ });
814
+ }
815
+ var AiChatController = class extends EventEmitter {
816
+ constructor(apiConfig) {
817
+ super();
818
+ this.messages = [];
819
+ this.abortController = null;
820
+ this.loading = false;
821
+ this.apiConfig = apiConfig;
822
+ }
823
+ setApiConfig(config) {
824
+ this.apiConfig = config;
825
+ }
826
+ getApiConfig() {
827
+ return this.apiConfig;
828
+ }
829
+ getMessages() {
830
+ return this.messages;
831
+ }
832
+ isLoading() {
833
+ return this.loading;
834
+ }
835
+ /** 发送消息,可选携带编辑器上下文 */
836
+ async send(userInput, options) {
837
+ const trimmed = userInput.trim();
838
+ if (!trimmed || this.loading) return;
839
+ if (!this.apiConfig.apiKey.trim()) {
840
+ const userMsg2 = { id: generateUUID(), role: "user", content: trimmed };
841
+ this.messages.push(userMsg2);
842
+ this.emit("messageAdded", userMsg2);
843
+ const assistantMsg2 = {
844
+ id: generateUUID(),
845
+ role: "assistant",
846
+ content: "\u26A0\uFE0F \u5C1A\u672A\u914D\u7F6E API Key\uFF0C\u8BF7\u914D\u7F6E Base URL\u3001API Key \u548C\u6A21\u578B\u540D\u79F0\u540E\u518D\u5F00\u59CB\u5BF9\u8BDD\u3002"
847
+ };
848
+ this.messages.push(assistantMsg2);
849
+ this.emit("messageAdded", assistantMsg2);
850
+ return;
851
+ }
852
+ const userMsg = { id: generateUUID(), role: "user", content: trimmed };
853
+ this.messages.push(userMsg);
854
+ this.emit("messageAdded", userMsg);
855
+ const assistantMsg = {
856
+ id: generateUUID(),
857
+ role: "assistant",
858
+ content: "",
859
+ isStreaming: true
860
+ };
861
+ this.messages.push(assistantMsg);
862
+ this.emit("messageAdded", assistantMsg);
863
+ this.loading = true;
864
+ this.emit("loadingChange", { loading: true });
865
+ try {
866
+ this.abortController = new AbortController();
867
+ const systemPrompt = options?.language ? `You are an expert coding assistant. The user is working with ${options.language} code. Respond concisely in the same language as the user's message, defaulting to Chinese.` : "You are an expert coding assistant. Respond concisely in the same language as the user's message, defaulting to Chinese.";
868
+ const historyForApi = this.messages.filter((m) => m.id !== assistantMsg.id).map((m) => ({ role: m.role, content: m.content }));
869
+ const response = await fetch(`${this.apiConfig.baseUrl}/chat/completions`, {
870
+ method: "POST",
871
+ headers: {
872
+ "Content-Type": "application/json",
873
+ Authorization: `Bearer ${this.apiConfig.apiKey}`
874
+ },
875
+ body: JSON.stringify({
876
+ model: this.apiConfig.model,
877
+ messages: [{ role: "system", content: systemPrompt }, ...historyForApi],
878
+ stream: true
879
+ }),
880
+ signal: this.abortController.signal
881
+ });
882
+ if (!response.ok) {
883
+ let errText = "";
884
+ try {
885
+ errText = await response.text();
886
+ } catch {
887
+ }
888
+ let hint = "";
889
+ if (response.status === 401) hint = "\uFF08API Key \u65E0\u6548\u6216\u5DF2\u8FC7\u671F\uFF09";
890
+ else if (response.status === 403) hint = "\uFF08\u65E0\u8BBF\u95EE\u6743\u9650\uFF0C\u8BF7\u68C0\u67E5 API Key \u548C Base URL\uFF09";
891
+ else if (response.status === 429) hint = "\uFF08\u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\uFF09";
892
+ else if (response.status >= 500) hint = "\uFF08\u670D\u52A1\u7AEF\u9519\u8BEF\uFF09";
893
+ throw new Error(`HTTP ${response.status}${hint}${errText ? `
894
+ ${errText}` : ""}`);
895
+ }
896
+ const reader = response.body.getReader();
897
+ const decoder = new TextDecoder();
898
+ let buffer = "";
899
+ let fullContent = "";
900
+ while (true) {
901
+ const { done, value } = await reader.read();
902
+ if (done) break;
903
+ buffer += decoder.decode(value, { stream: true });
904
+ const lines = buffer.split("\n");
905
+ buffer = lines.pop() ?? "";
906
+ for (const line of lines) {
907
+ if (!line.startsWith("data: ")) continue;
908
+ const data = line.slice(6).trim();
909
+ if (data === "[DONE]") break;
910
+ try {
911
+ const parsed = JSON.parse(data);
912
+ const delta = parsed.choices?.[0]?.delta?.content ?? "";
913
+ if (delta) {
914
+ fullContent += delta;
915
+ assistantMsg.content = fullContent;
916
+ this.emit("messageUpdated", { ...assistantMsg });
917
+ }
918
+ } catch {
919
+ }
920
+ }
921
+ }
922
+ assistantMsg.isStreaming = false;
923
+ this.emit("messageUpdated", { ...assistantMsg });
924
+ } catch (err) {
925
+ const isAbort = err instanceof Error && err.name === "AbortError";
926
+ const errMsg = err instanceof Error ? err.message : String(err);
927
+ const isNetworkErr = err instanceof TypeError && errMsg.toLowerCase().includes("fetch");
928
+ const errorContent = isAbort ? "\u23F9 \u8BF7\u6C42\u5DF2\u53D6\u6D88" : isNetworkErr ? `\u26A0\uFE0F \u7F51\u7EDC\u9519\u8BEF\uFF0C\u8BF7\u68C0\u67E5 Base URL \u548C\u7F51\u7EDC\u3002
929
+
930
+ ${errMsg}` : `\u26A0\uFE0F \u8BF7\u6C42\u5931\u8D25\uFF1A${errMsg}`;
931
+ assistantMsg.content = errorContent;
932
+ assistantMsg.isStreaming = false;
933
+ this.emit("messageUpdated", { ...assistantMsg });
934
+ if (!isAbort) this.emit("error", { message: errMsg });
935
+ } finally {
936
+ this.loading = false;
937
+ this.emit("loadingChange", { loading: false });
938
+ }
939
+ }
940
+ stop() {
941
+ this.abortController?.abort();
942
+ }
943
+ clear() {
944
+ this.messages = [];
945
+ this.emit("cleared", void 0);
946
+ }
947
+ dispose() {
948
+ this.abortController?.abort();
949
+ this.removeAllListeners();
950
+ }
951
+ };
952
+
953
+ // src/bus/EditorBus.ts
954
+ var EditorBus = class extends EventEmitter {
955
+ constructor() {
956
+ super(...arguments);
957
+ this.editor = null;
958
+ this.chat = null;
959
+ }
960
+ registerEditor(controller) {
961
+ this.editor = controller;
962
+ const offChange = controller.on("change", (data) => this.emit("editor:change", data));
963
+ const offCursor = controller.on("cursor", (data) => this.emit("editor:cursor", data));
964
+ const offLang = controller.on("languageChange", (data) => this.emit("editor:languageChange", data));
965
+ const offInsert = this.on("chat:insertCode", ({ code }) => controller.insertText(code));
966
+ const offReplace = this.on("chat:replaceCode", ({ code }) => controller.setValue(code));
967
+ this.emit("editor:ready", controller);
968
+ return () => {
969
+ offChange();
970
+ offCursor();
971
+ offLang();
972
+ offInsert();
973
+ offReplace();
974
+ if (this.editor === controller) {
975
+ this.editor = null;
976
+ this.emit("editor:dispose", void 0);
977
+ }
978
+ };
979
+ }
980
+ registerChat(controller) {
981
+ this.chat = controller;
982
+ this.emit("chat:ready", controller);
983
+ return () => {
984
+ if (this.chat === controller) {
985
+ this.chat = null;
986
+ this.emit("chat:dispose", void 0);
987
+ }
988
+ };
989
+ }
990
+ getEditor() {
991
+ return this.editor;
992
+ }
993
+ getChat() {
994
+ return this.chat;
995
+ }
996
+ /** 让聊天面板向编辑器请求当前内容 */
997
+ getEditorContent() {
998
+ return this.editor?.getValue() ?? "";
999
+ }
1000
+ /** 让聊天面板向编辑器请求当前语言 */
1001
+ getEditorLanguage() {
1002
+ const editor = this.editor?.getEditor();
1003
+ return editor?.getModel()?.getLanguageId();
1004
+ }
1005
+ /** 让聊天面板请求向编辑器插入代码 */
1006
+ insertToEditor(code) {
1007
+ this.emit("chat:insertCode", { code });
1008
+ }
1009
+ /** 让聊天面板请求替换编辑器全部内容 */
1010
+ replaceEditor(code) {
1011
+ this.emit("chat:replaceCode", { code });
1012
+ }
1013
+ };
1014
+ export {
1015
+ AI_INLINE_LANGUAGES,
1016
+ AiChatController,
1017
+ EditorBus,
1018
+ EditorController,
1019
+ EventEmitter,
1020
+ LANGUAGES,
1021
+ LOCALES,
1022
+ SQL_DATA_TYPES,
1023
+ SQL_FUNCTIONS,
1024
+ SQL_KEYWORDS,
1025
+ THEMES,
1026
+ fetchAiCompletionNonStream,
1027
+ fetchAiCompletionStream,
1028
+ registerAiCompletionCommand,
1029
+ registerAiInlineCompletion,
1030
+ registerSqlCompletion
1031
+ };
1032
+ //# sourceMappingURL=index.js.map