@sciexpr/ai 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,545 @@
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
+ AIPipeline: () => AIPipeline,
24
+ BUILTIN_PROMPTS: () => BUILTIN_PROMPTS,
25
+ BuiltinCompletionProvider: () => BuiltinCompletionProvider,
26
+ BuiltinFormatter: () => BuiltinFormatter,
27
+ BuiltinKnowledgeBase: () => BuiltinKnowledgeBase,
28
+ BuiltinNormalizer: () => BuiltinNormalizer,
29
+ PromptRegistry: () => PromptRegistry
30
+ });
31
+ module.exports = __toCommonJS(index_exports);
32
+
33
+ // src/fallback.ts
34
+ var import_core = require("@sciexpr/core");
35
+ var BUILTIN_SYMBOLS = [
36
+ // Greek lowercase
37
+ { text: "\\alpha", label: "\u03B1 alpha", category: "symbol", score: 100, description: "Greek alpha" },
38
+ { text: "\\beta", label: "\u03B2 beta", category: "symbol", score: 100, description: "Greek beta" },
39
+ { text: "\\gamma", label: "\u03B3 gamma", category: "symbol", score: 100, description: "Greek gamma" },
40
+ { text: "\\delta", label: "\u03B4 delta", category: "symbol", score: 100, description: "Greek delta" },
41
+ { text: "\\epsilon", label: "\u03B5 epsilon", category: "symbol", score: 100, description: "Greek epsilon" },
42
+ { text: "\\zeta", label: "\u03B6 zeta", category: "symbol", score: 100, description: "Greek zeta" },
43
+ { text: "\\eta", label: "\u03B7 eta", category: "symbol", score: 100, description: "Greek eta" },
44
+ { text: "\\theta", label: "\u03B8 theta", category: "symbol", score: 100, description: "Greek theta" },
45
+ { text: "\\iota", label: "\u03B9 iota", category: "symbol", score: 100, description: "Greek iota" },
46
+ { text: "\\kappa", label: "\u03BA kappa", category: "symbol", score: 100, description: "Greek kappa" },
47
+ { text: "\\lambda", label: "\u03BB lambda", category: "symbol", score: 100, description: "Greek lambda" },
48
+ { text: "\\mu", label: "\u03BC mu", category: "symbol", score: 100, description: "Greek mu" },
49
+ { text: "\\pi", label: "\u03C0 pi", category: "symbol", score: 100, description: "Greek pi" },
50
+ { text: "\\sigma", label: "\u03C3 sigma", category: "symbol", score: 100, description: "Greek sigma" },
51
+ { text: "\\tau", label: "\u03C4 tau", category: "symbol", score: 100, description: "Greek tau" },
52
+ { text: "\\phi", label: "\u03C6 phi", category: "symbol", score: 100, description: "Greek phi" },
53
+ { text: "\\omega", label: "\u03C9 omega", category: "symbol", score: 100, description: "Greek omega" },
54
+ // Greek uppercase
55
+ { text: "\\Gamma", label: "\u0393 Gamma", category: "symbol", score: 95, description: "Greek Gamma" },
56
+ { text: "\\Delta", label: "\u0394 Delta", category: "symbol", score: 95, description: "Greek Delta" },
57
+ { text: "\\Theta", label: "\u0398 Theta", category: "symbol", score: 95, description: "Greek Theta" },
58
+ { text: "\\Lambda", label: "\u039B Lambda", category: "symbol", score: 95, description: "Greek Lambda" },
59
+ { text: "\\Sigma", label: "\u03A3 Sigma", category: "symbol", score: 95, description: "Greek Sigma" },
60
+ { text: "\\Omega", label: "\u03A9 Omega", category: "symbol", score: 95, description: "Greek Omega" },
61
+ // Common commands
62
+ { text: "\\frac{}{}", label: "\\frac \u2014 Fraction", category: "command", score: 90, description: "Fraction: \\frac{num}{den}" },
63
+ { text: "\\sqrt{}", label: "\\sqrt \u2014 Square root", category: "command", score: 90, description: "Square root" },
64
+ { text: "\\sum", label: "\\sum \u2014 Summation", category: "command", score: 85, description: "Summation operator" },
65
+ { text: "\\prod", label: "\\prod \u2014 Product", category: "command", score: 85, description: "Product operator" },
66
+ { text: "\\int", label: "\\int \u2014 Integral", category: "command", score: 85, description: "Integral" },
67
+ { text: "\\lim", label: "\\lim \u2014 Limit", category: "command", score: 85, description: "Limit" },
68
+ { text: "\\infty", label: "\\infty \u2014 Infinity", category: "symbol", score: 90, description: "Infinity symbol" },
69
+ { text: "\\partial", label: "\\partial \u2014 Partial", category: "symbol", score: 90, description: "Partial derivative" },
70
+ { text: "\\nabla", label: "\\nabla \u2014 Nabla", category: "symbol", score: 90, description: "Nabla/del operator" },
71
+ // Common templates
72
+ { text: "\\frac{}{}", label: "Fraction", category: "template", score: 80 },
73
+ { text: "\\sqrt{}", label: "Square Root", category: "template", score: 80 },
74
+ { text: "\\sum_{}^{}", label: "Sum", category: "template", score: 80 },
75
+ { text: "\\int_{}^{}", label: "Integral", category: "template", score: 80 },
76
+ { text: "\\lim_{x \\to }{}", label: "Limit", category: "template", score: 75 },
77
+ { text: "\\begin{pmatrix} & \\\\ & \\end{pmatrix}", label: "Matrix 2x2", category: "template", score: 70 },
78
+ // Chemical
79
+ { text: "\\ce{H2O}", label: "H\u2082O \u2014 Water", category: "formula", score: 80, description: "Water" },
80
+ { text: "\\ce{H2SO4}", label: "H\u2082SO\u2084 \u2014 Sulfuric acid", category: "formula", score: 80, description: "Sulfuric acid" },
81
+ { text: "\\ce{NaOH}", label: "NaOH \u2014 Sodium hydroxide", category: "formula", score: 80, description: "Sodium hydroxide" },
82
+ { text: "\\ce{NaCl}", label: "NaCl \u2014 Salt", category: "formula", score: 80, description: "Table salt" },
83
+ { text: "\\ce{CO2}", label: "CO\u2082 \u2014 Carbon dioxide", category: "formula", score: 80, description: "Carbon dioxide" }
84
+ ];
85
+ var BuiltinCompletionProvider = class {
86
+ id = "builtin-completion";
87
+ name = "Built-in Symbol Completion";
88
+ async complete(context) {
89
+ const beforeCursor = context.currentInput.slice(0, context.cursorPosition);
90
+ const lastToken = getLastToken(beforeCursor);
91
+ if (!lastToken) {
92
+ return { items: [], source: "local", latencyMs: 0 };
93
+ }
94
+ const matched = BUILTIN_SYMBOLS.filter((item) => item.text.startsWith(lastToken) || item.label.toLowerCase().includes(lastToken.toLowerCase())).sort((a, b) => b.score - a.score).slice(0, 10);
95
+ return {
96
+ items: matched,
97
+ source: "local",
98
+ latencyMs: 0
99
+ };
100
+ }
101
+ };
102
+ function getLastToken(input) {
103
+ const backslashIdx = input.lastIndexOf("\\");
104
+ if (backslashIdx !== -1) {
105
+ const afterBackslash = input.slice(backslashIdx);
106
+ const match2 = afterBackslash.match(/^\\[a-zA-Z]*$/);
107
+ if (match2) return match2[0];
108
+ }
109
+ const match = input.match(/([a-zA-Z_]+)$/);
110
+ return match ? match[1] : "";
111
+ }
112
+ var CHEMICAL_ELEMENTS = [
113
+ { symbol: "H", name: "\u6C22 / Hydrogen", atomicNumber: 1, atomicMass: 1.008, group: 1, period: 1, block: "s", electronegativity: 2.2, oxidationStates: [1, -1] },
114
+ { symbol: "He", name: "\u6C26 / Helium", atomicNumber: 2, atomicMass: 4.0026, group: 18, period: 1, block: "s" },
115
+ { symbol: "Li", name: "\u9502 / Lithium", atomicNumber: 3, atomicMass: 6.94, group: 1, period: 2, block: "s", electronegativity: 0.98, oxidationStates: [1] },
116
+ { symbol: "C", name: "\u78B3 / Carbon", atomicNumber: 6, atomicMass: 12.011, group: 14, period: 2, block: "p", electronegativity: 2.55, oxidationStates: [4, 2, -4] },
117
+ { symbol: "N", name: "\u6C2E / Nitrogen", atomicNumber: 7, atomicMass: 14.007, group: 15, period: 2, block: "p", electronegativity: 3.04, oxidationStates: [5, 3, -3] },
118
+ { symbol: "O", name: "\u6C27 / Oxygen", atomicNumber: 8, atomicMass: 15.999, group: 16, period: 2, block: "p", electronegativity: 3.44, oxidationStates: [-2] },
119
+ { symbol: "F", name: "\u6C1F / Fluorine", atomicNumber: 9, atomicMass: 18.998, group: 17, period: 2, block: "p", electronegativity: 3.98, oxidationStates: [-1] },
120
+ { symbol: "Na", name: "\u94A0 / Sodium", atomicNumber: 11, atomicMass: 22.99, group: 1, period: 3, block: "s", electronegativity: 0.93, oxidationStates: [1] },
121
+ { symbol: "Mg", name: "\u9541 / Magnesium", atomicNumber: 12, atomicMass: 24.305, group: 2, period: 3, block: "s", electronegativity: 1.31, oxidationStates: [2] },
122
+ { symbol: "Al", name: "\u94DD / Aluminium", atomicNumber: 13, atomicMass: 26.982, group: 13, period: 3, block: "p", electronegativity: 1.61, oxidationStates: [3] },
123
+ { symbol: "Si", name: "\u7845 / Silicon", atomicNumber: 14, atomicMass: 28.085, group: 14, period: 3, block: "p", electronegativity: 1.9, oxidationStates: [4] },
124
+ { symbol: "P", name: "\u78F7 / Phosphorus", atomicNumber: 15, atomicMass: 30.974, group: 15, period: 3, block: "p", electronegativity: 2.19, oxidationStates: [5, 3, -3] },
125
+ { symbol: "S", name: "\u786B / Sulfur", atomicNumber: 16, atomicMass: 32.06, group: 16, period: 3, block: "p", electronegativity: 2.58, oxidationStates: [6, 4, -2] },
126
+ { symbol: "Cl", name: "\u6C2F / Chlorine", atomicNumber: 17, atomicMass: 35.45, group: 17, period: 3, block: "p", electronegativity: 3.16, oxidationStates: [7, -1] },
127
+ { symbol: "K", name: "\u94BE / Potassium", atomicNumber: 19, atomicMass: 39.098, group: 1, period: 4, block: "s", electronegativity: 0.82, oxidationStates: [1] },
128
+ { symbol: "Ca", name: "\u9499 / Calcium", atomicNumber: 20, atomicMass: 40.078, group: 2, period: 4, block: "s", electronegativity: 1, oxidationStates: [2] },
129
+ { symbol: "Fe", name: "\u94C1 / Iron", atomicNumber: 26, atomicMass: 55.845, group: 8, period: 4, block: "d", electronegativity: 1.83, oxidationStates: [3, 2] },
130
+ { symbol: "Cu", name: "\u94DC / Copper", atomicNumber: 29, atomicMass: 63.546, group: 11, period: 4, block: "d", electronegativity: 1.9, oxidationStates: [2, 1] },
131
+ { symbol: "Zn", name: "\u950C / Zinc", atomicNumber: 30, atomicMass: 65.38, group: 12, period: 4, block: "d", electronegativity: 1.65, oxidationStates: [2] },
132
+ { symbol: "Ag", name: "\u94F6 / Silver", atomicNumber: 47, atomicMass: 107.87, group: 11, period: 5, block: "d", electronegativity: 1.93, oxidationStates: [1] },
133
+ { symbol: "Au", name: "\u91D1 / Gold", atomicNumber: 79, atomicMass: 196.97, group: 11, period: 6, block: "d", electronegativity: 2.54, oxidationStates: [3, 1] }
134
+ ];
135
+ var elementMap = /* @__PURE__ */ new Map();
136
+ for (const el of CHEMICAL_ELEMENTS) {
137
+ elementMap.set(el.symbol.toLowerCase(), el);
138
+ }
139
+ var BuiltinKnowledgeBase = class {
140
+ id = "builtin-kb";
141
+ name = "Built-in Knowledge Base";
142
+ async query(params) {
143
+ const result = { symbols: [], formulas: [], elements: [] };
144
+ const types = params.types ?? ["element", "symbol"];
145
+ if (types.includes("element")) {
146
+ result.elements = CHEMICAL_ELEMENTS.filter(
147
+ (el) => el.symbol.toLowerCase().includes(params.query.toLowerCase()) || el.name.toLowerCase().includes(params.query.toLowerCase())
148
+ ).slice(0, params.maxResults ?? 10);
149
+ }
150
+ if (types.includes("symbol")) {
151
+ const matchedSymbols = BUILTIN_SYMBOLS.filter(
152
+ (s) => s.text.toLowerCase().includes(params.query.toLowerCase()) || s.label.toLowerCase().includes(params.query.toLowerCase())
153
+ ).slice(0, params.maxResults ?? 10);
154
+ result.symbols = matchedSymbols.map((s) => ({
155
+ symbol: s.text,
156
+ name: s.label,
157
+ description: s.description ?? "",
158
+ category: s.category
159
+ }));
160
+ }
161
+ return result;
162
+ }
163
+ async searchSymbols(query, limit = 10) {
164
+ return BUILTIN_SYMBOLS.filter((s) => s.text.includes(query) || s.label.includes(query)).slice(0, limit).map((s) => ({ symbol: s.text, name: s.label, description: s.description ?? "", category: s.category }));
165
+ }
166
+ async searchFormulas(query, limit = 10) {
167
+ return BUILTIN_SYMBOLS.filter((s) => s.category === "formula" && (s.text.includes(query) || s.label.includes(query))).slice(0, limit).map((s) => ({ id: s.text, name: s.label, latex: s.text, description: s.description ?? "", category: s.category }));
168
+ }
169
+ async getElementInfo(symbol) {
170
+ return elementMap.get(symbol.toLowerCase()) ?? null;
171
+ }
172
+ };
173
+ var BuiltinNormalizer = class {
174
+ id = "builtin-normalizer";
175
+ name = "Built-in Regex Normalizer";
176
+ async normalize(input, targetFormat) {
177
+ let normalized = input;
178
+ const changes = [];
179
+ const displayBlock = /\\\[([\s\S]*?)\\\]/.test(normalized);
180
+ if (displayBlock) {
181
+ const before = normalized;
182
+ normalized = normalized.replace(/\\\[([\s\S]*?)\\\]/g, (_match, content) => `$$${content}$$`);
183
+ if (normalized !== before) {
184
+ changes.push({ type: "delimiter-fix", from: before, to: normalized, reason: "Convert \\[...\\] to $$...$$" });
185
+ }
186
+ }
187
+ const inlineBlock = /\\\(([\s\S]*?)\\\)/.test(normalized);
188
+ if (inlineBlock) {
189
+ const before = normalized;
190
+ normalized = normalized.replace(/\\\(([\s\S]*?)\\\)/g, (_match, content) => `$${content}$`);
191
+ if (normalized !== before) {
192
+ changes.push({ type: "delimiter-fix", from: before, to: normalized, reason: "Convert \\(...\\) to $...$" });
193
+ }
194
+ }
195
+ const unicodeGreekMap = {
196
+ "\u03B1": "\\alpha",
197
+ "\u03B2": "\\beta",
198
+ "\u03B3": "\\gamma",
199
+ "\u03B4": "\\delta",
200
+ "\u03B5": "\\epsilon",
201
+ "\u03B6": "\\zeta",
202
+ "\u03B7": "\\eta",
203
+ "\u03B8": "\\theta",
204
+ "\u03BB": "\\lambda",
205
+ "\u03BC": "\\mu",
206
+ "\u03C0": "\\pi",
207
+ "\u03C3": "\\sigma",
208
+ "\u03C6": "\\phi",
209
+ "\u03C9": "\\omega"
210
+ };
211
+ for (const [unicode, latex] of Object.entries(unicodeGreekMap)) {
212
+ if (normalized.includes(unicode) && targetFormat !== "unicode") {
213
+ const before = normalized;
214
+ normalized = normalized.replace(new RegExp(unicode, "g"), latex);
215
+ changes.push({ type: "unicode-convert", from: before, to: normalized, reason: `Convert ${unicode} to ${latex}` });
216
+ }
217
+ }
218
+ const subscriptMap = {
219
+ "\u2080": "_0",
220
+ "\u2081": "_1",
221
+ "\u2082": "_2",
222
+ "\u2083": "_3",
223
+ "\u2084": "_4",
224
+ "\u2085": "_5",
225
+ "\u2086": "_6",
226
+ "\u2087": "_7",
227
+ "\u2088": "_8",
228
+ "\u2089": "_9"
229
+ };
230
+ for (const [unicode, latex] of Object.entries(subscriptMap)) {
231
+ if (normalized.includes(unicode) && targetFormat !== "unicode") {
232
+ normalized = normalized.replace(new RegExp(unicode, "g"), latex);
233
+ changes.push({ type: "unicode-convert", from: input, to: normalized, reason: `Convert subscript ${unicode}` });
234
+ }
235
+ }
236
+ const root = (0, import_core.createRoot)(import_core.ExpressionKind.Math, [(0, import_core.createText)(normalized)]);
237
+ return {
238
+ normalized,
239
+ original: input,
240
+ confidence: changes.length > 0 ? 0.8 : 1,
241
+ changes,
242
+ ast: root
243
+ };
244
+ }
245
+ async detectAndFix(input) {
246
+ return this.normalize(input, "auto");
247
+ }
248
+ };
249
+ var BuiltinFormatter = class {
250
+ id = "builtin-formatter";
251
+ name = "Built-in Rule Formatter";
252
+ async format(ast, _style) {
253
+ return ast;
254
+ }
255
+ };
256
+
257
+ // src/pipeline.ts
258
+ var AIPipeline = class {
259
+ completionProvider;
260
+ knowledgeBaseProvider;
261
+ normalizerProvider;
262
+ formatterProvider;
263
+ aiEnabled;
264
+ maxCompletions;
265
+ cache;
266
+ completionCache = /* @__PURE__ */ new Map();
267
+ kbCache = /* @__PURE__ */ new Map();
268
+ eventListeners = /* @__PURE__ */ new Map();
269
+ constructor(config = {}) {
270
+ this.completionProvider = config.completionProvider ?? new BuiltinCompletionProvider();
271
+ this.knowledgeBaseProvider = config.knowledgeBaseProvider ?? new BuiltinKnowledgeBase();
272
+ this.normalizerProvider = config.normalizerProvider ?? new BuiltinNormalizer();
273
+ this.formatterProvider = config.formatterProvider ?? new BuiltinFormatter();
274
+ this.aiEnabled = config.aiEnabled ?? true;
275
+ this.maxCompletions = config.maxCompletions ?? 20;
276
+ this.cache = config.cache ?? { enabled: true, ttlMs: 6e4, maxSize: 500 };
277
+ }
278
+ // ==========================================================================
279
+ // Public API
280
+ // ==========================================================================
281
+ /**
282
+ * 获取智能补全(本地 + AI 混合)
283
+ */
284
+ async getCompletions(context) {
285
+ const cacheKey = `comp:${context.currentInput}:${context.cursorPosition}`;
286
+ const cached = this.getCached(this.completionCache, cacheKey);
287
+ if (cached) return cached;
288
+ const localResult = await new BuiltinCompletionProvider().complete(context);
289
+ if (!this.aiEnabled || this.completionProvider.id === "builtin-completion") {
290
+ return localResult;
291
+ }
292
+ try {
293
+ const aiResult = await this.completionProvider.complete(context);
294
+ if (aiResult.items.length > 0) {
295
+ const seen = new Set(localResult.items.map((i) => i.text));
296
+ const aiOnly = aiResult.items.filter((i) => !seen.has(i.text));
297
+ const merged = {
298
+ items: [...localResult.items, ...aiOnly].slice(0, this.maxCompletions),
299
+ source: "hybrid",
300
+ latencyMs: aiResult.latencyMs
301
+ };
302
+ this.setCache(this.completionCache, cacheKey, merged);
303
+ this.emit("aiComplete", merged);
304
+ return merged;
305
+ }
306
+ } catch {
307
+ }
308
+ this.setCache(this.completionCache, cacheKey, localResult);
309
+ return localResult;
310
+ }
311
+ /**
312
+ * 查询知识库
313
+ */
314
+ async queryKnowledge(params) {
315
+ const cacheKey = `kb:${params.query}:${params.types?.join(",") ?? "all"}`;
316
+ const cached = this.getCached(this.kbCache, cacheKey);
317
+ if (cached) return cached;
318
+ try {
319
+ const result = await this.knowledgeBaseProvider.query(params);
320
+ this.setCache(this.kbCache, cacheKey, result);
321
+ return result;
322
+ } catch {
323
+ return new BuiltinKnowledgeBase().query(params);
324
+ }
325
+ }
326
+ /**
327
+ * 规范化 AI 输出
328
+ */
329
+ async normalize(input, format = "auto") {
330
+ try {
331
+ return await this.normalizerProvider.normalize(input, format);
332
+ } catch {
333
+ return new BuiltinNormalizer().normalize(input, format);
334
+ }
335
+ }
336
+ /**
337
+ * 格式化表达式
338
+ */
339
+ async format(ast, style) {
340
+ const fullStyle = {
341
+ outputFormat: "latex",
342
+ domain: "general",
343
+ ...style
344
+ };
345
+ try {
346
+ return await this.formatterProvider.format(ast, fullStyle);
347
+ } catch {
348
+ return new BuiltinFormatter().format(ast, fullStyle);
349
+ }
350
+ }
351
+ /**
352
+ * 自然语言 → 公式
353
+ */
354
+ async naturalLanguageToFormula(query) {
355
+ if (!this.aiEnabled) return [];
356
+ if (this.completionProvider.naturalLanguageToFormula) {
357
+ try {
358
+ return await this.completionProvider.naturalLanguageToFormula(query);
359
+ } catch {
360
+ return [];
361
+ }
362
+ }
363
+ return [];
364
+ }
365
+ /**
366
+ * AI 纠错
367
+ */
368
+ async correct(input, error) {
369
+ if (!this.aiEnabled) return [];
370
+ if (this.completionProvider.correct) {
371
+ try {
372
+ return await this.completionProvider.correct(input, error);
373
+ } catch {
374
+ return [];
375
+ }
376
+ }
377
+ return [];
378
+ }
379
+ // ==========================================================================
380
+ // Configuration
381
+ // ==========================================================================
382
+ setEnabled(enabled) {
383
+ this.aiEnabled = enabled;
384
+ }
385
+ isEnabled() {
386
+ return this.aiEnabled;
387
+ }
388
+ getCacheStats() {
389
+ return {
390
+ hits: 0,
391
+ // Simplified — would need tracking
392
+ misses: 0,
393
+ completionSize: this.completionCache.size,
394
+ kbSize: this.kbCache.size
395
+ };
396
+ }
397
+ clearCache() {
398
+ this.completionCache.clear();
399
+ this.kbCache.clear();
400
+ }
401
+ // ==========================================================================
402
+ // Event System
403
+ // ==========================================================================
404
+ on(event, callback) {
405
+ if (!this.eventListeners.has(event)) {
406
+ this.eventListeners.set(event, /* @__PURE__ */ new Set());
407
+ }
408
+ this.eventListeners.get(event).add(callback);
409
+ }
410
+ off(event, callback) {
411
+ this.eventListeners.get(event)?.delete(callback);
412
+ }
413
+ emit(event, ...args) {
414
+ this.eventListeners.get(event)?.forEach((cb) => cb(...args));
415
+ }
416
+ // ==========================================================================
417
+ // Private
418
+ // ==========================================================================
419
+ getCached(cache, key) {
420
+ if (!this.cache.enabled) return null;
421
+ const entry = cache.get(key);
422
+ if (!entry) return null;
423
+ if (Date.now() - entry.timestamp > this.cache.ttlMs) {
424
+ cache.delete(key);
425
+ return null;
426
+ }
427
+ return entry.value;
428
+ }
429
+ setCache(cache, key, value) {
430
+ if (!this.cache.enabled) return;
431
+ if (cache.size >= this.cache.maxSize) {
432
+ const firstKey = cache.keys().next().value;
433
+ if (firstKey !== void 0) cache.delete(firstKey);
434
+ }
435
+ cache.set(key, { value, timestamp: Date.now() });
436
+ }
437
+ };
438
+
439
+ // src/prompts.ts
440
+ var BUILTIN_PROMPTS = {
441
+ /** 公式自动补全 */
442
+ completion: {
443
+ name: "Formula Completion",
444
+ system: `You are a scientific expression completion assistant.
445
+ Given a partially typed LaTeX expression, suggest completions.
446
+ Return ONLY a JSON array of {text, label, description, category, score}.
447
+ Categories: symbol, command, template, correction, formula.`,
448
+ user: `Complete the following expression at cursor position {{cursor}}:
449
+ Expression: {{input}}
450
+ Expression type: {{type}}
451
+ Surrounding context: {{context}}`,
452
+ variables: ["input", "cursor", "type", "context"]
453
+ },
454
+ /** AI 输出规范化 */
455
+ normalization: {
456
+ name: "Formula Normalization",
457
+ system: `You are a LaTeX/math notation normalization expert.
458
+ Standardize scientific expressions to consistent format.
459
+ Rules:
460
+ - Use $$...$$ for display math, $...$ for inline
461
+ - Chemical formulas must use \\ce{...}
462
+ - Greek letters must use LaTeX commands (\\alpha not \u03B1)
463
+ Return JSON: {normalized, changes: [{type, from, to, reason}], confidence}.`,
464
+ user: `Normalize the following expression to standard {{targetFormat}} format:
465
+ {{input}}`,
466
+ variables: ["input", "targetFormat"]
467
+ },
468
+ /** 自然语言 → 公式 */
469
+ "nl-to-formula": {
470
+ name: "Natural Language to Formula",
471
+ system: `Convert natural language descriptions to LaTeX scientific expressions.
472
+ Return JSON: [{text: latex_code, label: display_name, description, category: "formula", score: 1}].`,
473
+ user: `Convert to {{type}} expression: {{query}}`,
474
+ variables: ["query", "type"]
475
+ },
476
+ /** 化学方程式验证 */
477
+ "chemistry-validation": {
478
+ name: "Chemistry Validation",
479
+ system: `You are a chemistry expert. Validate chemical equations.
480
+ Check: element conservation, charge balance, physical possibility.
481
+ Return JSON: {valid: boolean, errors: [{message, code}], warnings: [{message, code}]}.`,
482
+ user: `Validate this chemical equation:
483
+ Reactants: {{reactants}}
484
+ Products: {{products}}
485
+ Arrow: {{arrow}}
486
+ Conditions: {{conditions}}`,
487
+ variables: ["reactants", "products", "arrow", "conditions"]
488
+ }
489
+ };
490
+ var PromptRegistry = class {
491
+ templates;
492
+ constructor() {
493
+ this.templates = new Map(Object.entries(BUILTIN_PROMPTS));
494
+ }
495
+ /**
496
+ * 注册自定义 Prompt 模板(可覆盖内置)
497
+ */
498
+ register(template) {
499
+ this.templates.set(template.name, template);
500
+ }
501
+ /**
502
+ * 获取模板
503
+ */
504
+ get(name) {
505
+ return this.templates.get(name);
506
+ }
507
+ /**
508
+ * 渲染模板为 system + user 消息
509
+ */
510
+ render(name, variables) {
511
+ const template = this.templates.get(name);
512
+ if (!template) return null;
513
+ let system = template.system;
514
+ let user = template.user;
515
+ for (const [key, value] of Object.entries(variables)) {
516
+ const placeholder = `{{${key}}}`;
517
+ system = system.replaceAll(placeholder, value);
518
+ user = user.replaceAll(placeholder, value);
519
+ }
520
+ return { system, user };
521
+ }
522
+ /**
523
+ * 获取所有已注册模板名称
524
+ */
525
+ list() {
526
+ return Array.from(this.templates.keys());
527
+ }
528
+ /**
529
+ * 移除自定义模板(不能移除内置模板)
530
+ */
531
+ remove(name) {
532
+ if (name in BUILTIN_PROMPTS) return false;
533
+ return this.templates.delete(name);
534
+ }
535
+ };
536
+ // Annotate the CommonJS export names for ESM import in node:
537
+ 0 && (module.exports = {
538
+ AIPipeline,
539
+ BUILTIN_PROMPTS,
540
+ BuiltinCompletionProvider,
541
+ BuiltinFormatter,
542
+ BuiltinKnowledgeBase,
543
+ BuiltinNormalizer,
544
+ PromptRegistry
545
+ });