@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 +1074 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +295 -0
- package/dist/index.d.ts +295 -0
- package/dist/index.js +1032 -0
- package/dist/index.js.map +1 -0
- package/package.json +36 -0
- package/src/ai/aiCompletion.ts +310 -0
- package/src/bus/EditorBus.ts +102 -0
- package/src/bus/EventEmitter.ts +33 -0
- package/src/completion/sqlCompletion.ts +151 -0
- package/src/constants.ts +71 -0
- package/src/controllers/AiChatController.ts +198 -0
- package/src/controllers/EditorController.ts +231 -0
- package/src/index.ts +47 -0
- package/src/types.ts +70 -0
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
|