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