llm_meta_widget 0.6.1 → 0.7.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.
- checksums.yaml +4 -4
- data/README.md +64 -2
- data/app/assets/javascripts/llm_meta_widget/config.js +79 -0
- data/app/assets/javascripts/llm_meta_widget/element.js +1133 -0
- data/app/assets/javascripts/llm_meta_widget/llm-meta-widget.js +4429 -0
- data/app/assets/javascripts/llm_meta_widget/orchestrator.js +3 -3
- data/app/assets/stylesheets/llm_meta_widget/panel.css +363 -0
- data/app/controllers/llm_meta_widget/assets_controller.rb +11 -0
- data/app/helpers/llm_meta_widget/widget_helper.rb +34 -5
- data/app/views/llm_meta_widget/_chat_panel.html.erb +39 -1466
- data/config/routes.rb +8 -0
- data/lib/llm_meta_widget/version.rb +1 -1
- metadata +5 -1
|
@@ -0,0 +1,4429 @@
|
|
|
1
|
+
// GENERATED by `npm run build` from element.js + config.js + orchestrator.js + marked.esm.js + conversation.css + panel.css.
|
|
2
|
+
// Do not edit this file; edit the sources and rebuild. `npm test` fails if it drifts.
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
// app/assets/javascripts/llm_meta_widget/orchestrator.js
|
|
6
|
+
async function singleLlmCall({
|
|
7
|
+
baseUrl,
|
|
8
|
+
apiKeyUuid,
|
|
9
|
+
modelName,
|
|
10
|
+
messages,
|
|
11
|
+
toolIds = [],
|
|
12
|
+
localTools = [],
|
|
13
|
+
generationSettings = {},
|
|
14
|
+
bearerToken,
|
|
15
|
+
onTextDelta,
|
|
16
|
+
onThinkingDelta,
|
|
17
|
+
onToolCall,
|
|
18
|
+
onPhase,
|
|
19
|
+
signal
|
|
20
|
+
}) {
|
|
21
|
+
if (!baseUrl || !apiKeyUuid || !modelName) {
|
|
22
|
+
throw new Error("singleLlmCall: baseUrl, apiKeyUuid, modelName are required");
|
|
23
|
+
}
|
|
24
|
+
if (!Array.isArray(messages) || messages.length === 0) {
|
|
25
|
+
throw new Error("singleLlmCall: messages must be a non-empty array");
|
|
26
|
+
}
|
|
27
|
+
const url = `${baseUrl.replace(/\/$/, "")}/api/llm_api_keys/${encodeURIComponent(
|
|
28
|
+
apiKeyUuid
|
|
29
|
+
)}/models/${encodeURIComponent(modelName)}/single_llm_calls`;
|
|
30
|
+
const headers = { "Content-Type": "application/json", Accept: "text/event-stream" };
|
|
31
|
+
if (bearerToken) headers["Authorization"] = `Bearer ${bearerToken}`;
|
|
32
|
+
const body = JSON.stringify({
|
|
33
|
+
messages,
|
|
34
|
+
tool_ids: toolIds,
|
|
35
|
+
// Convert camelCase → snake_case at the wire boundary; the server's
|
|
36
|
+
// strong-params permits `input_schema:` and rejects `inputSchema:`.
|
|
37
|
+
local_tools: (localTools || []).map((t) => ({
|
|
38
|
+
name: t.name,
|
|
39
|
+
description: t.description,
|
|
40
|
+
input_schema: t.input_schema || t.inputSchema
|
|
41
|
+
})),
|
|
42
|
+
generation_settings: generationSettings
|
|
43
|
+
});
|
|
44
|
+
const response = await fetch(url, { method: "POST", headers, body, signal });
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
const text = await response.text().catch(() => "");
|
|
47
|
+
throw new Error(`singleLlmCall: HTTP ${response.status} ${response.statusText}${text ? " \u2014 " + text.slice(0, 200) : ""}`);
|
|
48
|
+
}
|
|
49
|
+
if (!response.body) {
|
|
50
|
+
throw new Error("singleLlmCall: response has no body (streaming unsupported?)");
|
|
51
|
+
}
|
|
52
|
+
const toolCalls = [];
|
|
53
|
+
let content = "";
|
|
54
|
+
let finishReason = null;
|
|
55
|
+
for await (const event of parseSseStream(response.body, signal)) {
|
|
56
|
+
switch (event.name) {
|
|
57
|
+
case "text_delta": {
|
|
58
|
+
const delta = event.data?.delta;
|
|
59
|
+
if (typeof delta === "string" && delta.length > 0) {
|
|
60
|
+
content += delta;
|
|
61
|
+
onTextDelta?.(delta);
|
|
62
|
+
}
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
case "thinking_delta": {
|
|
66
|
+
const delta = event.data?.delta;
|
|
67
|
+
if (typeof delta === "string" && delta.length > 0) {
|
|
68
|
+
onThinkingDelta?.(delta);
|
|
69
|
+
}
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
case "tool_call": {
|
|
73
|
+
const tc = event.data?.tool_call;
|
|
74
|
+
if (tc && tc.name) {
|
|
75
|
+
toolCalls.push(tc);
|
|
76
|
+
onToolCall?.(tc);
|
|
77
|
+
}
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
case "phase": {
|
|
81
|
+
const name = event.data?.name;
|
|
82
|
+
if (typeof name === "string") onPhase?.(name);
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
case "done": {
|
|
86
|
+
if (typeof event.data?.content === "string") content = event.data.content;
|
|
87
|
+
if (event.data?.finish_reason != null) finishReason = event.data.finish_reason;
|
|
88
|
+
return { content, finishReason, toolCalls };
|
|
89
|
+
}
|
|
90
|
+
case "error": {
|
|
91
|
+
const code = event.data?.code || "server_error";
|
|
92
|
+
const message = event.data?.message || "meta-server emitted an error event";
|
|
93
|
+
const err = new Error(`singleLlmCall: ${code}: ${message}`);
|
|
94
|
+
err.code = code;
|
|
95
|
+
throw err;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
throw new Error("singleLlmCall: stream closed without done event");
|
|
100
|
+
}
|
|
101
|
+
async function* parseSseStream(readableStream, signal) {
|
|
102
|
+
const reader = readableStream.getReader();
|
|
103
|
+
const decoder = new TextDecoder("utf-8");
|
|
104
|
+
let buffer = "";
|
|
105
|
+
const onAbort = () => {
|
|
106
|
+
try {
|
|
107
|
+
reader.cancel();
|
|
108
|
+
} catch {
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
signal?.addEventListener("abort", onAbort);
|
|
112
|
+
try {
|
|
113
|
+
while (true) {
|
|
114
|
+
const { value, done } = await reader.read();
|
|
115
|
+
if (done) break;
|
|
116
|
+
buffer += decoder.decode(value, { stream: true });
|
|
117
|
+
let sepIdx;
|
|
118
|
+
while ((sepIdx = indexOfEither(buffer, "\n\n", "\r\n\r\n")) !== -1) {
|
|
119
|
+
const rawFrame = buffer.slice(0, sepIdx);
|
|
120
|
+
buffer = buffer.slice(
|
|
121
|
+
sepIdx + (buffer.slice(sepIdx, sepIdx + 4) === "\r\n\r\n" ? 4 : 2)
|
|
122
|
+
);
|
|
123
|
+
const parsed = parseSseFrame(rawFrame);
|
|
124
|
+
if (parsed) yield parsed;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (buffer.trim().length > 0) {
|
|
128
|
+
const parsed = parseSseFrame(buffer);
|
|
129
|
+
if (parsed) yield parsed;
|
|
130
|
+
}
|
|
131
|
+
} finally {
|
|
132
|
+
signal?.removeEventListener("abort", onAbort);
|
|
133
|
+
try {
|
|
134
|
+
reader.releaseLock();
|
|
135
|
+
} catch {
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function indexOfEither(str, a, b) {
|
|
140
|
+
const ai = str.indexOf(a);
|
|
141
|
+
const bi = str.indexOf(b);
|
|
142
|
+
if (ai === -1) return bi;
|
|
143
|
+
if (bi === -1) return ai;
|
|
144
|
+
return Math.min(ai, bi);
|
|
145
|
+
}
|
|
146
|
+
async function dispatchLocalToolCalls(toolCalls, aiActions) {
|
|
147
|
+
const dispatched = [];
|
|
148
|
+
const skipped = [];
|
|
149
|
+
for (const tc of toolCalls || []) {
|
|
150
|
+
const handler = aiActions?.[tc.name];
|
|
151
|
+
if (typeof handler !== "function") {
|
|
152
|
+
skipped.push(tc);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const args = coerceArguments(tc.arguments);
|
|
156
|
+
try {
|
|
157
|
+
const value = await handler(args);
|
|
158
|
+
dispatched.push({ toolCall: tc, value });
|
|
159
|
+
} catch (error) {
|
|
160
|
+
dispatched.push({ toolCall: tc, error });
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return { dispatched, skipped };
|
|
164
|
+
}
|
|
165
|
+
async function runChatLoop(opts) {
|
|
166
|
+
const {
|
|
167
|
+
aiActions = {},
|
|
168
|
+
remoteTools = [],
|
|
169
|
+
hostWideTools = [],
|
|
170
|
+
maxRounds = 10,
|
|
171
|
+
// "hub" (default) talks to llm_meta_server; "ollama" talks straight to an
|
|
172
|
+
// Ollama, with no server component of ours in between. The loop is the
|
|
173
|
+
// same either way — only the call is swapped.
|
|
174
|
+
provider = "hub",
|
|
175
|
+
// Where Class 1 (hub-registered) tools are proxied. Usually the same
|
|
176
|
+
// llm_meta_server that answers the chat, but not when the chat goes
|
|
177
|
+
// straight to an Ollama — the tools still belong to the hub.
|
|
178
|
+
toolHubUrl,
|
|
179
|
+
signal,
|
|
180
|
+
onRoundStart,
|
|
181
|
+
onTextDelta,
|
|
182
|
+
onThinkingDelta,
|
|
183
|
+
onToolCall,
|
|
184
|
+
// Fired as each tool finishes, so a caller can show what ran WHILE the
|
|
185
|
+
// turn is still going. The returned `dispatched` array says the same
|
|
186
|
+
// thing, but only once the whole loop ends — which on a slow model is
|
|
187
|
+
// minutes after the work happened, with the user watching unnamed tools.
|
|
188
|
+
onToolDispatched,
|
|
189
|
+
onPhase,
|
|
190
|
+
...singleOpts
|
|
191
|
+
} = opts;
|
|
192
|
+
const messages = [...singleOpts.messages || []];
|
|
193
|
+
const remoteByName = Object.fromEntries((remoteTools || []).map((t) => [t.name, t]));
|
|
194
|
+
const hostWideByName = Object.fromEntries((hostWideTools || []).map((t) => [t.name, t]));
|
|
195
|
+
const toolIds = [...singleOpts.toolIds || [], ...(remoteTools || []).map((t) => t.id)];
|
|
196
|
+
const inlineByName = {};
|
|
197
|
+
for (const t of singleOpts.localTools || []) inlineByName[t.name] = t;
|
|
198
|
+
for (const t of hostWideTools || []) {
|
|
199
|
+
if (!inlineByName[t.name]) inlineByName[t.name] = { name: t.name, description: t.description, input_schema: t.input_schema };
|
|
200
|
+
}
|
|
201
|
+
const mergedLocalTools = Object.values(inlineByName);
|
|
202
|
+
const rounds = [];
|
|
203
|
+
const allDispatched = [];
|
|
204
|
+
const allSkipped = [];
|
|
205
|
+
let lastResult = null;
|
|
206
|
+
const seenToolCalls = /* @__PURE__ */ new Set();
|
|
207
|
+
const toolKey = (tc) => {
|
|
208
|
+
let args;
|
|
209
|
+
try {
|
|
210
|
+
args = coerceArguments(tc.arguments);
|
|
211
|
+
} catch {
|
|
212
|
+
args = tc.arguments;
|
|
213
|
+
}
|
|
214
|
+
let serialized;
|
|
215
|
+
try {
|
|
216
|
+
if (args && typeof args === "object" && !Array.isArray(args)) {
|
|
217
|
+
const sorted = Object.keys(args).sort().reduce((o, k) => {
|
|
218
|
+
o[k] = args[k];
|
|
219
|
+
return o;
|
|
220
|
+
}, {});
|
|
221
|
+
serialized = JSON.stringify(sorted);
|
|
222
|
+
} else {
|
|
223
|
+
serialized = JSON.stringify(args);
|
|
224
|
+
}
|
|
225
|
+
} catch {
|
|
226
|
+
serialized = String(args);
|
|
227
|
+
}
|
|
228
|
+
return tc.name + "" + serialized;
|
|
229
|
+
};
|
|
230
|
+
for (let round = 0; round < maxRounds; round++) {
|
|
231
|
+
if (signal?.aborted) throw new DOMException("aborted", "AbortError");
|
|
232
|
+
onRoundStart?.(round);
|
|
233
|
+
const call = provider === "ollama" ? ollamaChatCall : singleLlmCall;
|
|
234
|
+
const turnResult = await call({
|
|
235
|
+
...singleOpts,
|
|
236
|
+
messages,
|
|
237
|
+
toolIds,
|
|
238
|
+
localTools: mergedLocalTools,
|
|
239
|
+
signal,
|
|
240
|
+
onTextDelta: onTextDelta ? (d) => onTextDelta(d, round) : void 0,
|
|
241
|
+
onThinkingDelta: onThinkingDelta ? (d) => onThinkingDelta(d, round) : void 0,
|
|
242
|
+
onToolCall: onToolCall ? (t) => onToolCall(t, round) : void 0,
|
|
243
|
+
onPhase: onPhase ? (n) => onPhase(n, round) : void 0
|
|
244
|
+
});
|
|
245
|
+
lastResult = turnResult;
|
|
246
|
+
const localCalls = [];
|
|
247
|
+
const hostWideCalls = [];
|
|
248
|
+
const remoteCalls = [];
|
|
249
|
+
const unknownCalls = [];
|
|
250
|
+
const duplicateCalls = [];
|
|
251
|
+
const emittedCount = (turnResult.toolCalls || []).length;
|
|
252
|
+
for (const tc of turnResult.toolCalls || []) {
|
|
253
|
+
const key = toolKey(tc);
|
|
254
|
+
if (seenToolCalls.has(key)) {
|
|
255
|
+
duplicateCalls.push(tc);
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
seenToolCalls.add(key);
|
|
259
|
+
if (typeof aiActions[tc.name] === "function") localCalls.push(tc);
|
|
260
|
+
else if (hostWideByName[tc.name]) hostWideCalls.push(tc);
|
|
261
|
+
else if (remoteByName[tc.name]) remoteCalls.push(tc);
|
|
262
|
+
else unknownCalls.push(tc);
|
|
263
|
+
}
|
|
264
|
+
const allDuplicates = emittedCount > 0 && duplicateCalls.length === emittedCount;
|
|
265
|
+
if (allDuplicates) {
|
|
266
|
+
rounds.push({
|
|
267
|
+
round,
|
|
268
|
+
content: turnResult.content,
|
|
269
|
+
finishReason: turnResult.finishReason,
|
|
270
|
+
localCalls: [],
|
|
271
|
+
hostWideCalls: [],
|
|
272
|
+
remoteCalls: [],
|
|
273
|
+
unknownCalls: [],
|
|
274
|
+
duplicateCalls,
|
|
275
|
+
toolCalls: turnResult.toolCalls
|
|
276
|
+
});
|
|
277
|
+
return {
|
|
278
|
+
content: turnResult.content,
|
|
279
|
+
finishReason: turnResult.finishReason,
|
|
280
|
+
rounds,
|
|
281
|
+
dispatched: allDispatched,
|
|
282
|
+
skipped: allSkipped,
|
|
283
|
+
stopped_reason: "duplicate_tool_calls"
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
const localOut = await dispatchLocalToolCalls(localCalls, aiActions);
|
|
287
|
+
allDispatched.push(...localOut.dispatched);
|
|
288
|
+
const roundTripResults = [];
|
|
289
|
+
for (const { toolCall, value, error } of localOut.dispatched) {
|
|
290
|
+
onToolDispatched?.({ toolCall, value, error });
|
|
291
|
+
roundTripResults.push({
|
|
292
|
+
tc: toolCall,
|
|
293
|
+
result: error ? { error: String(error.message || error) } : { ok: true, applied: toolCall.name }
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
for (const tc of hostWideCalls) {
|
|
297
|
+
const tool = hostWideByName[tc.name];
|
|
298
|
+
const args = coerceArguments(tc.arguments);
|
|
299
|
+
try {
|
|
300
|
+
const value = await callMcpTool({ endpoint: tool.endpoint, name: tc.name, args, signal });
|
|
301
|
+
allDispatched.push({ toolCall: tc, value });
|
|
302
|
+
onToolDispatched?.({ toolCall: tc, value });
|
|
303
|
+
roundTripResults.push({ tc, result: value });
|
|
304
|
+
} catch (error) {
|
|
305
|
+
allDispatched.push({ toolCall: tc, error });
|
|
306
|
+
onToolDispatched?.({ toolCall: tc, error });
|
|
307
|
+
roundTripResults.push({ tc, result: { error: String(error.message || error) } });
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
for (const tc of remoteCalls) {
|
|
311
|
+
const tool = remoteByName[tc.name];
|
|
312
|
+
const args = coerceArguments(tc.arguments);
|
|
313
|
+
try {
|
|
314
|
+
const value = await dispatchRemoteToolCall({
|
|
315
|
+
baseUrl: toolHubUrl || singleOpts.baseUrl,
|
|
316
|
+
bearerToken: singleOpts.bearerToken,
|
|
317
|
+
toolId: tool.id,
|
|
318
|
+
args,
|
|
319
|
+
signal
|
|
320
|
+
});
|
|
321
|
+
allDispatched.push({ toolCall: tc, value });
|
|
322
|
+
onToolDispatched?.({ toolCall: tc, value });
|
|
323
|
+
roundTripResults.push({ tc, result: value });
|
|
324
|
+
} catch (error) {
|
|
325
|
+
allDispatched.push({ toolCall: tc, error });
|
|
326
|
+
onToolDispatched?.({ toolCall: tc, error });
|
|
327
|
+
roundTripResults.push({ tc, result: { error: String(error.message || error) } });
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
allSkipped.push(...unknownCalls);
|
|
331
|
+
rounds.push({
|
|
332
|
+
round,
|
|
333
|
+
content: turnResult.content,
|
|
334
|
+
finishReason: turnResult.finishReason,
|
|
335
|
+
localCalls,
|
|
336
|
+
hostWideCalls,
|
|
337
|
+
remoteCalls,
|
|
338
|
+
unknownCalls,
|
|
339
|
+
toolCalls: turnResult.toolCalls
|
|
340
|
+
});
|
|
341
|
+
if (roundTripResults.length === 0) {
|
|
342
|
+
return {
|
|
343
|
+
content: turnResult.content,
|
|
344
|
+
finishReason: turnResult.finishReason,
|
|
345
|
+
rounds,
|
|
346
|
+
dispatched: allDispatched,
|
|
347
|
+
skipped: allSkipped
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
messages.push({
|
|
351
|
+
role: "assistant",
|
|
352
|
+
content: turnResult.content || "",
|
|
353
|
+
tool_calls: turnResult.toolCalls
|
|
354
|
+
});
|
|
355
|
+
for (const { tc, result } of roundTripResults) {
|
|
356
|
+
messages.push({
|
|
357
|
+
role: "tool",
|
|
358
|
+
tool_call_id: tc.id || "",
|
|
359
|
+
name: tc.name,
|
|
360
|
+
content: typeof result === "string" ? result : JSON.stringify(result)
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return {
|
|
365
|
+
content: lastResult?.content || "",
|
|
366
|
+
finishReason: lastResult?.finishReason || null,
|
|
367
|
+
rounds,
|
|
368
|
+
dispatched: allDispatched,
|
|
369
|
+
skipped: allSkipped,
|
|
370
|
+
stopped_reason: `max_rounds (${maxRounds}) exceeded`
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
async function fetchMcpManifest(manifestUrl) {
|
|
374
|
+
let manifest;
|
|
375
|
+
try {
|
|
376
|
+
const response = await fetch(manifestUrl);
|
|
377
|
+
if (!response.ok) return [];
|
|
378
|
+
manifest = await response.json();
|
|
379
|
+
} catch {
|
|
380
|
+
return [];
|
|
381
|
+
}
|
|
382
|
+
const base = new URL(manifestUrl);
|
|
383
|
+
const out = [];
|
|
384
|
+
for (const server of manifest?.servers || []) {
|
|
385
|
+
let endpoint;
|
|
386
|
+
try {
|
|
387
|
+
endpoint = new URL(server.url, base).toString();
|
|
388
|
+
} catch {
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
for (const tool of server.tools || []) {
|
|
392
|
+
if (!tool.name) continue;
|
|
393
|
+
out.push({
|
|
394
|
+
name: tool.name,
|
|
395
|
+
description: tool.description || "",
|
|
396
|
+
input_schema: tool.input_schema || tool.inputSchema || { type: "object", properties: {} },
|
|
397
|
+
endpoint,
|
|
398
|
+
serverName: server.name || null
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return out;
|
|
403
|
+
}
|
|
404
|
+
var _mcpReqId = 0;
|
|
405
|
+
async function mcpRpc({ endpoint, method, params, signal, label }) {
|
|
406
|
+
const what = label || method;
|
|
407
|
+
const response = await fetch(endpoint, {
|
|
408
|
+
method: "POST",
|
|
409
|
+
headers: {
|
|
410
|
+
"Content-Type": "application/json",
|
|
411
|
+
"Accept": "application/json, text/event-stream"
|
|
412
|
+
},
|
|
413
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: ++_mcpReqId, method, params: params || {} }),
|
|
414
|
+
signal,
|
|
415
|
+
// Send session cookies for same-origin MCP endpoints. Cross-origin CORS
|
|
416
|
+
// with credentials requires the server to echo Access-Control-Allow-
|
|
417
|
+
// Credentials: true — which most MCP servers won't. This is fine: for
|
|
418
|
+
// cross-origin the widget doesn't send cookies; the server enforces
|
|
419
|
+
// its own auth (API key in header, etc.) if it wants any.
|
|
420
|
+
credentials: "same-origin"
|
|
421
|
+
});
|
|
422
|
+
if (!response.ok) {
|
|
423
|
+
const text = await response.text().catch(() => "");
|
|
424
|
+
throw new Error(`${what}: HTTP ${response.status}${text ? " \u2014 " + text.slice(0, 200) : ""}`);
|
|
425
|
+
}
|
|
426
|
+
const contentType = response.headers.get("content-type") || "";
|
|
427
|
+
if (contentType.includes("application/json")) {
|
|
428
|
+
const body = await response.json();
|
|
429
|
+
if (body?.error) throw new Error(`${what}: ${body.error.message || "JSON-RPC error"}`);
|
|
430
|
+
return body?.result;
|
|
431
|
+
}
|
|
432
|
+
if (contentType.includes("text/event-stream")) {
|
|
433
|
+
for await (const evt of parseSseStream(response.body, signal)) {
|
|
434
|
+
const payload = evt.data;
|
|
435
|
+
if (!payload || typeof payload !== "object") continue;
|
|
436
|
+
if (payload.error) throw new Error(`${what}: ${payload.error.message || "JSON-RPC error"}`);
|
|
437
|
+
if ("result" in payload) return payload.result;
|
|
438
|
+
}
|
|
439
|
+
throw new Error(`${what}: SSE stream ended without result`);
|
|
440
|
+
}
|
|
441
|
+
throw new Error(`${what}: unexpected content-type ${contentType}`);
|
|
442
|
+
}
|
|
443
|
+
async function callMcpTool({ endpoint, name, args, signal }) {
|
|
444
|
+
return mcpRpc({
|
|
445
|
+
endpoint,
|
|
446
|
+
signal,
|
|
447
|
+
method: "tools/call",
|
|
448
|
+
params: { name, arguments: args || {} },
|
|
449
|
+
label: `callMcpTool(${name})`
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
async function listMcpPrompts({ endpoint, signal }) {
|
|
453
|
+
try {
|
|
454
|
+
const result = await mcpRpc({ endpoint, method: "prompts/list", signal });
|
|
455
|
+
return (result?.prompts || []).map((p) => ({ ...p, endpoint }));
|
|
456
|
+
} catch {
|
|
457
|
+
return [];
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
async function getMcpPrompt({ endpoint, name, args, signal }) {
|
|
461
|
+
return mcpRpc({
|
|
462
|
+
endpoint,
|
|
463
|
+
signal,
|
|
464
|
+
method: "prompts/get",
|
|
465
|
+
params: { name, arguments: args || {} },
|
|
466
|
+
label: `getMcpPrompt(${name})`
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
async function listMcpResources({ endpoint, signal }) {
|
|
470
|
+
try {
|
|
471
|
+
const result = await mcpRpc({ endpoint, method: "resources/list", signal });
|
|
472
|
+
return (result?.resources || []).map((r) => ({ ...r, endpoint }));
|
|
473
|
+
} catch {
|
|
474
|
+
return [];
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
async function readMcpResource({ endpoint, uri, signal }) {
|
|
478
|
+
return mcpRpc({
|
|
479
|
+
endpoint,
|
|
480
|
+
signal,
|
|
481
|
+
method: "resources/read",
|
|
482
|
+
params: { uri },
|
|
483
|
+
label: `readMcpResource(${uri})`
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
function promptMessagesToText(result) {
|
|
487
|
+
return (result?.messages || []).map((m) => {
|
|
488
|
+
const c = m.content;
|
|
489
|
+
if (typeof c === "string") return c;
|
|
490
|
+
if (Array.isArray(c)) return c.filter((b) => b?.type === "text").map((b) => b.text).join("\n");
|
|
491
|
+
if (c && c.type === "text") return c.text;
|
|
492
|
+
return "";
|
|
493
|
+
}).filter(Boolean).join("\n\n");
|
|
494
|
+
}
|
|
495
|
+
async function dispatchRemoteToolCall({ baseUrl, bearerToken, toolId, args, signal }) {
|
|
496
|
+
if (!baseUrl || toolId == null) {
|
|
497
|
+
throw new Error("dispatchRemoteToolCall: baseUrl and toolId are required");
|
|
498
|
+
}
|
|
499
|
+
const url = `${baseUrl.replace(/\/$/, "")}/api/mcp_tools/${encodeURIComponent(toolId)}/call`;
|
|
500
|
+
const headers = { "Content-Type": "application/json" };
|
|
501
|
+
if (bearerToken) headers["Authorization"] = `Bearer ${bearerToken}`;
|
|
502
|
+
const response = await fetch(url, {
|
|
503
|
+
method: "POST",
|
|
504
|
+
headers,
|
|
505
|
+
body: JSON.stringify({ arguments: args || {} }),
|
|
506
|
+
signal
|
|
507
|
+
});
|
|
508
|
+
if (!response.ok) {
|
|
509
|
+
const text = await response.text().catch(() => "");
|
|
510
|
+
throw new Error(`dispatchRemoteToolCall: HTTP ${response.status} ${response.statusText}${text ? " \u2014 " + text.slice(0, 200) : ""}`);
|
|
511
|
+
}
|
|
512
|
+
const body = await response.json();
|
|
513
|
+
return body.result;
|
|
514
|
+
}
|
|
515
|
+
function coerceArguments(raw) {
|
|
516
|
+
if (raw == null) return {};
|
|
517
|
+
if (typeof raw === "object") return raw;
|
|
518
|
+
if (typeof raw === "string") {
|
|
519
|
+
try {
|
|
520
|
+
return JSON.parse(raw);
|
|
521
|
+
} catch {
|
|
522
|
+
return { _raw: raw };
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return { _raw: raw };
|
|
526
|
+
}
|
|
527
|
+
function parseSseFrame(raw) {
|
|
528
|
+
let name = "message";
|
|
529
|
+
const dataLines = [];
|
|
530
|
+
for (const line of raw.split("\n")) {
|
|
531
|
+
if (line.length === 0 || line.startsWith(":")) continue;
|
|
532
|
+
const colon = line.indexOf(":");
|
|
533
|
+
const field = colon === -1 ? line : line.slice(0, colon);
|
|
534
|
+
let value = colon === -1 ? "" : line.slice(colon + 1);
|
|
535
|
+
if (value.startsWith(" ")) value = value.slice(1);
|
|
536
|
+
if (field === "event") name = value;
|
|
537
|
+
else if (field === "data") dataLines.push(value);
|
|
538
|
+
}
|
|
539
|
+
if (dataLines.length === 0 && name === "message") return null;
|
|
540
|
+
const dataStr = dataLines.join("\n");
|
|
541
|
+
let data = null;
|
|
542
|
+
if (dataStr.length > 0) {
|
|
543
|
+
try {
|
|
544
|
+
data = JSON.parse(dataStr);
|
|
545
|
+
} catch (e) {
|
|
546
|
+
data = { _raw: dataStr, _parseError: e.message };
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
return { name, data };
|
|
550
|
+
}
|
|
551
|
+
var STATIC_PRIMITIVES_META = "io.modelcontextprotocol/static-primitives";
|
|
552
|
+
var VOLATILITIES = ["stable", "volatile"];
|
|
553
|
+
function resourceHints(entry) {
|
|
554
|
+
const meta = entry && entry._meta && entry._meta[STATIC_PRIMITIVES_META] || {};
|
|
555
|
+
return {
|
|
556
|
+
// A non-numeric or absent size means "unknown", never 0 — 0 would read
|
|
557
|
+
// as a free resource and sail through every budget check.
|
|
558
|
+
sizeBytes: typeof meta.sizeBytes === "number" && isFinite(meta.sizeBytes) ? meta.sizeBytes : null,
|
|
559
|
+
volatility: VOLATILITIES.indexOf(meta.volatility) === -1 ? "stable" : meta.volatility,
|
|
560
|
+
autoAttach: typeof meta.autoAttach === "boolean" ? meta.autoAttach : true
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
function planResourceAttachment(entry, budgetBytes) {
|
|
564
|
+
const { sizeBytes, volatility, autoAttach } = resourceHints(entry);
|
|
565
|
+
const base = { sizeBytes, volatility, uri: entry && entry.uri };
|
|
566
|
+
if (!autoAttach) {
|
|
567
|
+
return { ...base, fetch: false, autoAttach: false, reason: "not-auto-attach" };
|
|
568
|
+
}
|
|
569
|
+
if (sizeBytes !== null && sizeBytes > budgetBytes) {
|
|
570
|
+
return { ...base, fetch: false, autoAttach: false, reason: "over-budget" };
|
|
571
|
+
}
|
|
572
|
+
return {
|
|
573
|
+
...base,
|
|
574
|
+
fetch: true,
|
|
575
|
+
autoAttach: true,
|
|
576
|
+
// Unknown size still gets fetched, then trimmed — the trim is the
|
|
577
|
+
// safety net for servers that do not declare a size.
|
|
578
|
+
reason: sizeBytes === null ? "size-unknown" : "within-budget"
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
function trimResourceText(text, maxBytes) {
|
|
582
|
+
if (text.length <= maxBytes) return text;
|
|
583
|
+
try {
|
|
584
|
+
const parsed = JSON.parse(text);
|
|
585
|
+
const rows = parsed && parsed.dictionaries;
|
|
586
|
+
if (Array.isArray(rows)) {
|
|
587
|
+
const names = rows.map((d) => d && d.name).filter(Boolean);
|
|
588
|
+
return JSON.stringify({ dictionaries: names, note: "names only \u2014 full catalog too large to inline" });
|
|
589
|
+
}
|
|
590
|
+
} catch (e) {
|
|
591
|
+
}
|
|
592
|
+
return text.slice(0, maxBytes) + "\n\u2026truncated";
|
|
593
|
+
}
|
|
594
|
+
async function readResourceContext({ endpoint, uri, name, read, budgetBytes }) {
|
|
595
|
+
try {
|
|
596
|
+
const result = await read({ endpoint, uri });
|
|
597
|
+
const entry = (result && result.contents || [])[0];
|
|
598
|
+
if (!entry || !entry.text) return null;
|
|
599
|
+
return { uri, name: name || uri, text: trimResourceText(entry.text, budgetBytes) };
|
|
600
|
+
} catch (e) {
|
|
601
|
+
return null;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
async function loadHostResource({ endpoint, budgetBytes, list: list2, read, onSkip }) {
|
|
605
|
+
const resources = await list2({ endpoint });
|
|
606
|
+
const candidate = (resources || []).filter((r) => (r.mimeType || "") === "application/json")[0];
|
|
607
|
+
if (!candidate) return null;
|
|
608
|
+
const plan = planResourceAttachment(candidate, budgetBytes);
|
|
609
|
+
plan.name = candidate.name || candidate.uri;
|
|
610
|
+
plan.endpoint = endpoint;
|
|
611
|
+
if (!plan.fetch) {
|
|
612
|
+
if (onSkip) onSkip(plan);
|
|
613
|
+
return { plan, context: null };
|
|
614
|
+
}
|
|
615
|
+
if (plan.volatility === "volatile") return { plan, context: null };
|
|
616
|
+
const context = await readResourceContext({
|
|
617
|
+
endpoint,
|
|
618
|
+
uri: candidate.uri,
|
|
619
|
+
name: plan.name,
|
|
620
|
+
read,
|
|
621
|
+
budgetBytes
|
|
622
|
+
});
|
|
623
|
+
return { plan, context };
|
|
624
|
+
}
|
|
625
|
+
function resourceContextLines(context) {
|
|
626
|
+
if (!context) return [];
|
|
627
|
+
return ["", "Reference data \u2014 " + context.name + " (" + context.uri + "):", context.text];
|
|
628
|
+
}
|
|
629
|
+
async function resourceLinesForTurn({ plan, cached, endpoint, read, budgetBytes }) {
|
|
630
|
+
if (!plan || !plan.autoAttach) return { lines: [], cached };
|
|
631
|
+
if (plan.volatility === "volatile") {
|
|
632
|
+
const fresh = await readResourceContext({
|
|
633
|
+
endpoint,
|
|
634
|
+
uri: plan.uri,
|
|
635
|
+
name: plan.name,
|
|
636
|
+
read,
|
|
637
|
+
budgetBytes
|
|
638
|
+
});
|
|
639
|
+
return fresh ? { lines: resourceContextLines(fresh), cached: fresh } : { lines: [], cached };
|
|
640
|
+
}
|
|
641
|
+
return { lines: resourceContextLines(cached), cached };
|
|
642
|
+
}
|
|
643
|
+
var PROMPT_ARG_ALIASES = { dictionaries: "selected_dictionaries" };
|
|
644
|
+
function promptArgFromState(argName, state) {
|
|
645
|
+
const reader = state && state[argName] || state && state[PROMPT_ARG_ALIASES[argName]];
|
|
646
|
+
if (typeof reader !== "function") return "";
|
|
647
|
+
let value;
|
|
648
|
+
try {
|
|
649
|
+
value = reader();
|
|
650
|
+
} catch (e) {
|
|
651
|
+
return "";
|
|
652
|
+
}
|
|
653
|
+
if (value === null || value === void 0) return "";
|
|
654
|
+
return Array.isArray(value) ? value.join(",") : String(value);
|
|
655
|
+
}
|
|
656
|
+
function resolvePromptArguments(prompt, state) {
|
|
657
|
+
const args = {};
|
|
658
|
+
const missing = [];
|
|
659
|
+
for (const arg of prompt && prompt.arguments || []) {
|
|
660
|
+
const value = promptArgFromState(arg.name, state);
|
|
661
|
+
if (value) args[arg.name] = value;
|
|
662
|
+
else if (arg.required) missing.push(arg.name);
|
|
663
|
+
}
|
|
664
|
+
return { args, missing };
|
|
665
|
+
}
|
|
666
|
+
function promptArgumentSummary(prompt, state) {
|
|
667
|
+
return (prompt && prompt.arguments || []).map((arg) => {
|
|
668
|
+
const value = promptArgFromState(arg.name, state);
|
|
669
|
+
return { name: arg.name, value, filled: value.length > 0, required: !!arg.required };
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
function promptButtonProps(prompt) {
|
|
673
|
+
return {
|
|
674
|
+
label: prompt.title || prompt.name,
|
|
675
|
+
title: prompt.description || "Run the " + prompt.name + " prompt"
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
async function* parseNdjsonStream(readableStream, signal) {
|
|
679
|
+
const reader = readableStream.getReader();
|
|
680
|
+
const decoder = new TextDecoder("utf-8");
|
|
681
|
+
let buffer = "";
|
|
682
|
+
const onAbort = () => {
|
|
683
|
+
try {
|
|
684
|
+
reader.cancel();
|
|
685
|
+
} catch {
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
signal?.addEventListener("abort", onAbort);
|
|
689
|
+
try {
|
|
690
|
+
while (true) {
|
|
691
|
+
const { value, done } = await reader.read();
|
|
692
|
+
if (done) break;
|
|
693
|
+
buffer += decoder.decode(value, { stream: true });
|
|
694
|
+
let nl;
|
|
695
|
+
while ((nl = buffer.indexOf("\n")) !== -1) {
|
|
696
|
+
const line = buffer.slice(0, nl).trim();
|
|
697
|
+
buffer = buffer.slice(nl + 1);
|
|
698
|
+
if (!line) continue;
|
|
699
|
+
try {
|
|
700
|
+
yield JSON.parse(line);
|
|
701
|
+
} catch (e) {
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
const tail = buffer.trim();
|
|
706
|
+
if (tail) {
|
|
707
|
+
try {
|
|
708
|
+
yield JSON.parse(tail);
|
|
709
|
+
} catch (e) {
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
} finally {
|
|
713
|
+
signal?.removeEventListener("abort", onAbort);
|
|
714
|
+
try {
|
|
715
|
+
reader.releaseLock();
|
|
716
|
+
} catch {
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
function toolsForOllama(localTools) {
|
|
721
|
+
return (localTools || []).map((t) => ({
|
|
722
|
+
type: "function",
|
|
723
|
+
function: {
|
|
724
|
+
name: t.name,
|
|
725
|
+
description: t.description,
|
|
726
|
+
parameters: t.input_schema || t.inputSchema || { type: "object", properties: {} }
|
|
727
|
+
}
|
|
728
|
+
}));
|
|
729
|
+
}
|
|
730
|
+
function messagesForOllama(messages) {
|
|
731
|
+
return (messages || []).map((m) => {
|
|
732
|
+
if (m.role === "tool") {
|
|
733
|
+
return { role: "tool", tool_name: m.name, content: m.content };
|
|
734
|
+
}
|
|
735
|
+
if (m.tool_calls) {
|
|
736
|
+
return {
|
|
737
|
+
role: m.role,
|
|
738
|
+
content: m.content || "",
|
|
739
|
+
tool_calls: m.tool_calls.map((tc) => ({
|
|
740
|
+
function: { name: tc.name, arguments: coerceArguments(tc.arguments) }
|
|
741
|
+
}))
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
return { role: m.role, content: m.content };
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
async function ollamaChatCall({
|
|
748
|
+
baseUrl,
|
|
749
|
+
modelName,
|
|
750
|
+
messages,
|
|
751
|
+
localTools = [],
|
|
752
|
+
generationSettings = {},
|
|
753
|
+
onTextDelta,
|
|
754
|
+
onThinkingDelta,
|
|
755
|
+
onToolCall,
|
|
756
|
+
onPhase,
|
|
757
|
+
signal
|
|
758
|
+
}) {
|
|
759
|
+
if (!baseUrl || !modelName) throw new Error("ollamaChatCall: baseUrl and modelName are required");
|
|
760
|
+
if (!Array.isArray(messages) || messages.length === 0) {
|
|
761
|
+
throw new Error("ollamaChatCall: messages must be a non-empty array");
|
|
762
|
+
}
|
|
763
|
+
const { think, ...options2 } = generationSettings || {};
|
|
764
|
+
const body = { model: modelName, messages: messagesForOllama(messages), stream: true };
|
|
765
|
+
if (localTools && localTools.length) body.tools = toolsForOllama(localTools);
|
|
766
|
+
if (think !== void 0) body.think = think;
|
|
767
|
+
if (Object.keys(options2).length) body.options = options2;
|
|
768
|
+
const response = await fetch(`${baseUrl.replace(/\/$/, "")}/api/chat`, {
|
|
769
|
+
method: "POST",
|
|
770
|
+
headers: { "Content-Type": "application/json" },
|
|
771
|
+
body: JSON.stringify(body),
|
|
772
|
+
signal
|
|
773
|
+
});
|
|
774
|
+
if (!response.ok) {
|
|
775
|
+
const text = await response.text().catch(() => "");
|
|
776
|
+
throw new Error(`ollamaChatCall: HTTP ${response.status} ${response.statusText}${text ? " \u2014 " + text.slice(0, 200) : ""}`);
|
|
777
|
+
}
|
|
778
|
+
if (!response.body) throw new Error("ollamaChatCall: response has no body (streaming unsupported?)");
|
|
779
|
+
const toolCalls = [];
|
|
780
|
+
let content = "";
|
|
781
|
+
let finishReason = null;
|
|
782
|
+
let announcedPhase = null;
|
|
783
|
+
const phase = (name) => {
|
|
784
|
+
if (announcedPhase === name) return;
|
|
785
|
+
announcedPhase = name;
|
|
786
|
+
onPhase?.(name);
|
|
787
|
+
};
|
|
788
|
+
for await (const frame of parseNdjsonStream(response.body, signal)) {
|
|
789
|
+
const message = frame.message || {};
|
|
790
|
+
if (message.thinking) {
|
|
791
|
+
phase("thinking");
|
|
792
|
+
onThinkingDelta?.(message.thinking);
|
|
793
|
+
}
|
|
794
|
+
if (message.content) {
|
|
795
|
+
phase("responding");
|
|
796
|
+
content += message.content;
|
|
797
|
+
onTextDelta?.(message.content);
|
|
798
|
+
}
|
|
799
|
+
for (const tc of message.tool_calls || []) {
|
|
800
|
+
const call = {
|
|
801
|
+
id: tc.id || `ollama-${toolCalls.length}`,
|
|
802
|
+
name: tc.function?.name,
|
|
803
|
+
arguments: tc.function?.arguments ?? {}
|
|
804
|
+
};
|
|
805
|
+
toolCalls.push(call);
|
|
806
|
+
onToolCall?.(call);
|
|
807
|
+
}
|
|
808
|
+
if (frame.done) finishReason = frame.done_reason || "stop";
|
|
809
|
+
}
|
|
810
|
+
return { content, toolCalls, finishReason };
|
|
811
|
+
}
|
|
812
|
+
async function fetchOllamaModels({ baseUrl, signal }) {
|
|
813
|
+
try {
|
|
814
|
+
const response = await fetch(`${baseUrl.replace(/\/$/, "")}/api/tags`, { signal });
|
|
815
|
+
if (!response.ok) return [];
|
|
816
|
+
const data = await response.json();
|
|
817
|
+
return (data.models || []).map((m) => m.name).filter(Boolean);
|
|
818
|
+
} catch (e) {
|
|
819
|
+
return [];
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
var CONVERSATION_FORMAT = 1;
|
|
823
|
+
function packConversation({
|
|
824
|
+
turns = [],
|
|
825
|
+
open = false,
|
|
826
|
+
model = null,
|
|
827
|
+
maxTurns = 40,
|
|
828
|
+
maxBytes = 2e5
|
|
829
|
+
} = {}) {
|
|
830
|
+
let kept = turns.slice(-maxTurns);
|
|
831
|
+
let payload = { v: CONVERSATION_FORMAT, savedAt: Date.now(), open, model, turns: kept };
|
|
832
|
+
let json = JSON.stringify(payload);
|
|
833
|
+
while (json.length > maxBytes && kept.length > 1) {
|
|
834
|
+
kept = kept.slice(1);
|
|
835
|
+
payload = { ...payload, turns: kept };
|
|
836
|
+
json = JSON.stringify(payload);
|
|
837
|
+
}
|
|
838
|
+
return json;
|
|
839
|
+
}
|
|
840
|
+
function unpackConversation(raw) {
|
|
841
|
+
if (!raw) return null;
|
|
842
|
+
let payload;
|
|
843
|
+
try {
|
|
844
|
+
payload = JSON.parse(raw);
|
|
845
|
+
} catch (e) {
|
|
846
|
+
return null;
|
|
847
|
+
}
|
|
848
|
+
if (!payload || payload.v !== CONVERSATION_FORMAT || !Array.isArray(payload.turns)) return null;
|
|
849
|
+
const turns = payload.turns.filter((t) => t && typeof t.content === "string" && (t.role === "user" || t.role === "assistant"));
|
|
850
|
+
return { turns, open: !!payload.open, model: payload.model || null, savedAt: payload.savedAt || 0 };
|
|
851
|
+
}
|
|
852
|
+
function createConversationStore({ storage, key }) {
|
|
853
|
+
const safely = (fn, fallback = null) => {
|
|
854
|
+
try {
|
|
855
|
+
return fn();
|
|
856
|
+
} catch (e) {
|
|
857
|
+
return fallback;
|
|
858
|
+
}
|
|
859
|
+
};
|
|
860
|
+
return {
|
|
861
|
+
key,
|
|
862
|
+
save(state) {
|
|
863
|
+
return safely(() => {
|
|
864
|
+
storage.setItem(key, packConversation(state));
|
|
865
|
+
return true;
|
|
866
|
+
}, false);
|
|
867
|
+
},
|
|
868
|
+
load() {
|
|
869
|
+
return safely(() => unpackConversation(storage.getItem(key)));
|
|
870
|
+
},
|
|
871
|
+
clear() {
|
|
872
|
+
return safely(() => {
|
|
873
|
+
storage.removeItem(key);
|
|
874
|
+
return true;
|
|
875
|
+
}, false);
|
|
876
|
+
}
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
function conversationKeyFor(location, prefix = "llm_meta_widget") {
|
|
880
|
+
return `${prefix}:${location.pathname}`;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// app/assets/javascripts/llm_meta_widget/marked.esm.js
|
|
884
|
+
function _getDefaults() {
|
|
885
|
+
return {
|
|
886
|
+
async: false,
|
|
887
|
+
breaks: false,
|
|
888
|
+
extensions: null,
|
|
889
|
+
gfm: true,
|
|
890
|
+
hooks: null,
|
|
891
|
+
pedantic: false,
|
|
892
|
+
renderer: null,
|
|
893
|
+
silent: false,
|
|
894
|
+
tokenizer: null,
|
|
895
|
+
walkTokens: null
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
var _defaults = _getDefaults();
|
|
899
|
+
function changeDefaults(newDefaults) {
|
|
900
|
+
_defaults = newDefaults;
|
|
901
|
+
}
|
|
902
|
+
var escapeTest = /[&<>"']/;
|
|
903
|
+
var escapeReplace = new RegExp(escapeTest.source, "g");
|
|
904
|
+
var escapeTestNoEncode = /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/;
|
|
905
|
+
var escapeReplaceNoEncode = new RegExp(escapeTestNoEncode.source, "g");
|
|
906
|
+
var escapeReplacements = {
|
|
907
|
+
"&": "&",
|
|
908
|
+
"<": "<",
|
|
909
|
+
">": ">",
|
|
910
|
+
'"': """,
|
|
911
|
+
"'": "'"
|
|
912
|
+
};
|
|
913
|
+
var getEscapeReplacement = (ch) => escapeReplacements[ch];
|
|
914
|
+
function escape$1(html2, encode) {
|
|
915
|
+
if (encode) {
|
|
916
|
+
if (escapeTest.test(html2)) {
|
|
917
|
+
return html2.replace(escapeReplace, getEscapeReplacement);
|
|
918
|
+
}
|
|
919
|
+
} else {
|
|
920
|
+
if (escapeTestNoEncode.test(html2)) {
|
|
921
|
+
return html2.replace(escapeReplaceNoEncode, getEscapeReplacement);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
return html2;
|
|
925
|
+
}
|
|
926
|
+
var unescapeTest = /&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig;
|
|
927
|
+
function unescape(html2) {
|
|
928
|
+
return html2.replace(unescapeTest, (_, n) => {
|
|
929
|
+
n = n.toLowerCase();
|
|
930
|
+
if (n === "colon")
|
|
931
|
+
return ":";
|
|
932
|
+
if (n.charAt(0) === "#") {
|
|
933
|
+
return n.charAt(1) === "x" ? String.fromCharCode(parseInt(n.substring(2), 16)) : String.fromCharCode(+n.substring(1));
|
|
934
|
+
}
|
|
935
|
+
return "";
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
var caret = /(^|[^\[])\^/g;
|
|
939
|
+
function edit(regex, opt) {
|
|
940
|
+
let source = typeof regex === "string" ? regex : regex.source;
|
|
941
|
+
opt = opt || "";
|
|
942
|
+
const obj = {
|
|
943
|
+
replace: (name, val) => {
|
|
944
|
+
let valSource = typeof val === "string" ? val : val.source;
|
|
945
|
+
valSource = valSource.replace(caret, "$1");
|
|
946
|
+
source = source.replace(name, valSource);
|
|
947
|
+
return obj;
|
|
948
|
+
},
|
|
949
|
+
getRegex: () => {
|
|
950
|
+
return new RegExp(source, opt);
|
|
951
|
+
}
|
|
952
|
+
};
|
|
953
|
+
return obj;
|
|
954
|
+
}
|
|
955
|
+
function cleanUrl(href) {
|
|
956
|
+
try {
|
|
957
|
+
href = encodeURI(href).replace(/%25/g, "%");
|
|
958
|
+
} catch (e) {
|
|
959
|
+
return null;
|
|
960
|
+
}
|
|
961
|
+
return href;
|
|
962
|
+
}
|
|
963
|
+
var noopTest = { exec: () => null };
|
|
964
|
+
function splitCells(tableRow, count) {
|
|
965
|
+
const row = tableRow.replace(/\|/g, (match, offset, str) => {
|
|
966
|
+
let escaped = false;
|
|
967
|
+
let curr = offset;
|
|
968
|
+
while (--curr >= 0 && str[curr] === "\\")
|
|
969
|
+
escaped = !escaped;
|
|
970
|
+
if (escaped) {
|
|
971
|
+
return "|";
|
|
972
|
+
} else {
|
|
973
|
+
return " |";
|
|
974
|
+
}
|
|
975
|
+
}), cells = row.split(/ \|/);
|
|
976
|
+
let i = 0;
|
|
977
|
+
if (!cells[0].trim()) {
|
|
978
|
+
cells.shift();
|
|
979
|
+
}
|
|
980
|
+
if (cells.length > 0 && !cells[cells.length - 1].trim()) {
|
|
981
|
+
cells.pop();
|
|
982
|
+
}
|
|
983
|
+
if (count) {
|
|
984
|
+
if (cells.length > count) {
|
|
985
|
+
cells.splice(count);
|
|
986
|
+
} else {
|
|
987
|
+
while (cells.length < count)
|
|
988
|
+
cells.push("");
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
for (; i < cells.length; i++) {
|
|
992
|
+
cells[i] = cells[i].trim().replace(/\\\|/g, "|");
|
|
993
|
+
}
|
|
994
|
+
return cells;
|
|
995
|
+
}
|
|
996
|
+
function rtrim(str, c, invert) {
|
|
997
|
+
const l = str.length;
|
|
998
|
+
if (l === 0) {
|
|
999
|
+
return "";
|
|
1000
|
+
}
|
|
1001
|
+
let suffLen = 0;
|
|
1002
|
+
while (suffLen < l) {
|
|
1003
|
+
const currChar = str.charAt(l - suffLen - 1);
|
|
1004
|
+
if (currChar === c && !invert) {
|
|
1005
|
+
suffLen++;
|
|
1006
|
+
} else if (currChar !== c && invert) {
|
|
1007
|
+
suffLen++;
|
|
1008
|
+
} else {
|
|
1009
|
+
break;
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
return str.slice(0, l - suffLen);
|
|
1013
|
+
}
|
|
1014
|
+
function findClosingBracket(str, b) {
|
|
1015
|
+
if (str.indexOf(b[1]) === -1) {
|
|
1016
|
+
return -1;
|
|
1017
|
+
}
|
|
1018
|
+
let level = 0;
|
|
1019
|
+
for (let i = 0; i < str.length; i++) {
|
|
1020
|
+
if (str[i] === "\\") {
|
|
1021
|
+
i++;
|
|
1022
|
+
} else if (str[i] === b[0]) {
|
|
1023
|
+
level++;
|
|
1024
|
+
} else if (str[i] === b[1]) {
|
|
1025
|
+
level--;
|
|
1026
|
+
if (level < 0) {
|
|
1027
|
+
return i;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
return -1;
|
|
1032
|
+
}
|
|
1033
|
+
function outputLink(cap, link2, raw, lexer2) {
|
|
1034
|
+
const href = link2.href;
|
|
1035
|
+
const title = link2.title ? escape$1(link2.title) : null;
|
|
1036
|
+
const text = cap[1].replace(/\\([\[\]])/g, "$1");
|
|
1037
|
+
if (cap[0].charAt(0) !== "!") {
|
|
1038
|
+
lexer2.state.inLink = true;
|
|
1039
|
+
const token = {
|
|
1040
|
+
type: "link",
|
|
1041
|
+
raw,
|
|
1042
|
+
href,
|
|
1043
|
+
title,
|
|
1044
|
+
text,
|
|
1045
|
+
tokens: lexer2.inlineTokens(text)
|
|
1046
|
+
};
|
|
1047
|
+
lexer2.state.inLink = false;
|
|
1048
|
+
return token;
|
|
1049
|
+
}
|
|
1050
|
+
return {
|
|
1051
|
+
type: "image",
|
|
1052
|
+
raw,
|
|
1053
|
+
href,
|
|
1054
|
+
title,
|
|
1055
|
+
text: escape$1(text)
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
function indentCodeCompensation(raw, text) {
|
|
1059
|
+
const matchIndentToCode = raw.match(/^(\s+)(?:```)/);
|
|
1060
|
+
if (matchIndentToCode === null) {
|
|
1061
|
+
return text;
|
|
1062
|
+
}
|
|
1063
|
+
const indentToCode = matchIndentToCode[1];
|
|
1064
|
+
return text.split("\n").map((node) => {
|
|
1065
|
+
const matchIndentInNode = node.match(/^\s+/);
|
|
1066
|
+
if (matchIndentInNode === null) {
|
|
1067
|
+
return node;
|
|
1068
|
+
}
|
|
1069
|
+
const [indentInNode] = matchIndentInNode;
|
|
1070
|
+
if (indentInNode.length >= indentToCode.length) {
|
|
1071
|
+
return node.slice(indentToCode.length);
|
|
1072
|
+
}
|
|
1073
|
+
return node;
|
|
1074
|
+
}).join("\n");
|
|
1075
|
+
}
|
|
1076
|
+
var _Tokenizer = class {
|
|
1077
|
+
options;
|
|
1078
|
+
rules;
|
|
1079
|
+
// set by the lexer
|
|
1080
|
+
lexer;
|
|
1081
|
+
// set by the lexer
|
|
1082
|
+
constructor(options2) {
|
|
1083
|
+
this.options = options2 || _defaults;
|
|
1084
|
+
}
|
|
1085
|
+
space(src) {
|
|
1086
|
+
const cap = this.rules.block.newline.exec(src);
|
|
1087
|
+
if (cap && cap[0].length > 0) {
|
|
1088
|
+
return {
|
|
1089
|
+
type: "space",
|
|
1090
|
+
raw: cap[0]
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
code(src) {
|
|
1095
|
+
const cap = this.rules.block.code.exec(src);
|
|
1096
|
+
if (cap) {
|
|
1097
|
+
const text = cap[0].replace(/^ {1,4}/gm, "");
|
|
1098
|
+
return {
|
|
1099
|
+
type: "code",
|
|
1100
|
+
raw: cap[0],
|
|
1101
|
+
codeBlockStyle: "indented",
|
|
1102
|
+
text: !this.options.pedantic ? rtrim(text, "\n") : text
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
fences(src) {
|
|
1107
|
+
const cap = this.rules.block.fences.exec(src);
|
|
1108
|
+
if (cap) {
|
|
1109
|
+
const raw = cap[0];
|
|
1110
|
+
const text = indentCodeCompensation(raw, cap[3] || "");
|
|
1111
|
+
return {
|
|
1112
|
+
type: "code",
|
|
1113
|
+
raw,
|
|
1114
|
+
lang: cap[2] ? cap[2].trim().replace(this.rules.inline.anyPunctuation, "$1") : cap[2],
|
|
1115
|
+
text
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
heading(src) {
|
|
1120
|
+
const cap = this.rules.block.heading.exec(src);
|
|
1121
|
+
if (cap) {
|
|
1122
|
+
let text = cap[2].trim();
|
|
1123
|
+
if (/#$/.test(text)) {
|
|
1124
|
+
const trimmed = rtrim(text, "#");
|
|
1125
|
+
if (this.options.pedantic) {
|
|
1126
|
+
text = trimmed.trim();
|
|
1127
|
+
} else if (!trimmed || / $/.test(trimmed)) {
|
|
1128
|
+
text = trimmed.trim();
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
return {
|
|
1132
|
+
type: "heading",
|
|
1133
|
+
raw: cap[0],
|
|
1134
|
+
depth: cap[1].length,
|
|
1135
|
+
text,
|
|
1136
|
+
tokens: this.lexer.inline(text)
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
hr(src) {
|
|
1141
|
+
const cap = this.rules.block.hr.exec(src);
|
|
1142
|
+
if (cap) {
|
|
1143
|
+
return {
|
|
1144
|
+
type: "hr",
|
|
1145
|
+
raw: cap[0]
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
blockquote(src) {
|
|
1150
|
+
const cap = this.rules.block.blockquote.exec(src);
|
|
1151
|
+
if (cap) {
|
|
1152
|
+
let text = cap[0].replace(/\n {0,3}((?:=+|-+) *)(?=\n|$)/g, "\n $1");
|
|
1153
|
+
text = rtrim(text.replace(/^ *>[ \t]?/gm, ""), "\n");
|
|
1154
|
+
const top = this.lexer.state.top;
|
|
1155
|
+
this.lexer.state.top = true;
|
|
1156
|
+
const tokens = this.lexer.blockTokens(text);
|
|
1157
|
+
this.lexer.state.top = top;
|
|
1158
|
+
return {
|
|
1159
|
+
type: "blockquote",
|
|
1160
|
+
raw: cap[0],
|
|
1161
|
+
tokens,
|
|
1162
|
+
text
|
|
1163
|
+
};
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
list(src) {
|
|
1167
|
+
let cap = this.rules.block.list.exec(src);
|
|
1168
|
+
if (cap) {
|
|
1169
|
+
let bull = cap[1].trim();
|
|
1170
|
+
const isordered = bull.length > 1;
|
|
1171
|
+
const list2 = {
|
|
1172
|
+
type: "list",
|
|
1173
|
+
raw: "",
|
|
1174
|
+
ordered: isordered,
|
|
1175
|
+
start: isordered ? +bull.slice(0, -1) : "",
|
|
1176
|
+
loose: false,
|
|
1177
|
+
items: []
|
|
1178
|
+
};
|
|
1179
|
+
bull = isordered ? `\\d{1,9}\\${bull.slice(-1)}` : `\\${bull}`;
|
|
1180
|
+
if (this.options.pedantic) {
|
|
1181
|
+
bull = isordered ? bull : "[*+-]";
|
|
1182
|
+
}
|
|
1183
|
+
const itemRegex = new RegExp(`^( {0,3}${bull})((?:[ ][^\\n]*)?(?:\\n|$))`);
|
|
1184
|
+
let raw = "";
|
|
1185
|
+
let itemContents = "";
|
|
1186
|
+
let endsWithBlankLine = false;
|
|
1187
|
+
while (src) {
|
|
1188
|
+
let endEarly = false;
|
|
1189
|
+
if (!(cap = itemRegex.exec(src))) {
|
|
1190
|
+
break;
|
|
1191
|
+
}
|
|
1192
|
+
if (this.rules.block.hr.test(src)) {
|
|
1193
|
+
break;
|
|
1194
|
+
}
|
|
1195
|
+
raw = cap[0];
|
|
1196
|
+
src = src.substring(raw.length);
|
|
1197
|
+
let line = cap[2].split("\n", 1)[0].replace(/^\t+/, (t) => " ".repeat(3 * t.length));
|
|
1198
|
+
let nextLine = src.split("\n", 1)[0];
|
|
1199
|
+
let indent = 0;
|
|
1200
|
+
if (this.options.pedantic) {
|
|
1201
|
+
indent = 2;
|
|
1202
|
+
itemContents = line.trimStart();
|
|
1203
|
+
} else {
|
|
1204
|
+
indent = cap[2].search(/[^ ]/);
|
|
1205
|
+
indent = indent > 4 ? 1 : indent;
|
|
1206
|
+
itemContents = line.slice(indent);
|
|
1207
|
+
indent += cap[1].length;
|
|
1208
|
+
}
|
|
1209
|
+
let blankLine = false;
|
|
1210
|
+
if (!line && /^ *$/.test(nextLine)) {
|
|
1211
|
+
raw += nextLine + "\n";
|
|
1212
|
+
src = src.substring(nextLine.length + 1);
|
|
1213
|
+
endEarly = true;
|
|
1214
|
+
}
|
|
1215
|
+
if (!endEarly) {
|
|
1216
|
+
const nextBulletRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ ][^\\n]*)?(?:\\n|$))`);
|
|
1217
|
+
const hrRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`);
|
|
1218
|
+
const fencesBeginRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}(?:\`\`\`|~~~)`);
|
|
1219
|
+
const headingBeginRegex = new RegExp(`^ {0,${Math.min(3, indent - 1)}}#`);
|
|
1220
|
+
while (src) {
|
|
1221
|
+
const rawLine = src.split("\n", 1)[0];
|
|
1222
|
+
nextLine = rawLine;
|
|
1223
|
+
if (this.options.pedantic) {
|
|
1224
|
+
nextLine = nextLine.replace(/^ {1,4}(?=( {4})*[^ ])/g, " ");
|
|
1225
|
+
}
|
|
1226
|
+
if (fencesBeginRegex.test(nextLine)) {
|
|
1227
|
+
break;
|
|
1228
|
+
}
|
|
1229
|
+
if (headingBeginRegex.test(nextLine)) {
|
|
1230
|
+
break;
|
|
1231
|
+
}
|
|
1232
|
+
if (nextBulletRegex.test(nextLine)) {
|
|
1233
|
+
break;
|
|
1234
|
+
}
|
|
1235
|
+
if (hrRegex.test(src)) {
|
|
1236
|
+
break;
|
|
1237
|
+
}
|
|
1238
|
+
if (nextLine.search(/[^ ]/) >= indent || !nextLine.trim()) {
|
|
1239
|
+
itemContents += "\n" + nextLine.slice(indent);
|
|
1240
|
+
} else {
|
|
1241
|
+
if (blankLine) {
|
|
1242
|
+
break;
|
|
1243
|
+
}
|
|
1244
|
+
if (line.search(/[^ ]/) >= 4) {
|
|
1245
|
+
break;
|
|
1246
|
+
}
|
|
1247
|
+
if (fencesBeginRegex.test(line)) {
|
|
1248
|
+
break;
|
|
1249
|
+
}
|
|
1250
|
+
if (headingBeginRegex.test(line)) {
|
|
1251
|
+
break;
|
|
1252
|
+
}
|
|
1253
|
+
if (hrRegex.test(line)) {
|
|
1254
|
+
break;
|
|
1255
|
+
}
|
|
1256
|
+
itemContents += "\n" + nextLine;
|
|
1257
|
+
}
|
|
1258
|
+
if (!blankLine && !nextLine.trim()) {
|
|
1259
|
+
blankLine = true;
|
|
1260
|
+
}
|
|
1261
|
+
raw += rawLine + "\n";
|
|
1262
|
+
src = src.substring(rawLine.length + 1);
|
|
1263
|
+
line = nextLine.slice(indent);
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
if (!list2.loose) {
|
|
1267
|
+
if (endsWithBlankLine) {
|
|
1268
|
+
list2.loose = true;
|
|
1269
|
+
} else if (/\n *\n *$/.test(raw)) {
|
|
1270
|
+
endsWithBlankLine = true;
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
let istask = null;
|
|
1274
|
+
let ischecked;
|
|
1275
|
+
if (this.options.gfm) {
|
|
1276
|
+
istask = /^\[[ xX]\] /.exec(itemContents);
|
|
1277
|
+
if (istask) {
|
|
1278
|
+
ischecked = istask[0] !== "[ ] ";
|
|
1279
|
+
itemContents = itemContents.replace(/^\[[ xX]\] +/, "");
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
list2.items.push({
|
|
1283
|
+
type: "list_item",
|
|
1284
|
+
raw,
|
|
1285
|
+
task: !!istask,
|
|
1286
|
+
checked: ischecked,
|
|
1287
|
+
loose: false,
|
|
1288
|
+
text: itemContents,
|
|
1289
|
+
tokens: []
|
|
1290
|
+
});
|
|
1291
|
+
list2.raw += raw;
|
|
1292
|
+
}
|
|
1293
|
+
list2.items[list2.items.length - 1].raw = raw.trimEnd();
|
|
1294
|
+
list2.items[list2.items.length - 1].text = itemContents.trimEnd();
|
|
1295
|
+
list2.raw = list2.raw.trimEnd();
|
|
1296
|
+
for (let i = 0; i < list2.items.length; i++) {
|
|
1297
|
+
this.lexer.state.top = false;
|
|
1298
|
+
list2.items[i].tokens = this.lexer.blockTokens(list2.items[i].text, []);
|
|
1299
|
+
if (!list2.loose) {
|
|
1300
|
+
const spacers = list2.items[i].tokens.filter((t) => t.type === "space");
|
|
1301
|
+
const hasMultipleLineBreaks = spacers.length > 0 && spacers.some((t) => /\n.*\n/.test(t.raw));
|
|
1302
|
+
list2.loose = hasMultipleLineBreaks;
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
if (list2.loose) {
|
|
1306
|
+
for (let i = 0; i < list2.items.length; i++) {
|
|
1307
|
+
list2.items[i].loose = true;
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
return list2;
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
html(src) {
|
|
1314
|
+
const cap = this.rules.block.html.exec(src);
|
|
1315
|
+
if (cap) {
|
|
1316
|
+
const token = {
|
|
1317
|
+
type: "html",
|
|
1318
|
+
block: true,
|
|
1319
|
+
raw: cap[0],
|
|
1320
|
+
pre: cap[1] === "pre" || cap[1] === "script" || cap[1] === "style",
|
|
1321
|
+
text: cap[0]
|
|
1322
|
+
};
|
|
1323
|
+
return token;
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
def(src) {
|
|
1327
|
+
const cap = this.rules.block.def.exec(src);
|
|
1328
|
+
if (cap) {
|
|
1329
|
+
const tag2 = cap[1].toLowerCase().replace(/\s+/g, " ");
|
|
1330
|
+
const href = cap[2] ? cap[2].replace(/^<(.*)>$/, "$1").replace(this.rules.inline.anyPunctuation, "$1") : "";
|
|
1331
|
+
const title = cap[3] ? cap[3].substring(1, cap[3].length - 1).replace(this.rules.inline.anyPunctuation, "$1") : cap[3];
|
|
1332
|
+
return {
|
|
1333
|
+
type: "def",
|
|
1334
|
+
tag: tag2,
|
|
1335
|
+
raw: cap[0],
|
|
1336
|
+
href,
|
|
1337
|
+
title
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
table(src) {
|
|
1342
|
+
const cap = this.rules.block.table.exec(src);
|
|
1343
|
+
if (!cap) {
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
if (!/[:|]/.test(cap[2])) {
|
|
1347
|
+
return;
|
|
1348
|
+
}
|
|
1349
|
+
const headers = splitCells(cap[1]);
|
|
1350
|
+
const aligns = cap[2].replace(/^\||\| *$/g, "").split("|");
|
|
1351
|
+
const rows = cap[3] && cap[3].trim() ? cap[3].replace(/\n[ \t]*$/, "").split("\n") : [];
|
|
1352
|
+
const item = {
|
|
1353
|
+
type: "table",
|
|
1354
|
+
raw: cap[0],
|
|
1355
|
+
header: [],
|
|
1356
|
+
align: [],
|
|
1357
|
+
rows: []
|
|
1358
|
+
};
|
|
1359
|
+
if (headers.length !== aligns.length) {
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
for (const align of aligns) {
|
|
1363
|
+
if (/^ *-+: *$/.test(align)) {
|
|
1364
|
+
item.align.push("right");
|
|
1365
|
+
} else if (/^ *:-+: *$/.test(align)) {
|
|
1366
|
+
item.align.push("center");
|
|
1367
|
+
} else if (/^ *:-+ *$/.test(align)) {
|
|
1368
|
+
item.align.push("left");
|
|
1369
|
+
} else {
|
|
1370
|
+
item.align.push(null);
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
for (const header of headers) {
|
|
1374
|
+
item.header.push({
|
|
1375
|
+
text: header,
|
|
1376
|
+
tokens: this.lexer.inline(header)
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1379
|
+
for (const row of rows) {
|
|
1380
|
+
item.rows.push(splitCells(row, item.header.length).map((cell) => {
|
|
1381
|
+
return {
|
|
1382
|
+
text: cell,
|
|
1383
|
+
tokens: this.lexer.inline(cell)
|
|
1384
|
+
};
|
|
1385
|
+
}));
|
|
1386
|
+
}
|
|
1387
|
+
return item;
|
|
1388
|
+
}
|
|
1389
|
+
lheading(src) {
|
|
1390
|
+
const cap = this.rules.block.lheading.exec(src);
|
|
1391
|
+
if (cap) {
|
|
1392
|
+
return {
|
|
1393
|
+
type: "heading",
|
|
1394
|
+
raw: cap[0],
|
|
1395
|
+
depth: cap[2].charAt(0) === "=" ? 1 : 2,
|
|
1396
|
+
text: cap[1],
|
|
1397
|
+
tokens: this.lexer.inline(cap[1])
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
paragraph(src) {
|
|
1402
|
+
const cap = this.rules.block.paragraph.exec(src);
|
|
1403
|
+
if (cap) {
|
|
1404
|
+
const text = cap[1].charAt(cap[1].length - 1) === "\n" ? cap[1].slice(0, -1) : cap[1];
|
|
1405
|
+
return {
|
|
1406
|
+
type: "paragraph",
|
|
1407
|
+
raw: cap[0],
|
|
1408
|
+
text,
|
|
1409
|
+
tokens: this.lexer.inline(text)
|
|
1410
|
+
};
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
text(src) {
|
|
1414
|
+
const cap = this.rules.block.text.exec(src);
|
|
1415
|
+
if (cap) {
|
|
1416
|
+
return {
|
|
1417
|
+
type: "text",
|
|
1418
|
+
raw: cap[0],
|
|
1419
|
+
text: cap[0],
|
|
1420
|
+
tokens: this.lexer.inline(cap[0])
|
|
1421
|
+
};
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
escape(src) {
|
|
1425
|
+
const cap = this.rules.inline.escape.exec(src);
|
|
1426
|
+
if (cap) {
|
|
1427
|
+
return {
|
|
1428
|
+
type: "escape",
|
|
1429
|
+
raw: cap[0],
|
|
1430
|
+
text: escape$1(cap[1])
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
tag(src) {
|
|
1435
|
+
const cap = this.rules.inline.tag.exec(src);
|
|
1436
|
+
if (cap) {
|
|
1437
|
+
if (!this.lexer.state.inLink && /^<a /i.test(cap[0])) {
|
|
1438
|
+
this.lexer.state.inLink = true;
|
|
1439
|
+
} else if (this.lexer.state.inLink && /^<\/a>/i.test(cap[0])) {
|
|
1440
|
+
this.lexer.state.inLink = false;
|
|
1441
|
+
}
|
|
1442
|
+
if (!this.lexer.state.inRawBlock && /^<(pre|code|kbd|script)(\s|>)/i.test(cap[0])) {
|
|
1443
|
+
this.lexer.state.inRawBlock = true;
|
|
1444
|
+
} else if (this.lexer.state.inRawBlock && /^<\/(pre|code|kbd|script)(\s|>)/i.test(cap[0])) {
|
|
1445
|
+
this.lexer.state.inRawBlock = false;
|
|
1446
|
+
}
|
|
1447
|
+
return {
|
|
1448
|
+
type: "html",
|
|
1449
|
+
raw: cap[0],
|
|
1450
|
+
inLink: this.lexer.state.inLink,
|
|
1451
|
+
inRawBlock: this.lexer.state.inRawBlock,
|
|
1452
|
+
block: false,
|
|
1453
|
+
text: cap[0]
|
|
1454
|
+
};
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
link(src) {
|
|
1458
|
+
const cap = this.rules.inline.link.exec(src);
|
|
1459
|
+
if (cap) {
|
|
1460
|
+
const trimmedUrl = cap[2].trim();
|
|
1461
|
+
if (!this.options.pedantic && /^</.test(trimmedUrl)) {
|
|
1462
|
+
if (!/>$/.test(trimmedUrl)) {
|
|
1463
|
+
return;
|
|
1464
|
+
}
|
|
1465
|
+
const rtrimSlash = rtrim(trimmedUrl.slice(0, -1), "\\");
|
|
1466
|
+
if ((trimmedUrl.length - rtrimSlash.length) % 2 === 0) {
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
} else {
|
|
1470
|
+
const lastParenIndex = findClosingBracket(cap[2], "()");
|
|
1471
|
+
if (lastParenIndex > -1) {
|
|
1472
|
+
const start = cap[0].indexOf("!") === 0 ? 5 : 4;
|
|
1473
|
+
const linkLen = start + cap[1].length + lastParenIndex;
|
|
1474
|
+
cap[2] = cap[2].substring(0, lastParenIndex);
|
|
1475
|
+
cap[0] = cap[0].substring(0, linkLen).trim();
|
|
1476
|
+
cap[3] = "";
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
let href = cap[2];
|
|
1480
|
+
let title = "";
|
|
1481
|
+
if (this.options.pedantic) {
|
|
1482
|
+
const link2 = /^([^'"]*[^\s])\s+(['"])(.*)\2/.exec(href);
|
|
1483
|
+
if (link2) {
|
|
1484
|
+
href = link2[1];
|
|
1485
|
+
title = link2[3];
|
|
1486
|
+
}
|
|
1487
|
+
} else {
|
|
1488
|
+
title = cap[3] ? cap[3].slice(1, -1) : "";
|
|
1489
|
+
}
|
|
1490
|
+
href = href.trim();
|
|
1491
|
+
if (/^</.test(href)) {
|
|
1492
|
+
if (this.options.pedantic && !/>$/.test(trimmedUrl)) {
|
|
1493
|
+
href = href.slice(1);
|
|
1494
|
+
} else {
|
|
1495
|
+
href = href.slice(1, -1);
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
return outputLink(cap, {
|
|
1499
|
+
href: href ? href.replace(this.rules.inline.anyPunctuation, "$1") : href,
|
|
1500
|
+
title: title ? title.replace(this.rules.inline.anyPunctuation, "$1") : title
|
|
1501
|
+
}, cap[0], this.lexer);
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
reflink(src, links) {
|
|
1505
|
+
let cap;
|
|
1506
|
+
if ((cap = this.rules.inline.reflink.exec(src)) || (cap = this.rules.inline.nolink.exec(src))) {
|
|
1507
|
+
const linkString = (cap[2] || cap[1]).replace(/\s+/g, " ");
|
|
1508
|
+
const link2 = links[linkString.toLowerCase()];
|
|
1509
|
+
if (!link2) {
|
|
1510
|
+
const text = cap[0].charAt(0);
|
|
1511
|
+
return {
|
|
1512
|
+
type: "text",
|
|
1513
|
+
raw: text,
|
|
1514
|
+
text
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
return outputLink(cap, link2, cap[0], this.lexer);
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
emStrong(src, maskedSrc, prevChar = "") {
|
|
1521
|
+
let match = this.rules.inline.emStrongLDelim.exec(src);
|
|
1522
|
+
if (!match)
|
|
1523
|
+
return;
|
|
1524
|
+
if (match[3] && prevChar.match(/[\p{L}\p{N}]/u))
|
|
1525
|
+
return;
|
|
1526
|
+
const nextChar = match[1] || match[2] || "";
|
|
1527
|
+
if (!nextChar || !prevChar || this.rules.inline.punctuation.exec(prevChar)) {
|
|
1528
|
+
const lLength = [...match[0]].length - 1;
|
|
1529
|
+
let rDelim, rLength, delimTotal = lLength, midDelimTotal = 0;
|
|
1530
|
+
const endReg = match[0][0] === "*" ? this.rules.inline.emStrongRDelimAst : this.rules.inline.emStrongRDelimUnd;
|
|
1531
|
+
endReg.lastIndex = 0;
|
|
1532
|
+
maskedSrc = maskedSrc.slice(-1 * src.length + lLength);
|
|
1533
|
+
while ((match = endReg.exec(maskedSrc)) != null) {
|
|
1534
|
+
rDelim = match[1] || match[2] || match[3] || match[4] || match[5] || match[6];
|
|
1535
|
+
if (!rDelim)
|
|
1536
|
+
continue;
|
|
1537
|
+
rLength = [...rDelim].length;
|
|
1538
|
+
if (match[3] || match[4]) {
|
|
1539
|
+
delimTotal += rLength;
|
|
1540
|
+
continue;
|
|
1541
|
+
} else if (match[5] || match[6]) {
|
|
1542
|
+
if (lLength % 3 && !((lLength + rLength) % 3)) {
|
|
1543
|
+
midDelimTotal += rLength;
|
|
1544
|
+
continue;
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
delimTotal -= rLength;
|
|
1548
|
+
if (delimTotal > 0)
|
|
1549
|
+
continue;
|
|
1550
|
+
rLength = Math.min(rLength, rLength + delimTotal + midDelimTotal);
|
|
1551
|
+
const lastCharLength = [...match[0]][0].length;
|
|
1552
|
+
const raw = src.slice(0, lLength + match.index + lastCharLength + rLength);
|
|
1553
|
+
if (Math.min(lLength, rLength) % 2) {
|
|
1554
|
+
const text2 = raw.slice(1, -1);
|
|
1555
|
+
return {
|
|
1556
|
+
type: "em",
|
|
1557
|
+
raw,
|
|
1558
|
+
text: text2,
|
|
1559
|
+
tokens: this.lexer.inlineTokens(text2)
|
|
1560
|
+
};
|
|
1561
|
+
}
|
|
1562
|
+
const text = raw.slice(2, -2);
|
|
1563
|
+
return {
|
|
1564
|
+
type: "strong",
|
|
1565
|
+
raw,
|
|
1566
|
+
text,
|
|
1567
|
+
tokens: this.lexer.inlineTokens(text)
|
|
1568
|
+
};
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
codespan(src) {
|
|
1573
|
+
const cap = this.rules.inline.code.exec(src);
|
|
1574
|
+
if (cap) {
|
|
1575
|
+
let text = cap[2].replace(/\n/g, " ");
|
|
1576
|
+
const hasNonSpaceChars = /[^ ]/.test(text);
|
|
1577
|
+
const hasSpaceCharsOnBothEnds = /^ /.test(text) && / $/.test(text);
|
|
1578
|
+
if (hasNonSpaceChars && hasSpaceCharsOnBothEnds) {
|
|
1579
|
+
text = text.substring(1, text.length - 1);
|
|
1580
|
+
}
|
|
1581
|
+
text = escape$1(text, true);
|
|
1582
|
+
return {
|
|
1583
|
+
type: "codespan",
|
|
1584
|
+
raw: cap[0],
|
|
1585
|
+
text
|
|
1586
|
+
};
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
br(src) {
|
|
1590
|
+
const cap = this.rules.inline.br.exec(src);
|
|
1591
|
+
if (cap) {
|
|
1592
|
+
return {
|
|
1593
|
+
type: "br",
|
|
1594
|
+
raw: cap[0]
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
del(src) {
|
|
1599
|
+
const cap = this.rules.inline.del.exec(src);
|
|
1600
|
+
if (cap) {
|
|
1601
|
+
return {
|
|
1602
|
+
type: "del",
|
|
1603
|
+
raw: cap[0],
|
|
1604
|
+
text: cap[2],
|
|
1605
|
+
tokens: this.lexer.inlineTokens(cap[2])
|
|
1606
|
+
};
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
autolink(src) {
|
|
1610
|
+
const cap = this.rules.inline.autolink.exec(src);
|
|
1611
|
+
if (cap) {
|
|
1612
|
+
let text, href;
|
|
1613
|
+
if (cap[2] === "@") {
|
|
1614
|
+
text = escape$1(cap[1]);
|
|
1615
|
+
href = "mailto:" + text;
|
|
1616
|
+
} else {
|
|
1617
|
+
text = escape$1(cap[1]);
|
|
1618
|
+
href = text;
|
|
1619
|
+
}
|
|
1620
|
+
return {
|
|
1621
|
+
type: "link",
|
|
1622
|
+
raw: cap[0],
|
|
1623
|
+
text,
|
|
1624
|
+
href,
|
|
1625
|
+
tokens: [
|
|
1626
|
+
{
|
|
1627
|
+
type: "text",
|
|
1628
|
+
raw: text,
|
|
1629
|
+
text
|
|
1630
|
+
}
|
|
1631
|
+
]
|
|
1632
|
+
};
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
url(src) {
|
|
1636
|
+
let cap;
|
|
1637
|
+
if (cap = this.rules.inline.url.exec(src)) {
|
|
1638
|
+
let text, href;
|
|
1639
|
+
if (cap[2] === "@") {
|
|
1640
|
+
text = escape$1(cap[0]);
|
|
1641
|
+
href = "mailto:" + text;
|
|
1642
|
+
} else {
|
|
1643
|
+
let prevCapZero;
|
|
1644
|
+
do {
|
|
1645
|
+
prevCapZero = cap[0];
|
|
1646
|
+
cap[0] = this.rules.inline._backpedal.exec(cap[0])?.[0] ?? "";
|
|
1647
|
+
} while (prevCapZero !== cap[0]);
|
|
1648
|
+
text = escape$1(cap[0]);
|
|
1649
|
+
if (cap[1] === "www.") {
|
|
1650
|
+
href = "http://" + cap[0];
|
|
1651
|
+
} else {
|
|
1652
|
+
href = cap[0];
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
return {
|
|
1656
|
+
type: "link",
|
|
1657
|
+
raw: cap[0],
|
|
1658
|
+
text,
|
|
1659
|
+
href,
|
|
1660
|
+
tokens: [
|
|
1661
|
+
{
|
|
1662
|
+
type: "text",
|
|
1663
|
+
raw: text,
|
|
1664
|
+
text
|
|
1665
|
+
}
|
|
1666
|
+
]
|
|
1667
|
+
};
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
inlineText(src) {
|
|
1671
|
+
const cap = this.rules.inline.text.exec(src);
|
|
1672
|
+
if (cap) {
|
|
1673
|
+
let text;
|
|
1674
|
+
if (this.lexer.state.inRawBlock) {
|
|
1675
|
+
text = cap[0];
|
|
1676
|
+
} else {
|
|
1677
|
+
text = escape$1(cap[0]);
|
|
1678
|
+
}
|
|
1679
|
+
return {
|
|
1680
|
+
type: "text",
|
|
1681
|
+
raw: cap[0],
|
|
1682
|
+
text
|
|
1683
|
+
};
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
};
|
|
1687
|
+
var newline = /^(?: *(?:\n|$))+/;
|
|
1688
|
+
var blockCode = /^( {4}[^\n]+(?:\n(?: *(?:\n|$))*)?)+/;
|
|
1689
|
+
var fences = /^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/;
|
|
1690
|
+
var hr = /^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/;
|
|
1691
|
+
var heading = /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/;
|
|
1692
|
+
var bullet = /(?:[*+-]|\d{1,9}[.)])/;
|
|
1693
|
+
var lheading = edit(/^(?!bull |blockCode|fences|blockquote|heading|html)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html))+?)\n {0,3}(=+|-+) *(?:\n+|$)/).replace(/bull/g, bullet).replace(/blockCode/g, / {4}/).replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g, / {0,3}>/).replace(/heading/g, / {0,3}#{1,6}/).replace(/html/g, / {0,3}<[^\n>]+>\n/).getRegex();
|
|
1694
|
+
var _paragraph = /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/;
|
|
1695
|
+
var blockText = /^[^\n]+/;
|
|
1696
|
+
var _blockLabel = /(?!\s*\])(?:\\.|[^\[\]\\])+/;
|
|
1697
|
+
var def = edit(/^ {0,3}\[(label)\]: *(?:\n *)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n *)?| *\n *)(title))? *(?:\n+|$)/).replace("label", _blockLabel).replace("title", /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex();
|
|
1698
|
+
var list = edit(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g, bullet).getRegex();
|
|
1699
|
+
var _tag = "address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul";
|
|
1700
|
+
var _comment = /<!--(?:-?>|[\s\S]*?(?:-->|$))/;
|
|
1701
|
+
var html = edit("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|<![A-Z][\\s\\S]*?(?:>\\n*|$)|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>\\n*|$)|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n *)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$)|</(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n *)+\\n|$))", "i").replace("comment", _comment).replace("tag", _tag).replace("attribute", / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex();
|
|
1702
|
+
var paragraph = edit(_paragraph).replace("hr", hr).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("|table", "").replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)]) ").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", _tag).getRegex();
|
|
1703
|
+
var blockquote = edit(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph", paragraph).getRegex();
|
|
1704
|
+
var blockNormal = {
|
|
1705
|
+
blockquote,
|
|
1706
|
+
code: blockCode,
|
|
1707
|
+
def,
|
|
1708
|
+
fences,
|
|
1709
|
+
heading,
|
|
1710
|
+
hr,
|
|
1711
|
+
html,
|
|
1712
|
+
lheading,
|
|
1713
|
+
list,
|
|
1714
|
+
newline,
|
|
1715
|
+
paragraph,
|
|
1716
|
+
table: noopTest,
|
|
1717
|
+
text: blockText
|
|
1718
|
+
};
|
|
1719
|
+
var gfmTable = edit("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr", hr).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("blockquote", " {0,3}>").replace("code", " {4}[^\\n]").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)]) ").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", _tag).getRegex();
|
|
1720
|
+
var blockGfm = {
|
|
1721
|
+
...blockNormal,
|
|
1722
|
+
table: gfmTable,
|
|
1723
|
+
paragraph: edit(_paragraph).replace("hr", hr).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("table", gfmTable).replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list", " {0,3}(?:[*+-]|1[.)]) ").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", _tag).getRegex()
|
|
1724
|
+
};
|
|
1725
|
+
var blockPedantic = {
|
|
1726
|
+
...blockNormal,
|
|
1727
|
+
html: edit(`^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)|<tag(?:"[^"]*"|'[^']*'|\\s[^'"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))`).replace("comment", _comment).replace(/tag/g, "(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),
|
|
1728
|
+
def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,
|
|
1729
|
+
heading: /^(#{1,6})(.*)(?:\n+|$)/,
|
|
1730
|
+
fences: noopTest,
|
|
1731
|
+
// fences not supported
|
|
1732
|
+
lheading: /^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/,
|
|
1733
|
+
paragraph: edit(_paragraph).replace("hr", hr).replace("heading", " *#{1,6} *[^\n]").replace("lheading", lheading).replace("|table", "").replace("blockquote", " {0,3}>").replace("|fences", "").replace("|list", "").replace("|html", "").replace("|tag", "").getRegex()
|
|
1734
|
+
};
|
|
1735
|
+
var escape = /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/;
|
|
1736
|
+
var inlineCode = /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/;
|
|
1737
|
+
var br = /^( {2,}|\\)\n(?!\s*$)/;
|
|
1738
|
+
var inlineText = /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*_]|\b_|$)|[^ ](?= {2,}\n)))/;
|
|
1739
|
+
var _punctuation = "\\p{P}\\p{S}";
|
|
1740
|
+
var punctuation = edit(/^((?![*_])[\spunctuation])/, "u").replace(/punctuation/g, _punctuation).getRegex();
|
|
1741
|
+
var blockSkip = /\[[^[\]]*?\]\([^\(\)]*?\)|`[^`]*?`|<[^<>]*?>/g;
|
|
1742
|
+
var emStrongLDelim = edit(/^(?:\*+(?:((?!\*)[punct])|[^\s*]))|^_+(?:((?!_)[punct])|([^\s_]))/, "u").replace(/punct/g, _punctuation).getRegex();
|
|
1743
|
+
var emStrongRDelimAst = edit("^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)[punct](\\*+)(?=[\\s]|$)|[^punct\\s](\\*+)(?!\\*)(?=[punct\\s]|$)|(?!\\*)[punct\\s](\\*+)(?=[^punct\\s])|[\\s](\\*+)(?!\\*)(?=[punct])|(?!\\*)[punct](\\*+)(?!\\*)(?=[punct])|[^punct\\s](\\*+)(?=[^punct\\s])", "gu").replace(/punct/g, _punctuation).getRegex();
|
|
1744
|
+
var emStrongRDelimUnd = edit("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)[punct](_+)(?=[\\s]|$)|[^punct\\s](_+)(?!_)(?=[punct\\s]|$)|(?!_)[punct\\s](_+)(?=[^punct\\s])|[\\s](_+)(?!_)(?=[punct])|(?!_)[punct](_+)(?!_)(?=[punct])", "gu").replace(/punct/g, _punctuation).getRegex();
|
|
1745
|
+
var anyPunctuation = edit(/\\([punct])/, "gu").replace(/punct/g, _punctuation).getRegex();
|
|
1746
|
+
var autolink = edit(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme", /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email", /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex();
|
|
1747
|
+
var _inlineComment = edit(_comment).replace("(?:-->|$)", "-->").getRegex();
|
|
1748
|
+
var tag = edit("^comment|^</[a-zA-Z][\\w:-]*\\s*>|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^<![a-zA-Z]+\\s[\\s\\S]*?>|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>").replace("comment", _inlineComment).replace("attribute", /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex();
|
|
1749
|
+
var _inlineLabel = /(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/;
|
|
1750
|
+
var link = edit(/^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/).replace("label", _inlineLabel).replace("href", /<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/).replace("title", /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex();
|
|
1751
|
+
var reflink = edit(/^!?\[(label)\]\[(ref)\]/).replace("label", _inlineLabel).replace("ref", _blockLabel).getRegex();
|
|
1752
|
+
var nolink = edit(/^!?\[(ref)\](?:\[\])?/).replace("ref", _blockLabel).getRegex();
|
|
1753
|
+
var reflinkSearch = edit("reflink|nolink(?!\\()", "g").replace("reflink", reflink).replace("nolink", nolink).getRegex();
|
|
1754
|
+
var inlineNormal = {
|
|
1755
|
+
_backpedal: noopTest,
|
|
1756
|
+
// only used for GFM url
|
|
1757
|
+
anyPunctuation,
|
|
1758
|
+
autolink,
|
|
1759
|
+
blockSkip,
|
|
1760
|
+
br,
|
|
1761
|
+
code: inlineCode,
|
|
1762
|
+
del: noopTest,
|
|
1763
|
+
emStrongLDelim,
|
|
1764
|
+
emStrongRDelimAst,
|
|
1765
|
+
emStrongRDelimUnd,
|
|
1766
|
+
escape,
|
|
1767
|
+
link,
|
|
1768
|
+
nolink,
|
|
1769
|
+
punctuation,
|
|
1770
|
+
reflink,
|
|
1771
|
+
reflinkSearch,
|
|
1772
|
+
tag,
|
|
1773
|
+
text: inlineText,
|
|
1774
|
+
url: noopTest
|
|
1775
|
+
};
|
|
1776
|
+
var inlinePedantic = {
|
|
1777
|
+
...inlineNormal,
|
|
1778
|
+
link: edit(/^!?\[(label)\]\((.*?)\)/).replace("label", _inlineLabel).getRegex(),
|
|
1779
|
+
reflink: edit(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label", _inlineLabel).getRegex()
|
|
1780
|
+
};
|
|
1781
|
+
var inlineGfm = {
|
|
1782
|
+
...inlineNormal,
|
|
1783
|
+
escape: edit(escape).replace("])", "~|])").getRegex(),
|
|
1784
|
+
url: edit(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/, "i").replace("email", /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(),
|
|
1785
|
+
_backpedal: /(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/,
|
|
1786
|
+
del: /^(~~?)(?=[^\s~])([\s\S]*?[^\s~])\1(?=[^~]|$)/,
|
|
1787
|
+
text: /^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\<!\[`*~_]|\b_|https?:\/\/|ftp:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)))/
|
|
1788
|
+
};
|
|
1789
|
+
var inlineBreaks = {
|
|
1790
|
+
...inlineGfm,
|
|
1791
|
+
br: edit(br).replace("{2,}", "*").getRegex(),
|
|
1792
|
+
text: edit(inlineGfm.text).replace("\\b_", "\\b_| {2,}\\n").replace(/\{2,\}/g, "*").getRegex()
|
|
1793
|
+
};
|
|
1794
|
+
var block = {
|
|
1795
|
+
normal: blockNormal,
|
|
1796
|
+
gfm: blockGfm,
|
|
1797
|
+
pedantic: blockPedantic
|
|
1798
|
+
};
|
|
1799
|
+
var inline = {
|
|
1800
|
+
normal: inlineNormal,
|
|
1801
|
+
gfm: inlineGfm,
|
|
1802
|
+
breaks: inlineBreaks,
|
|
1803
|
+
pedantic: inlinePedantic
|
|
1804
|
+
};
|
|
1805
|
+
var _Lexer = class __Lexer {
|
|
1806
|
+
tokens;
|
|
1807
|
+
options;
|
|
1808
|
+
state;
|
|
1809
|
+
tokenizer;
|
|
1810
|
+
inlineQueue;
|
|
1811
|
+
constructor(options2) {
|
|
1812
|
+
this.tokens = [];
|
|
1813
|
+
this.tokens.links = /* @__PURE__ */ Object.create(null);
|
|
1814
|
+
this.options = options2 || _defaults;
|
|
1815
|
+
this.options.tokenizer = this.options.tokenizer || new _Tokenizer();
|
|
1816
|
+
this.tokenizer = this.options.tokenizer;
|
|
1817
|
+
this.tokenizer.options = this.options;
|
|
1818
|
+
this.tokenizer.lexer = this;
|
|
1819
|
+
this.inlineQueue = [];
|
|
1820
|
+
this.state = {
|
|
1821
|
+
inLink: false,
|
|
1822
|
+
inRawBlock: false,
|
|
1823
|
+
top: true
|
|
1824
|
+
};
|
|
1825
|
+
const rules = {
|
|
1826
|
+
block: block.normal,
|
|
1827
|
+
inline: inline.normal
|
|
1828
|
+
};
|
|
1829
|
+
if (this.options.pedantic) {
|
|
1830
|
+
rules.block = block.pedantic;
|
|
1831
|
+
rules.inline = inline.pedantic;
|
|
1832
|
+
} else if (this.options.gfm) {
|
|
1833
|
+
rules.block = block.gfm;
|
|
1834
|
+
if (this.options.breaks) {
|
|
1835
|
+
rules.inline = inline.breaks;
|
|
1836
|
+
} else {
|
|
1837
|
+
rules.inline = inline.gfm;
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
this.tokenizer.rules = rules;
|
|
1841
|
+
}
|
|
1842
|
+
/**
|
|
1843
|
+
* Expose Rules
|
|
1844
|
+
*/
|
|
1845
|
+
static get rules() {
|
|
1846
|
+
return {
|
|
1847
|
+
block,
|
|
1848
|
+
inline
|
|
1849
|
+
};
|
|
1850
|
+
}
|
|
1851
|
+
/**
|
|
1852
|
+
* Static Lex Method
|
|
1853
|
+
*/
|
|
1854
|
+
static lex(src, options2) {
|
|
1855
|
+
const lexer2 = new __Lexer(options2);
|
|
1856
|
+
return lexer2.lex(src);
|
|
1857
|
+
}
|
|
1858
|
+
/**
|
|
1859
|
+
* Static Lex Inline Method
|
|
1860
|
+
*/
|
|
1861
|
+
static lexInline(src, options2) {
|
|
1862
|
+
const lexer2 = new __Lexer(options2);
|
|
1863
|
+
return lexer2.inlineTokens(src);
|
|
1864
|
+
}
|
|
1865
|
+
/**
|
|
1866
|
+
* Preprocessing
|
|
1867
|
+
*/
|
|
1868
|
+
lex(src) {
|
|
1869
|
+
src = src.replace(/\r\n|\r/g, "\n");
|
|
1870
|
+
this.blockTokens(src, this.tokens);
|
|
1871
|
+
for (let i = 0; i < this.inlineQueue.length; i++) {
|
|
1872
|
+
const next = this.inlineQueue[i];
|
|
1873
|
+
this.inlineTokens(next.src, next.tokens);
|
|
1874
|
+
}
|
|
1875
|
+
this.inlineQueue = [];
|
|
1876
|
+
return this.tokens;
|
|
1877
|
+
}
|
|
1878
|
+
blockTokens(src, tokens = []) {
|
|
1879
|
+
if (this.options.pedantic) {
|
|
1880
|
+
src = src.replace(/\t/g, " ").replace(/^ +$/gm, "");
|
|
1881
|
+
} else {
|
|
1882
|
+
src = src.replace(/^( *)(\t+)/gm, (_, leading, tabs) => {
|
|
1883
|
+
return leading + " ".repeat(tabs.length);
|
|
1884
|
+
});
|
|
1885
|
+
}
|
|
1886
|
+
let token;
|
|
1887
|
+
let lastToken;
|
|
1888
|
+
let cutSrc;
|
|
1889
|
+
let lastParagraphClipped;
|
|
1890
|
+
while (src) {
|
|
1891
|
+
if (this.options.extensions && this.options.extensions.block && this.options.extensions.block.some((extTokenizer) => {
|
|
1892
|
+
if (token = extTokenizer.call({ lexer: this }, src, tokens)) {
|
|
1893
|
+
src = src.substring(token.raw.length);
|
|
1894
|
+
tokens.push(token);
|
|
1895
|
+
return true;
|
|
1896
|
+
}
|
|
1897
|
+
return false;
|
|
1898
|
+
})) {
|
|
1899
|
+
continue;
|
|
1900
|
+
}
|
|
1901
|
+
if (token = this.tokenizer.space(src)) {
|
|
1902
|
+
src = src.substring(token.raw.length);
|
|
1903
|
+
if (token.raw.length === 1 && tokens.length > 0) {
|
|
1904
|
+
tokens[tokens.length - 1].raw += "\n";
|
|
1905
|
+
} else {
|
|
1906
|
+
tokens.push(token);
|
|
1907
|
+
}
|
|
1908
|
+
continue;
|
|
1909
|
+
}
|
|
1910
|
+
if (token = this.tokenizer.code(src)) {
|
|
1911
|
+
src = src.substring(token.raw.length);
|
|
1912
|
+
lastToken = tokens[tokens.length - 1];
|
|
1913
|
+
if (lastToken && (lastToken.type === "paragraph" || lastToken.type === "text")) {
|
|
1914
|
+
lastToken.raw += "\n" + token.raw;
|
|
1915
|
+
lastToken.text += "\n" + token.text;
|
|
1916
|
+
this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text;
|
|
1917
|
+
} else {
|
|
1918
|
+
tokens.push(token);
|
|
1919
|
+
}
|
|
1920
|
+
continue;
|
|
1921
|
+
}
|
|
1922
|
+
if (token = this.tokenizer.fences(src)) {
|
|
1923
|
+
src = src.substring(token.raw.length);
|
|
1924
|
+
tokens.push(token);
|
|
1925
|
+
continue;
|
|
1926
|
+
}
|
|
1927
|
+
if (token = this.tokenizer.heading(src)) {
|
|
1928
|
+
src = src.substring(token.raw.length);
|
|
1929
|
+
tokens.push(token);
|
|
1930
|
+
continue;
|
|
1931
|
+
}
|
|
1932
|
+
if (token = this.tokenizer.hr(src)) {
|
|
1933
|
+
src = src.substring(token.raw.length);
|
|
1934
|
+
tokens.push(token);
|
|
1935
|
+
continue;
|
|
1936
|
+
}
|
|
1937
|
+
if (token = this.tokenizer.blockquote(src)) {
|
|
1938
|
+
src = src.substring(token.raw.length);
|
|
1939
|
+
tokens.push(token);
|
|
1940
|
+
continue;
|
|
1941
|
+
}
|
|
1942
|
+
if (token = this.tokenizer.list(src)) {
|
|
1943
|
+
src = src.substring(token.raw.length);
|
|
1944
|
+
tokens.push(token);
|
|
1945
|
+
continue;
|
|
1946
|
+
}
|
|
1947
|
+
if (token = this.tokenizer.html(src)) {
|
|
1948
|
+
src = src.substring(token.raw.length);
|
|
1949
|
+
tokens.push(token);
|
|
1950
|
+
continue;
|
|
1951
|
+
}
|
|
1952
|
+
if (token = this.tokenizer.def(src)) {
|
|
1953
|
+
src = src.substring(token.raw.length);
|
|
1954
|
+
lastToken = tokens[tokens.length - 1];
|
|
1955
|
+
if (lastToken && (lastToken.type === "paragraph" || lastToken.type === "text")) {
|
|
1956
|
+
lastToken.raw += "\n" + token.raw;
|
|
1957
|
+
lastToken.text += "\n" + token.raw;
|
|
1958
|
+
this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text;
|
|
1959
|
+
} else if (!this.tokens.links[token.tag]) {
|
|
1960
|
+
this.tokens.links[token.tag] = {
|
|
1961
|
+
href: token.href,
|
|
1962
|
+
title: token.title
|
|
1963
|
+
};
|
|
1964
|
+
}
|
|
1965
|
+
continue;
|
|
1966
|
+
}
|
|
1967
|
+
if (token = this.tokenizer.table(src)) {
|
|
1968
|
+
src = src.substring(token.raw.length);
|
|
1969
|
+
tokens.push(token);
|
|
1970
|
+
continue;
|
|
1971
|
+
}
|
|
1972
|
+
if (token = this.tokenizer.lheading(src)) {
|
|
1973
|
+
src = src.substring(token.raw.length);
|
|
1974
|
+
tokens.push(token);
|
|
1975
|
+
continue;
|
|
1976
|
+
}
|
|
1977
|
+
cutSrc = src;
|
|
1978
|
+
if (this.options.extensions && this.options.extensions.startBlock) {
|
|
1979
|
+
let startIndex = Infinity;
|
|
1980
|
+
const tempSrc = src.slice(1);
|
|
1981
|
+
let tempStart;
|
|
1982
|
+
this.options.extensions.startBlock.forEach((getStartIndex) => {
|
|
1983
|
+
tempStart = getStartIndex.call({ lexer: this }, tempSrc);
|
|
1984
|
+
if (typeof tempStart === "number" && tempStart >= 0) {
|
|
1985
|
+
startIndex = Math.min(startIndex, tempStart);
|
|
1986
|
+
}
|
|
1987
|
+
});
|
|
1988
|
+
if (startIndex < Infinity && startIndex >= 0) {
|
|
1989
|
+
cutSrc = src.substring(0, startIndex + 1);
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
if (this.state.top && (token = this.tokenizer.paragraph(cutSrc))) {
|
|
1993
|
+
lastToken = tokens[tokens.length - 1];
|
|
1994
|
+
if (lastParagraphClipped && lastToken.type === "paragraph") {
|
|
1995
|
+
lastToken.raw += "\n" + token.raw;
|
|
1996
|
+
lastToken.text += "\n" + token.text;
|
|
1997
|
+
this.inlineQueue.pop();
|
|
1998
|
+
this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text;
|
|
1999
|
+
} else {
|
|
2000
|
+
tokens.push(token);
|
|
2001
|
+
}
|
|
2002
|
+
lastParagraphClipped = cutSrc.length !== src.length;
|
|
2003
|
+
src = src.substring(token.raw.length);
|
|
2004
|
+
continue;
|
|
2005
|
+
}
|
|
2006
|
+
if (token = this.tokenizer.text(src)) {
|
|
2007
|
+
src = src.substring(token.raw.length);
|
|
2008
|
+
lastToken = tokens[tokens.length - 1];
|
|
2009
|
+
if (lastToken && lastToken.type === "text") {
|
|
2010
|
+
lastToken.raw += "\n" + token.raw;
|
|
2011
|
+
lastToken.text += "\n" + token.text;
|
|
2012
|
+
this.inlineQueue.pop();
|
|
2013
|
+
this.inlineQueue[this.inlineQueue.length - 1].src = lastToken.text;
|
|
2014
|
+
} else {
|
|
2015
|
+
tokens.push(token);
|
|
2016
|
+
}
|
|
2017
|
+
continue;
|
|
2018
|
+
}
|
|
2019
|
+
if (src) {
|
|
2020
|
+
const errMsg = "Infinite loop on byte: " + src.charCodeAt(0);
|
|
2021
|
+
if (this.options.silent) {
|
|
2022
|
+
console.error(errMsg);
|
|
2023
|
+
break;
|
|
2024
|
+
} else {
|
|
2025
|
+
throw new Error(errMsg);
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
this.state.top = true;
|
|
2030
|
+
return tokens;
|
|
2031
|
+
}
|
|
2032
|
+
inline(src, tokens = []) {
|
|
2033
|
+
this.inlineQueue.push({ src, tokens });
|
|
2034
|
+
return tokens;
|
|
2035
|
+
}
|
|
2036
|
+
/**
|
|
2037
|
+
* Lexing/Compiling
|
|
2038
|
+
*/
|
|
2039
|
+
inlineTokens(src, tokens = []) {
|
|
2040
|
+
let token, lastToken, cutSrc;
|
|
2041
|
+
let maskedSrc = src;
|
|
2042
|
+
let match;
|
|
2043
|
+
let keepPrevChar, prevChar;
|
|
2044
|
+
if (this.tokens.links) {
|
|
2045
|
+
const links = Object.keys(this.tokens.links);
|
|
2046
|
+
if (links.length > 0) {
|
|
2047
|
+
while ((match = this.tokenizer.rules.inline.reflinkSearch.exec(maskedSrc)) != null) {
|
|
2048
|
+
if (links.includes(match[0].slice(match[0].lastIndexOf("[") + 1, -1))) {
|
|
2049
|
+
maskedSrc = maskedSrc.slice(0, match.index) + "[" + "a".repeat(match[0].length - 2) + "]" + maskedSrc.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex);
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
while ((match = this.tokenizer.rules.inline.blockSkip.exec(maskedSrc)) != null) {
|
|
2055
|
+
maskedSrc = maskedSrc.slice(0, match.index) + "[" + "a".repeat(match[0].length - 2) + "]" + maskedSrc.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);
|
|
2056
|
+
}
|
|
2057
|
+
while ((match = this.tokenizer.rules.inline.anyPunctuation.exec(maskedSrc)) != null) {
|
|
2058
|
+
maskedSrc = maskedSrc.slice(0, match.index) + "++" + maskedSrc.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex);
|
|
2059
|
+
}
|
|
2060
|
+
while (src) {
|
|
2061
|
+
if (!keepPrevChar) {
|
|
2062
|
+
prevChar = "";
|
|
2063
|
+
}
|
|
2064
|
+
keepPrevChar = false;
|
|
2065
|
+
if (this.options.extensions && this.options.extensions.inline && this.options.extensions.inline.some((extTokenizer) => {
|
|
2066
|
+
if (token = extTokenizer.call({ lexer: this }, src, tokens)) {
|
|
2067
|
+
src = src.substring(token.raw.length);
|
|
2068
|
+
tokens.push(token);
|
|
2069
|
+
return true;
|
|
2070
|
+
}
|
|
2071
|
+
return false;
|
|
2072
|
+
})) {
|
|
2073
|
+
continue;
|
|
2074
|
+
}
|
|
2075
|
+
if (token = this.tokenizer.escape(src)) {
|
|
2076
|
+
src = src.substring(token.raw.length);
|
|
2077
|
+
tokens.push(token);
|
|
2078
|
+
continue;
|
|
2079
|
+
}
|
|
2080
|
+
if (token = this.tokenizer.tag(src)) {
|
|
2081
|
+
src = src.substring(token.raw.length);
|
|
2082
|
+
lastToken = tokens[tokens.length - 1];
|
|
2083
|
+
if (lastToken && token.type === "text" && lastToken.type === "text") {
|
|
2084
|
+
lastToken.raw += token.raw;
|
|
2085
|
+
lastToken.text += token.text;
|
|
2086
|
+
} else {
|
|
2087
|
+
tokens.push(token);
|
|
2088
|
+
}
|
|
2089
|
+
continue;
|
|
2090
|
+
}
|
|
2091
|
+
if (token = this.tokenizer.link(src)) {
|
|
2092
|
+
src = src.substring(token.raw.length);
|
|
2093
|
+
tokens.push(token);
|
|
2094
|
+
continue;
|
|
2095
|
+
}
|
|
2096
|
+
if (token = this.tokenizer.reflink(src, this.tokens.links)) {
|
|
2097
|
+
src = src.substring(token.raw.length);
|
|
2098
|
+
lastToken = tokens[tokens.length - 1];
|
|
2099
|
+
if (lastToken && token.type === "text" && lastToken.type === "text") {
|
|
2100
|
+
lastToken.raw += token.raw;
|
|
2101
|
+
lastToken.text += token.text;
|
|
2102
|
+
} else {
|
|
2103
|
+
tokens.push(token);
|
|
2104
|
+
}
|
|
2105
|
+
continue;
|
|
2106
|
+
}
|
|
2107
|
+
if (token = this.tokenizer.emStrong(src, maskedSrc, prevChar)) {
|
|
2108
|
+
src = src.substring(token.raw.length);
|
|
2109
|
+
tokens.push(token);
|
|
2110
|
+
continue;
|
|
2111
|
+
}
|
|
2112
|
+
if (token = this.tokenizer.codespan(src)) {
|
|
2113
|
+
src = src.substring(token.raw.length);
|
|
2114
|
+
tokens.push(token);
|
|
2115
|
+
continue;
|
|
2116
|
+
}
|
|
2117
|
+
if (token = this.tokenizer.br(src)) {
|
|
2118
|
+
src = src.substring(token.raw.length);
|
|
2119
|
+
tokens.push(token);
|
|
2120
|
+
continue;
|
|
2121
|
+
}
|
|
2122
|
+
if (token = this.tokenizer.del(src)) {
|
|
2123
|
+
src = src.substring(token.raw.length);
|
|
2124
|
+
tokens.push(token);
|
|
2125
|
+
continue;
|
|
2126
|
+
}
|
|
2127
|
+
if (token = this.tokenizer.autolink(src)) {
|
|
2128
|
+
src = src.substring(token.raw.length);
|
|
2129
|
+
tokens.push(token);
|
|
2130
|
+
continue;
|
|
2131
|
+
}
|
|
2132
|
+
if (!this.state.inLink && (token = this.tokenizer.url(src))) {
|
|
2133
|
+
src = src.substring(token.raw.length);
|
|
2134
|
+
tokens.push(token);
|
|
2135
|
+
continue;
|
|
2136
|
+
}
|
|
2137
|
+
cutSrc = src;
|
|
2138
|
+
if (this.options.extensions && this.options.extensions.startInline) {
|
|
2139
|
+
let startIndex = Infinity;
|
|
2140
|
+
const tempSrc = src.slice(1);
|
|
2141
|
+
let tempStart;
|
|
2142
|
+
this.options.extensions.startInline.forEach((getStartIndex) => {
|
|
2143
|
+
tempStart = getStartIndex.call({ lexer: this }, tempSrc);
|
|
2144
|
+
if (typeof tempStart === "number" && tempStart >= 0) {
|
|
2145
|
+
startIndex = Math.min(startIndex, tempStart);
|
|
2146
|
+
}
|
|
2147
|
+
});
|
|
2148
|
+
if (startIndex < Infinity && startIndex >= 0) {
|
|
2149
|
+
cutSrc = src.substring(0, startIndex + 1);
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
if (token = this.tokenizer.inlineText(cutSrc)) {
|
|
2153
|
+
src = src.substring(token.raw.length);
|
|
2154
|
+
if (token.raw.slice(-1) !== "_") {
|
|
2155
|
+
prevChar = token.raw.slice(-1);
|
|
2156
|
+
}
|
|
2157
|
+
keepPrevChar = true;
|
|
2158
|
+
lastToken = tokens[tokens.length - 1];
|
|
2159
|
+
if (lastToken && lastToken.type === "text") {
|
|
2160
|
+
lastToken.raw += token.raw;
|
|
2161
|
+
lastToken.text += token.text;
|
|
2162
|
+
} else {
|
|
2163
|
+
tokens.push(token);
|
|
2164
|
+
}
|
|
2165
|
+
continue;
|
|
2166
|
+
}
|
|
2167
|
+
if (src) {
|
|
2168
|
+
const errMsg = "Infinite loop on byte: " + src.charCodeAt(0);
|
|
2169
|
+
if (this.options.silent) {
|
|
2170
|
+
console.error(errMsg);
|
|
2171
|
+
break;
|
|
2172
|
+
} else {
|
|
2173
|
+
throw new Error(errMsg);
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
return tokens;
|
|
2178
|
+
}
|
|
2179
|
+
};
|
|
2180
|
+
var _Renderer = class {
|
|
2181
|
+
options;
|
|
2182
|
+
constructor(options2) {
|
|
2183
|
+
this.options = options2 || _defaults;
|
|
2184
|
+
}
|
|
2185
|
+
code(code, infostring, escaped) {
|
|
2186
|
+
const lang = (infostring || "").match(/^\S*/)?.[0];
|
|
2187
|
+
code = code.replace(/\n$/, "") + "\n";
|
|
2188
|
+
if (!lang) {
|
|
2189
|
+
return "<pre><code>" + (escaped ? code : escape$1(code, true)) + "</code></pre>\n";
|
|
2190
|
+
}
|
|
2191
|
+
return '<pre><code class="language-' + escape$1(lang) + '">' + (escaped ? code : escape$1(code, true)) + "</code></pre>\n";
|
|
2192
|
+
}
|
|
2193
|
+
blockquote(quote) {
|
|
2194
|
+
return `<blockquote>
|
|
2195
|
+
${quote}</blockquote>
|
|
2196
|
+
`;
|
|
2197
|
+
}
|
|
2198
|
+
html(html2, block2) {
|
|
2199
|
+
return html2;
|
|
2200
|
+
}
|
|
2201
|
+
heading(text, level, raw) {
|
|
2202
|
+
return `<h${level}>${text}</h${level}>
|
|
2203
|
+
`;
|
|
2204
|
+
}
|
|
2205
|
+
hr() {
|
|
2206
|
+
return "<hr>\n";
|
|
2207
|
+
}
|
|
2208
|
+
list(body, ordered, start) {
|
|
2209
|
+
const type = ordered ? "ol" : "ul";
|
|
2210
|
+
const startatt = ordered && start !== 1 ? ' start="' + start + '"' : "";
|
|
2211
|
+
return "<" + type + startatt + ">\n" + body + "</" + type + ">\n";
|
|
2212
|
+
}
|
|
2213
|
+
listitem(text, task, checked) {
|
|
2214
|
+
return `<li>${text}</li>
|
|
2215
|
+
`;
|
|
2216
|
+
}
|
|
2217
|
+
checkbox(checked) {
|
|
2218
|
+
return "<input " + (checked ? 'checked="" ' : "") + 'disabled="" type="checkbox">';
|
|
2219
|
+
}
|
|
2220
|
+
paragraph(text) {
|
|
2221
|
+
return `<p>${text}</p>
|
|
2222
|
+
`;
|
|
2223
|
+
}
|
|
2224
|
+
table(header, body) {
|
|
2225
|
+
if (body)
|
|
2226
|
+
body = `<tbody>${body}</tbody>`;
|
|
2227
|
+
return "<table>\n<thead>\n" + header + "</thead>\n" + body + "</table>\n";
|
|
2228
|
+
}
|
|
2229
|
+
tablerow(content) {
|
|
2230
|
+
return `<tr>
|
|
2231
|
+
${content}</tr>
|
|
2232
|
+
`;
|
|
2233
|
+
}
|
|
2234
|
+
tablecell(content, flags) {
|
|
2235
|
+
const type = flags.header ? "th" : "td";
|
|
2236
|
+
const tag2 = flags.align ? `<${type} align="${flags.align}">` : `<${type}>`;
|
|
2237
|
+
return tag2 + content + `</${type}>
|
|
2238
|
+
`;
|
|
2239
|
+
}
|
|
2240
|
+
/**
|
|
2241
|
+
* span level renderer
|
|
2242
|
+
*/
|
|
2243
|
+
strong(text) {
|
|
2244
|
+
return `<strong>${text}</strong>`;
|
|
2245
|
+
}
|
|
2246
|
+
em(text) {
|
|
2247
|
+
return `<em>${text}</em>`;
|
|
2248
|
+
}
|
|
2249
|
+
codespan(text) {
|
|
2250
|
+
return `<code>${text}</code>`;
|
|
2251
|
+
}
|
|
2252
|
+
br() {
|
|
2253
|
+
return "<br>";
|
|
2254
|
+
}
|
|
2255
|
+
del(text) {
|
|
2256
|
+
return `<del>${text}</del>`;
|
|
2257
|
+
}
|
|
2258
|
+
link(href, title, text) {
|
|
2259
|
+
const cleanHref = cleanUrl(href);
|
|
2260
|
+
if (cleanHref === null) {
|
|
2261
|
+
return text;
|
|
2262
|
+
}
|
|
2263
|
+
href = cleanHref;
|
|
2264
|
+
let out = '<a href="' + href + '"';
|
|
2265
|
+
if (title) {
|
|
2266
|
+
out += ' title="' + title + '"';
|
|
2267
|
+
}
|
|
2268
|
+
out += ">" + text + "</a>";
|
|
2269
|
+
return out;
|
|
2270
|
+
}
|
|
2271
|
+
image(href, title, text) {
|
|
2272
|
+
const cleanHref = cleanUrl(href);
|
|
2273
|
+
if (cleanHref === null) {
|
|
2274
|
+
return text;
|
|
2275
|
+
}
|
|
2276
|
+
href = cleanHref;
|
|
2277
|
+
let out = `<img src="${href}" alt="${text}"`;
|
|
2278
|
+
if (title) {
|
|
2279
|
+
out += ` title="${title}"`;
|
|
2280
|
+
}
|
|
2281
|
+
out += ">";
|
|
2282
|
+
return out;
|
|
2283
|
+
}
|
|
2284
|
+
text(text) {
|
|
2285
|
+
return text;
|
|
2286
|
+
}
|
|
2287
|
+
};
|
|
2288
|
+
var _TextRenderer = class {
|
|
2289
|
+
// no need for block level renderers
|
|
2290
|
+
strong(text) {
|
|
2291
|
+
return text;
|
|
2292
|
+
}
|
|
2293
|
+
em(text) {
|
|
2294
|
+
return text;
|
|
2295
|
+
}
|
|
2296
|
+
codespan(text) {
|
|
2297
|
+
return text;
|
|
2298
|
+
}
|
|
2299
|
+
del(text) {
|
|
2300
|
+
return text;
|
|
2301
|
+
}
|
|
2302
|
+
html(text) {
|
|
2303
|
+
return text;
|
|
2304
|
+
}
|
|
2305
|
+
text(text) {
|
|
2306
|
+
return text;
|
|
2307
|
+
}
|
|
2308
|
+
link(href, title, text) {
|
|
2309
|
+
return "" + text;
|
|
2310
|
+
}
|
|
2311
|
+
image(href, title, text) {
|
|
2312
|
+
return "" + text;
|
|
2313
|
+
}
|
|
2314
|
+
br() {
|
|
2315
|
+
return "";
|
|
2316
|
+
}
|
|
2317
|
+
};
|
|
2318
|
+
var _Parser = class __Parser {
|
|
2319
|
+
options;
|
|
2320
|
+
renderer;
|
|
2321
|
+
textRenderer;
|
|
2322
|
+
constructor(options2) {
|
|
2323
|
+
this.options = options2 || _defaults;
|
|
2324
|
+
this.options.renderer = this.options.renderer || new _Renderer();
|
|
2325
|
+
this.renderer = this.options.renderer;
|
|
2326
|
+
this.renderer.options = this.options;
|
|
2327
|
+
this.textRenderer = new _TextRenderer();
|
|
2328
|
+
}
|
|
2329
|
+
/**
|
|
2330
|
+
* Static Parse Method
|
|
2331
|
+
*/
|
|
2332
|
+
static parse(tokens, options2) {
|
|
2333
|
+
const parser2 = new __Parser(options2);
|
|
2334
|
+
return parser2.parse(tokens);
|
|
2335
|
+
}
|
|
2336
|
+
/**
|
|
2337
|
+
* Static Parse Inline Method
|
|
2338
|
+
*/
|
|
2339
|
+
static parseInline(tokens, options2) {
|
|
2340
|
+
const parser2 = new __Parser(options2);
|
|
2341
|
+
return parser2.parseInline(tokens);
|
|
2342
|
+
}
|
|
2343
|
+
/**
|
|
2344
|
+
* Parse Loop
|
|
2345
|
+
*/
|
|
2346
|
+
parse(tokens, top = true) {
|
|
2347
|
+
let out = "";
|
|
2348
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
2349
|
+
const token = tokens[i];
|
|
2350
|
+
if (this.options.extensions && this.options.extensions.renderers && this.options.extensions.renderers[token.type]) {
|
|
2351
|
+
const genericToken = token;
|
|
2352
|
+
const ret = this.options.extensions.renderers[genericToken.type].call({ parser: this }, genericToken);
|
|
2353
|
+
if (ret !== false || !["space", "hr", "heading", "code", "table", "blockquote", "list", "html", "paragraph", "text"].includes(genericToken.type)) {
|
|
2354
|
+
out += ret || "";
|
|
2355
|
+
continue;
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
switch (token.type) {
|
|
2359
|
+
case "space": {
|
|
2360
|
+
continue;
|
|
2361
|
+
}
|
|
2362
|
+
case "hr": {
|
|
2363
|
+
out += this.renderer.hr();
|
|
2364
|
+
continue;
|
|
2365
|
+
}
|
|
2366
|
+
case "heading": {
|
|
2367
|
+
const headingToken = token;
|
|
2368
|
+
out += this.renderer.heading(this.parseInline(headingToken.tokens), headingToken.depth, unescape(this.parseInline(headingToken.tokens, this.textRenderer)));
|
|
2369
|
+
continue;
|
|
2370
|
+
}
|
|
2371
|
+
case "code": {
|
|
2372
|
+
const codeToken = token;
|
|
2373
|
+
out += this.renderer.code(codeToken.text, codeToken.lang, !!codeToken.escaped);
|
|
2374
|
+
continue;
|
|
2375
|
+
}
|
|
2376
|
+
case "table": {
|
|
2377
|
+
const tableToken = token;
|
|
2378
|
+
let header = "";
|
|
2379
|
+
let cell = "";
|
|
2380
|
+
for (let j = 0; j < tableToken.header.length; j++) {
|
|
2381
|
+
cell += this.renderer.tablecell(this.parseInline(tableToken.header[j].tokens), { header: true, align: tableToken.align[j] });
|
|
2382
|
+
}
|
|
2383
|
+
header += this.renderer.tablerow(cell);
|
|
2384
|
+
let body = "";
|
|
2385
|
+
for (let j = 0; j < tableToken.rows.length; j++) {
|
|
2386
|
+
const row = tableToken.rows[j];
|
|
2387
|
+
cell = "";
|
|
2388
|
+
for (let k = 0; k < row.length; k++) {
|
|
2389
|
+
cell += this.renderer.tablecell(this.parseInline(row[k].tokens), { header: false, align: tableToken.align[k] });
|
|
2390
|
+
}
|
|
2391
|
+
body += this.renderer.tablerow(cell);
|
|
2392
|
+
}
|
|
2393
|
+
out += this.renderer.table(header, body);
|
|
2394
|
+
continue;
|
|
2395
|
+
}
|
|
2396
|
+
case "blockquote": {
|
|
2397
|
+
const blockquoteToken = token;
|
|
2398
|
+
const body = this.parse(blockquoteToken.tokens);
|
|
2399
|
+
out += this.renderer.blockquote(body);
|
|
2400
|
+
continue;
|
|
2401
|
+
}
|
|
2402
|
+
case "list": {
|
|
2403
|
+
const listToken = token;
|
|
2404
|
+
const ordered = listToken.ordered;
|
|
2405
|
+
const start = listToken.start;
|
|
2406
|
+
const loose = listToken.loose;
|
|
2407
|
+
let body = "";
|
|
2408
|
+
for (let j = 0; j < listToken.items.length; j++) {
|
|
2409
|
+
const item = listToken.items[j];
|
|
2410
|
+
const checked = item.checked;
|
|
2411
|
+
const task = item.task;
|
|
2412
|
+
let itemBody = "";
|
|
2413
|
+
if (item.task) {
|
|
2414
|
+
const checkbox = this.renderer.checkbox(!!checked);
|
|
2415
|
+
if (loose) {
|
|
2416
|
+
if (item.tokens.length > 0 && item.tokens[0].type === "paragraph") {
|
|
2417
|
+
item.tokens[0].text = checkbox + " " + item.tokens[0].text;
|
|
2418
|
+
if (item.tokens[0].tokens && item.tokens[0].tokens.length > 0 && item.tokens[0].tokens[0].type === "text") {
|
|
2419
|
+
item.tokens[0].tokens[0].text = checkbox + " " + item.tokens[0].tokens[0].text;
|
|
2420
|
+
}
|
|
2421
|
+
} else {
|
|
2422
|
+
item.tokens.unshift({
|
|
2423
|
+
type: "text",
|
|
2424
|
+
text: checkbox + " "
|
|
2425
|
+
});
|
|
2426
|
+
}
|
|
2427
|
+
} else {
|
|
2428
|
+
itemBody += checkbox + " ";
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
itemBody += this.parse(item.tokens, loose);
|
|
2432
|
+
body += this.renderer.listitem(itemBody, task, !!checked);
|
|
2433
|
+
}
|
|
2434
|
+
out += this.renderer.list(body, ordered, start);
|
|
2435
|
+
continue;
|
|
2436
|
+
}
|
|
2437
|
+
case "html": {
|
|
2438
|
+
const htmlToken = token;
|
|
2439
|
+
out += this.renderer.html(htmlToken.text, htmlToken.block);
|
|
2440
|
+
continue;
|
|
2441
|
+
}
|
|
2442
|
+
case "paragraph": {
|
|
2443
|
+
const paragraphToken = token;
|
|
2444
|
+
out += this.renderer.paragraph(this.parseInline(paragraphToken.tokens));
|
|
2445
|
+
continue;
|
|
2446
|
+
}
|
|
2447
|
+
case "text": {
|
|
2448
|
+
let textToken = token;
|
|
2449
|
+
let body = textToken.tokens ? this.parseInline(textToken.tokens) : textToken.text;
|
|
2450
|
+
while (i + 1 < tokens.length && tokens[i + 1].type === "text") {
|
|
2451
|
+
textToken = tokens[++i];
|
|
2452
|
+
body += "\n" + (textToken.tokens ? this.parseInline(textToken.tokens) : textToken.text);
|
|
2453
|
+
}
|
|
2454
|
+
out += top ? this.renderer.paragraph(body) : body;
|
|
2455
|
+
continue;
|
|
2456
|
+
}
|
|
2457
|
+
default: {
|
|
2458
|
+
const errMsg = 'Token with "' + token.type + '" type was not found.';
|
|
2459
|
+
if (this.options.silent) {
|
|
2460
|
+
console.error(errMsg);
|
|
2461
|
+
return "";
|
|
2462
|
+
} else {
|
|
2463
|
+
throw new Error(errMsg);
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
return out;
|
|
2469
|
+
}
|
|
2470
|
+
/**
|
|
2471
|
+
* Parse Inline Tokens
|
|
2472
|
+
*/
|
|
2473
|
+
parseInline(tokens, renderer) {
|
|
2474
|
+
renderer = renderer || this.renderer;
|
|
2475
|
+
let out = "";
|
|
2476
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
2477
|
+
const token = tokens[i];
|
|
2478
|
+
if (this.options.extensions && this.options.extensions.renderers && this.options.extensions.renderers[token.type]) {
|
|
2479
|
+
const ret = this.options.extensions.renderers[token.type].call({ parser: this }, token);
|
|
2480
|
+
if (ret !== false || !["escape", "html", "link", "image", "strong", "em", "codespan", "br", "del", "text"].includes(token.type)) {
|
|
2481
|
+
out += ret || "";
|
|
2482
|
+
continue;
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
switch (token.type) {
|
|
2486
|
+
case "escape": {
|
|
2487
|
+
const escapeToken = token;
|
|
2488
|
+
out += renderer.text(escapeToken.text);
|
|
2489
|
+
break;
|
|
2490
|
+
}
|
|
2491
|
+
case "html": {
|
|
2492
|
+
const tagToken = token;
|
|
2493
|
+
out += renderer.html(tagToken.text);
|
|
2494
|
+
break;
|
|
2495
|
+
}
|
|
2496
|
+
case "link": {
|
|
2497
|
+
const linkToken = token;
|
|
2498
|
+
out += renderer.link(linkToken.href, linkToken.title, this.parseInline(linkToken.tokens, renderer));
|
|
2499
|
+
break;
|
|
2500
|
+
}
|
|
2501
|
+
case "image": {
|
|
2502
|
+
const imageToken = token;
|
|
2503
|
+
out += renderer.image(imageToken.href, imageToken.title, imageToken.text);
|
|
2504
|
+
break;
|
|
2505
|
+
}
|
|
2506
|
+
case "strong": {
|
|
2507
|
+
const strongToken = token;
|
|
2508
|
+
out += renderer.strong(this.parseInline(strongToken.tokens, renderer));
|
|
2509
|
+
break;
|
|
2510
|
+
}
|
|
2511
|
+
case "em": {
|
|
2512
|
+
const emToken = token;
|
|
2513
|
+
out += renderer.em(this.parseInline(emToken.tokens, renderer));
|
|
2514
|
+
break;
|
|
2515
|
+
}
|
|
2516
|
+
case "codespan": {
|
|
2517
|
+
const codespanToken = token;
|
|
2518
|
+
out += renderer.codespan(codespanToken.text);
|
|
2519
|
+
break;
|
|
2520
|
+
}
|
|
2521
|
+
case "br": {
|
|
2522
|
+
out += renderer.br();
|
|
2523
|
+
break;
|
|
2524
|
+
}
|
|
2525
|
+
case "del": {
|
|
2526
|
+
const delToken = token;
|
|
2527
|
+
out += renderer.del(this.parseInline(delToken.tokens, renderer));
|
|
2528
|
+
break;
|
|
2529
|
+
}
|
|
2530
|
+
case "text": {
|
|
2531
|
+
const textToken = token;
|
|
2532
|
+
out += renderer.text(textToken.text);
|
|
2533
|
+
break;
|
|
2534
|
+
}
|
|
2535
|
+
default: {
|
|
2536
|
+
const errMsg = 'Token with "' + token.type + '" type was not found.';
|
|
2537
|
+
if (this.options.silent) {
|
|
2538
|
+
console.error(errMsg);
|
|
2539
|
+
return "";
|
|
2540
|
+
} else {
|
|
2541
|
+
throw new Error(errMsg);
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
return out;
|
|
2547
|
+
}
|
|
2548
|
+
};
|
|
2549
|
+
var _Hooks = class {
|
|
2550
|
+
options;
|
|
2551
|
+
constructor(options2) {
|
|
2552
|
+
this.options = options2 || _defaults;
|
|
2553
|
+
}
|
|
2554
|
+
static passThroughHooks = /* @__PURE__ */ new Set([
|
|
2555
|
+
"preprocess",
|
|
2556
|
+
"postprocess",
|
|
2557
|
+
"processAllTokens"
|
|
2558
|
+
]);
|
|
2559
|
+
/**
|
|
2560
|
+
* Process markdown before marked
|
|
2561
|
+
*/
|
|
2562
|
+
preprocess(markdown) {
|
|
2563
|
+
return markdown;
|
|
2564
|
+
}
|
|
2565
|
+
/**
|
|
2566
|
+
* Process HTML after marked is finished
|
|
2567
|
+
*/
|
|
2568
|
+
postprocess(html2) {
|
|
2569
|
+
return html2;
|
|
2570
|
+
}
|
|
2571
|
+
/**
|
|
2572
|
+
* Process all tokens before walk tokens
|
|
2573
|
+
*/
|
|
2574
|
+
processAllTokens(tokens) {
|
|
2575
|
+
return tokens;
|
|
2576
|
+
}
|
|
2577
|
+
};
|
|
2578
|
+
var Marked = class {
|
|
2579
|
+
defaults = _getDefaults();
|
|
2580
|
+
options = this.setOptions;
|
|
2581
|
+
parse = this.#parseMarkdown(_Lexer.lex, _Parser.parse);
|
|
2582
|
+
parseInline = this.#parseMarkdown(_Lexer.lexInline, _Parser.parseInline);
|
|
2583
|
+
Parser = _Parser;
|
|
2584
|
+
Renderer = _Renderer;
|
|
2585
|
+
TextRenderer = _TextRenderer;
|
|
2586
|
+
Lexer = _Lexer;
|
|
2587
|
+
Tokenizer = _Tokenizer;
|
|
2588
|
+
Hooks = _Hooks;
|
|
2589
|
+
constructor(...args) {
|
|
2590
|
+
this.use(...args);
|
|
2591
|
+
}
|
|
2592
|
+
/**
|
|
2593
|
+
* Run callback for every token
|
|
2594
|
+
*/
|
|
2595
|
+
walkTokens(tokens, callback) {
|
|
2596
|
+
let values = [];
|
|
2597
|
+
for (const token of tokens) {
|
|
2598
|
+
values = values.concat(callback.call(this, token));
|
|
2599
|
+
switch (token.type) {
|
|
2600
|
+
case "table": {
|
|
2601
|
+
const tableToken = token;
|
|
2602
|
+
for (const cell of tableToken.header) {
|
|
2603
|
+
values = values.concat(this.walkTokens(cell.tokens, callback));
|
|
2604
|
+
}
|
|
2605
|
+
for (const row of tableToken.rows) {
|
|
2606
|
+
for (const cell of row) {
|
|
2607
|
+
values = values.concat(this.walkTokens(cell.tokens, callback));
|
|
2608
|
+
}
|
|
2609
|
+
}
|
|
2610
|
+
break;
|
|
2611
|
+
}
|
|
2612
|
+
case "list": {
|
|
2613
|
+
const listToken = token;
|
|
2614
|
+
values = values.concat(this.walkTokens(listToken.items, callback));
|
|
2615
|
+
break;
|
|
2616
|
+
}
|
|
2617
|
+
default: {
|
|
2618
|
+
const genericToken = token;
|
|
2619
|
+
if (this.defaults.extensions?.childTokens?.[genericToken.type]) {
|
|
2620
|
+
this.defaults.extensions.childTokens[genericToken.type].forEach((childTokens) => {
|
|
2621
|
+
const tokens2 = genericToken[childTokens].flat(Infinity);
|
|
2622
|
+
values = values.concat(this.walkTokens(tokens2, callback));
|
|
2623
|
+
});
|
|
2624
|
+
} else if (genericToken.tokens) {
|
|
2625
|
+
values = values.concat(this.walkTokens(genericToken.tokens, callback));
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
return values;
|
|
2631
|
+
}
|
|
2632
|
+
use(...args) {
|
|
2633
|
+
const extensions = this.defaults.extensions || { renderers: {}, childTokens: {} };
|
|
2634
|
+
args.forEach((pack) => {
|
|
2635
|
+
const opts = { ...pack };
|
|
2636
|
+
opts.async = this.defaults.async || opts.async || false;
|
|
2637
|
+
if (pack.extensions) {
|
|
2638
|
+
pack.extensions.forEach((ext) => {
|
|
2639
|
+
if (!ext.name) {
|
|
2640
|
+
throw new Error("extension name required");
|
|
2641
|
+
}
|
|
2642
|
+
if ("renderer" in ext) {
|
|
2643
|
+
const prevRenderer = extensions.renderers[ext.name];
|
|
2644
|
+
if (prevRenderer) {
|
|
2645
|
+
extensions.renderers[ext.name] = function(...args2) {
|
|
2646
|
+
let ret = ext.renderer.apply(this, args2);
|
|
2647
|
+
if (ret === false) {
|
|
2648
|
+
ret = prevRenderer.apply(this, args2);
|
|
2649
|
+
}
|
|
2650
|
+
return ret;
|
|
2651
|
+
};
|
|
2652
|
+
} else {
|
|
2653
|
+
extensions.renderers[ext.name] = ext.renderer;
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
if ("tokenizer" in ext) {
|
|
2657
|
+
if (!ext.level || ext.level !== "block" && ext.level !== "inline") {
|
|
2658
|
+
throw new Error("extension level must be 'block' or 'inline'");
|
|
2659
|
+
}
|
|
2660
|
+
const extLevel = extensions[ext.level];
|
|
2661
|
+
if (extLevel) {
|
|
2662
|
+
extLevel.unshift(ext.tokenizer);
|
|
2663
|
+
} else {
|
|
2664
|
+
extensions[ext.level] = [ext.tokenizer];
|
|
2665
|
+
}
|
|
2666
|
+
if (ext.start) {
|
|
2667
|
+
if (ext.level === "block") {
|
|
2668
|
+
if (extensions.startBlock) {
|
|
2669
|
+
extensions.startBlock.push(ext.start);
|
|
2670
|
+
} else {
|
|
2671
|
+
extensions.startBlock = [ext.start];
|
|
2672
|
+
}
|
|
2673
|
+
} else if (ext.level === "inline") {
|
|
2674
|
+
if (extensions.startInline) {
|
|
2675
|
+
extensions.startInline.push(ext.start);
|
|
2676
|
+
} else {
|
|
2677
|
+
extensions.startInline = [ext.start];
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
if ("childTokens" in ext && ext.childTokens) {
|
|
2683
|
+
extensions.childTokens[ext.name] = ext.childTokens;
|
|
2684
|
+
}
|
|
2685
|
+
});
|
|
2686
|
+
opts.extensions = extensions;
|
|
2687
|
+
}
|
|
2688
|
+
if (pack.renderer) {
|
|
2689
|
+
const renderer = this.defaults.renderer || new _Renderer(this.defaults);
|
|
2690
|
+
for (const prop in pack.renderer) {
|
|
2691
|
+
if (!(prop in renderer)) {
|
|
2692
|
+
throw new Error(`renderer '${prop}' does not exist`);
|
|
2693
|
+
}
|
|
2694
|
+
if (prop === "options") {
|
|
2695
|
+
continue;
|
|
2696
|
+
}
|
|
2697
|
+
const rendererProp = prop;
|
|
2698
|
+
const rendererFunc = pack.renderer[rendererProp];
|
|
2699
|
+
const prevRenderer = renderer[rendererProp];
|
|
2700
|
+
renderer[rendererProp] = (...args2) => {
|
|
2701
|
+
let ret = rendererFunc.apply(renderer, args2);
|
|
2702
|
+
if (ret === false) {
|
|
2703
|
+
ret = prevRenderer.apply(renderer, args2);
|
|
2704
|
+
}
|
|
2705
|
+
return ret || "";
|
|
2706
|
+
};
|
|
2707
|
+
}
|
|
2708
|
+
opts.renderer = renderer;
|
|
2709
|
+
}
|
|
2710
|
+
if (pack.tokenizer) {
|
|
2711
|
+
const tokenizer = this.defaults.tokenizer || new _Tokenizer(this.defaults);
|
|
2712
|
+
for (const prop in pack.tokenizer) {
|
|
2713
|
+
if (!(prop in tokenizer)) {
|
|
2714
|
+
throw new Error(`tokenizer '${prop}' does not exist`);
|
|
2715
|
+
}
|
|
2716
|
+
if (["options", "rules", "lexer"].includes(prop)) {
|
|
2717
|
+
continue;
|
|
2718
|
+
}
|
|
2719
|
+
const tokenizerProp = prop;
|
|
2720
|
+
const tokenizerFunc = pack.tokenizer[tokenizerProp];
|
|
2721
|
+
const prevTokenizer = tokenizer[tokenizerProp];
|
|
2722
|
+
tokenizer[tokenizerProp] = (...args2) => {
|
|
2723
|
+
let ret = tokenizerFunc.apply(tokenizer, args2);
|
|
2724
|
+
if (ret === false) {
|
|
2725
|
+
ret = prevTokenizer.apply(tokenizer, args2);
|
|
2726
|
+
}
|
|
2727
|
+
return ret;
|
|
2728
|
+
};
|
|
2729
|
+
}
|
|
2730
|
+
opts.tokenizer = tokenizer;
|
|
2731
|
+
}
|
|
2732
|
+
if (pack.hooks) {
|
|
2733
|
+
const hooks = this.defaults.hooks || new _Hooks();
|
|
2734
|
+
for (const prop in pack.hooks) {
|
|
2735
|
+
if (!(prop in hooks)) {
|
|
2736
|
+
throw new Error(`hook '${prop}' does not exist`);
|
|
2737
|
+
}
|
|
2738
|
+
if (prop === "options") {
|
|
2739
|
+
continue;
|
|
2740
|
+
}
|
|
2741
|
+
const hooksProp = prop;
|
|
2742
|
+
const hooksFunc = pack.hooks[hooksProp];
|
|
2743
|
+
const prevHook = hooks[hooksProp];
|
|
2744
|
+
if (_Hooks.passThroughHooks.has(prop)) {
|
|
2745
|
+
hooks[hooksProp] = (arg) => {
|
|
2746
|
+
if (this.defaults.async) {
|
|
2747
|
+
return Promise.resolve(hooksFunc.call(hooks, arg)).then((ret2) => {
|
|
2748
|
+
return prevHook.call(hooks, ret2);
|
|
2749
|
+
});
|
|
2750
|
+
}
|
|
2751
|
+
const ret = hooksFunc.call(hooks, arg);
|
|
2752
|
+
return prevHook.call(hooks, ret);
|
|
2753
|
+
};
|
|
2754
|
+
} else {
|
|
2755
|
+
hooks[hooksProp] = (...args2) => {
|
|
2756
|
+
let ret = hooksFunc.apply(hooks, args2);
|
|
2757
|
+
if (ret === false) {
|
|
2758
|
+
ret = prevHook.apply(hooks, args2);
|
|
2759
|
+
}
|
|
2760
|
+
return ret;
|
|
2761
|
+
};
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
opts.hooks = hooks;
|
|
2765
|
+
}
|
|
2766
|
+
if (pack.walkTokens) {
|
|
2767
|
+
const walkTokens2 = this.defaults.walkTokens;
|
|
2768
|
+
const packWalktokens = pack.walkTokens;
|
|
2769
|
+
opts.walkTokens = function(token) {
|
|
2770
|
+
let values = [];
|
|
2771
|
+
values.push(packWalktokens.call(this, token));
|
|
2772
|
+
if (walkTokens2) {
|
|
2773
|
+
values = values.concat(walkTokens2.call(this, token));
|
|
2774
|
+
}
|
|
2775
|
+
return values;
|
|
2776
|
+
};
|
|
2777
|
+
}
|
|
2778
|
+
this.defaults = { ...this.defaults, ...opts };
|
|
2779
|
+
});
|
|
2780
|
+
return this;
|
|
2781
|
+
}
|
|
2782
|
+
setOptions(opt) {
|
|
2783
|
+
this.defaults = { ...this.defaults, ...opt };
|
|
2784
|
+
return this;
|
|
2785
|
+
}
|
|
2786
|
+
lexer(src, options2) {
|
|
2787
|
+
return _Lexer.lex(src, options2 ?? this.defaults);
|
|
2788
|
+
}
|
|
2789
|
+
parser(tokens, options2) {
|
|
2790
|
+
return _Parser.parse(tokens, options2 ?? this.defaults);
|
|
2791
|
+
}
|
|
2792
|
+
#parseMarkdown(lexer2, parser2) {
|
|
2793
|
+
return (src, options2) => {
|
|
2794
|
+
const origOpt = { ...options2 };
|
|
2795
|
+
const opt = { ...this.defaults, ...origOpt };
|
|
2796
|
+
if (this.defaults.async === true && origOpt.async === false) {
|
|
2797
|
+
if (!opt.silent) {
|
|
2798
|
+
console.warn("marked(): The async option was set to true by an extension. The async: false option sent to parse will be ignored.");
|
|
2799
|
+
}
|
|
2800
|
+
opt.async = true;
|
|
2801
|
+
}
|
|
2802
|
+
const throwError = this.#onError(!!opt.silent, !!opt.async);
|
|
2803
|
+
if (typeof src === "undefined" || src === null) {
|
|
2804
|
+
return throwError(new Error("marked(): input parameter is undefined or null"));
|
|
2805
|
+
}
|
|
2806
|
+
if (typeof src !== "string") {
|
|
2807
|
+
return throwError(new Error("marked(): input parameter is of type " + Object.prototype.toString.call(src) + ", string expected"));
|
|
2808
|
+
}
|
|
2809
|
+
if (opt.hooks) {
|
|
2810
|
+
opt.hooks.options = opt;
|
|
2811
|
+
}
|
|
2812
|
+
if (opt.async) {
|
|
2813
|
+
return Promise.resolve(opt.hooks ? opt.hooks.preprocess(src) : src).then((src2) => lexer2(src2, opt)).then((tokens) => opt.hooks ? opt.hooks.processAllTokens(tokens) : tokens).then((tokens) => opt.walkTokens ? Promise.all(this.walkTokens(tokens, opt.walkTokens)).then(() => tokens) : tokens).then((tokens) => parser2(tokens, opt)).then((html2) => opt.hooks ? opt.hooks.postprocess(html2) : html2).catch(throwError);
|
|
2814
|
+
}
|
|
2815
|
+
try {
|
|
2816
|
+
if (opt.hooks) {
|
|
2817
|
+
src = opt.hooks.preprocess(src);
|
|
2818
|
+
}
|
|
2819
|
+
let tokens = lexer2(src, opt);
|
|
2820
|
+
if (opt.hooks) {
|
|
2821
|
+
tokens = opt.hooks.processAllTokens(tokens);
|
|
2822
|
+
}
|
|
2823
|
+
if (opt.walkTokens) {
|
|
2824
|
+
this.walkTokens(tokens, opt.walkTokens);
|
|
2825
|
+
}
|
|
2826
|
+
let html2 = parser2(tokens, opt);
|
|
2827
|
+
if (opt.hooks) {
|
|
2828
|
+
html2 = opt.hooks.postprocess(html2);
|
|
2829
|
+
}
|
|
2830
|
+
return html2;
|
|
2831
|
+
} catch (e) {
|
|
2832
|
+
return throwError(e);
|
|
2833
|
+
}
|
|
2834
|
+
};
|
|
2835
|
+
}
|
|
2836
|
+
#onError(silent, async) {
|
|
2837
|
+
return (e) => {
|
|
2838
|
+
e.message += "\nPlease report this to https://github.com/markedjs/marked.";
|
|
2839
|
+
if (silent) {
|
|
2840
|
+
const msg = "<p>An error occurred:</p><pre>" + escape$1(e.message + "", true) + "</pre>";
|
|
2841
|
+
if (async) {
|
|
2842
|
+
return Promise.resolve(msg);
|
|
2843
|
+
}
|
|
2844
|
+
return msg;
|
|
2845
|
+
}
|
|
2846
|
+
if (async) {
|
|
2847
|
+
return Promise.reject(e);
|
|
2848
|
+
}
|
|
2849
|
+
throw e;
|
|
2850
|
+
};
|
|
2851
|
+
}
|
|
2852
|
+
};
|
|
2853
|
+
var markedInstance = new Marked();
|
|
2854
|
+
function marked(src, opt) {
|
|
2855
|
+
return markedInstance.parse(src, opt);
|
|
2856
|
+
}
|
|
2857
|
+
marked.options = marked.setOptions = function(options2) {
|
|
2858
|
+
markedInstance.setOptions(options2);
|
|
2859
|
+
marked.defaults = markedInstance.defaults;
|
|
2860
|
+
changeDefaults(marked.defaults);
|
|
2861
|
+
return marked;
|
|
2862
|
+
};
|
|
2863
|
+
marked.getDefaults = _getDefaults;
|
|
2864
|
+
marked.defaults = _defaults;
|
|
2865
|
+
marked.use = function(...args) {
|
|
2866
|
+
markedInstance.use(...args);
|
|
2867
|
+
marked.defaults = markedInstance.defaults;
|
|
2868
|
+
changeDefaults(marked.defaults);
|
|
2869
|
+
return marked;
|
|
2870
|
+
};
|
|
2871
|
+
marked.walkTokens = function(tokens, callback) {
|
|
2872
|
+
return markedInstance.walkTokens(tokens, callback);
|
|
2873
|
+
};
|
|
2874
|
+
marked.parseInline = markedInstance.parseInline;
|
|
2875
|
+
marked.Parser = _Parser;
|
|
2876
|
+
marked.parser = _Parser.parse;
|
|
2877
|
+
marked.Renderer = _Renderer;
|
|
2878
|
+
marked.TextRenderer = _TextRenderer;
|
|
2879
|
+
marked.Lexer = _Lexer;
|
|
2880
|
+
marked.lexer = _Lexer.lex;
|
|
2881
|
+
marked.Tokenizer = _Tokenizer;
|
|
2882
|
+
marked.Hooks = _Hooks;
|
|
2883
|
+
marked.parse = marked;
|
|
2884
|
+
var options = marked.options;
|
|
2885
|
+
var setOptions = marked.setOptions;
|
|
2886
|
+
var use = marked.use;
|
|
2887
|
+
var walkTokens = marked.walkTokens;
|
|
2888
|
+
var parseInline = marked.parseInline;
|
|
2889
|
+
var parser = _Parser.parse;
|
|
2890
|
+
var lexer = _Lexer.lex;
|
|
2891
|
+
|
|
2892
|
+
// app/assets/javascripts/llm_meta_widget/config.js
|
|
2893
|
+
var DEFAULTS = Object.freeze({
|
|
2894
|
+
API_KEY_UUID: "ollama-local",
|
|
2895
|
+
ACTIONS_SCHEMA_ID: "ai-actions",
|
|
2896
|
+
STATE_GLOBAL: "aiState",
|
|
2897
|
+
ACTIONS_GLOBAL: "aiActions",
|
|
2898
|
+
REMOTE_TOOLS_SCHEMA_ID: "remote-mcp-tools",
|
|
2899
|
+
LLM_PROVIDER: "llm_meta_server",
|
|
2900
|
+
MAX_ROUNDS: 3
|
|
2901
|
+
});
|
|
2902
|
+
var splitList = (v) => v.split(",").map((s) => s.trim()).filter(Boolean);
|
|
2903
|
+
function readConfig(el) {
|
|
2904
|
+
const raw = (n) => el && el.hasAttribute(n) ? el.getAttribute(n) : null;
|
|
2905
|
+
const str = (n, d) => {
|
|
2906
|
+
const v = raw(n);
|
|
2907
|
+
return v === null ? d : v;
|
|
2908
|
+
};
|
|
2909
|
+
const bool = (n, d) => {
|
|
2910
|
+
const v = raw(n);
|
|
2911
|
+
return v === null ? d : v !== "false";
|
|
2912
|
+
};
|
|
2913
|
+
const int = (n, d) => {
|
|
2914
|
+
const v = raw(n);
|
|
2915
|
+
if (v === null) return d;
|
|
2916
|
+
const i = parseInt(v, 10);
|
|
2917
|
+
return Number.isNaN(i) ? d : i;
|
|
2918
|
+
};
|
|
2919
|
+
const list2 = (n) => {
|
|
2920
|
+
const v = raw(n);
|
|
2921
|
+
return v === null || v.trim() === "" ? null : splitList(v);
|
|
2922
|
+
};
|
|
2923
|
+
const toolHub = str("tool-hub-url", null) || null;
|
|
2924
|
+
const wellKnown = raw("well-known-urls");
|
|
2925
|
+
return {
|
|
2926
|
+
LLM_BASE: str("llm-url", null),
|
|
2927
|
+
TOOL_HUB_BASE: toolHub,
|
|
2928
|
+
API_KEY_UUID: str("api-key-uuid", DEFAULTS.API_KEY_UUID),
|
|
2929
|
+
MODEL: str("model", null),
|
|
2930
|
+
ACTIONS_SCHEMA_ID: str("actions-schema-id", DEFAULTS.ACTIONS_SCHEMA_ID),
|
|
2931
|
+
GREETING: str("greeting", null),
|
|
2932
|
+
STATE_GLOBAL: str("state-global", DEFAULTS.STATE_GLOBAL),
|
|
2933
|
+
ACTIONS_GLOBAL: str("actions-global", DEFAULTS.ACTIONS_GLOBAL),
|
|
2934
|
+
REMOTE_TOOLS_SCHEMA_ID: str("remote-tools-schema-id", DEFAULTS.REMOTE_TOOLS_SCHEMA_ID),
|
|
2935
|
+
MAX_ROUNDS: int("max-rounds", DEFAULTS.MAX_ROUNDS),
|
|
2936
|
+
WELL_KNOWN_URLS: wellKnown === null ? null : wellKnown.trim() === "" ? [] : splitList(wellKnown),
|
|
2937
|
+
LLM_PROVIDER: str("llm-provider", DEFAULTS.LLM_PROVIDER),
|
|
2938
|
+
ENABLE_MODEL_PICKER: bool("enable-model-picker", true),
|
|
2939
|
+
// Class 1 tools are registered on a hub, so the picker needs one — which is
|
|
2940
|
+
// independent of who answers the chat. Page actions and the host's own
|
|
2941
|
+
// .well-known MCP work regardless.
|
|
2942
|
+
ENABLE_TOOL_PICKER: bool("enable-tool-picker", true) && toolHub !== null,
|
|
2943
|
+
MODEL_ALLOWLIST: list2("models"),
|
|
2944
|
+
HUB_TOOLS_ALLOWLIST: list2("hub-tools")
|
|
2945
|
+
};
|
|
2946
|
+
}
|
|
2947
|
+
|
|
2948
|
+
// app/assets/stylesheets/llm_meta_widget/conversation.css
|
|
2949
|
+
var conversation_default = `/*
|
|
2950
|
+
* Canonical "chat conversation" styles for the llm_meta ecosystem.
|
|
2951
|
+
*
|
|
2952
|
+
* Owned by the llm_meta_widget gem. Consumed by:
|
|
2953
|
+
* - the widget partial itself (llm_meta_widget/_chat_panel.html.erb)
|
|
2954
|
+
* - any llm_meta_client-scaffolded chat host that adopts the same
|
|
2955
|
+
* conversation surface
|
|
2956
|
+
*
|
|
2957
|
+
* Scoped by the \`.llm-meta-conversation\` opt-in class on a wrapping
|
|
2958
|
+
* element \u2014 so these rules only affect elements the consumer has
|
|
2959
|
+
* declared as chat-conversation surfaces. Prevents accidental style
|
|
2960
|
+
* bleed onto random host-page elements named .message or .message-role.
|
|
2961
|
+
*
|
|
2962
|
+
* Any host that wants the styles applied wraps its conversation
|
|
2963
|
+
* markup in <div class="llm-meta-conversation"> ... </div> and includes
|
|
2964
|
+
* this file (served via /llm_meta_widget_assets/conversation.css).
|
|
2965
|
+
*/
|
|
2966
|
+
|
|
2967
|
+
.llm-meta-conversation .message {
|
|
2968
|
+
position: relative;
|
|
2969
|
+
margin-bottom: 6px;
|
|
2970
|
+
padding: 6px 10px;
|
|
2971
|
+
border-radius: 8px;
|
|
2972
|
+
max-width: 85%;
|
|
2973
|
+
line-height: 1.4;
|
|
2974
|
+
word-wrap: break-word;
|
|
2975
|
+
}
|
|
2976
|
+
.llm-meta-conversation .message.user {
|
|
2977
|
+
background-color: #dbeafe;
|
|
2978
|
+
border: 1px solid #bfdbfe;
|
|
2979
|
+
margin-left: auto;
|
|
2980
|
+
}
|
|
2981
|
+
.llm-meta-conversation .message.assistant {
|
|
2982
|
+
background-color: #ffffff;
|
|
2983
|
+
border: 1px solid #e5e7eb;
|
|
2984
|
+
margin-right: auto;
|
|
2985
|
+
}
|
|
2986
|
+
.llm-meta-conversation .message.system {
|
|
2987
|
+
background-color: #fef3c7;
|
|
2988
|
+
border: 1px solid #fde68a;
|
|
2989
|
+
color: #78350f;
|
|
2990
|
+
font-size: 0.9em;
|
|
2991
|
+
font-style: italic;
|
|
2992
|
+
}
|
|
2993
|
+
.llm-meta-conversation .message.error {
|
|
2994
|
+
background-color: #fee2e2;
|
|
2995
|
+
border: 1px solid #fecaca;
|
|
2996
|
+
color: #991b1b;
|
|
2997
|
+
}
|
|
2998
|
+
|
|
2999
|
+
.llm-meta-conversation .message-role {
|
|
3000
|
+
display: block;
|
|
3001
|
+
font-weight: 600;
|
|
3002
|
+
font-size: 13px;
|
|
3003
|
+
color: #374151;
|
|
3004
|
+
margin-bottom: 4px;
|
|
3005
|
+
}
|
|
3006
|
+
.llm-meta-conversation .message-content {
|
|
3007
|
+
line-height: 1.4;
|
|
3008
|
+
word-wrap: break-word;
|
|
3009
|
+
overflow-wrap: anywhere;
|
|
3010
|
+
min-width: 0;
|
|
3011
|
+
color: #1f2937;
|
|
3012
|
+
}
|
|
3013
|
+
|
|
3014
|
+
/* Markdown-rendered content styling \u2014 matches llm_meta_chat's
|
|
3015
|
+
* .message-content descendants in chats.css so LLM output looks the
|
|
3016
|
+
* same wherever it renders (widget's streaming path uses marked.esm.js;
|
|
3017
|
+
* chat host's saved-message path uses Redcarpet server-side; both
|
|
3018
|
+
* produce standard HTML that these rules style). */
|
|
3019
|
+
.llm-meta-conversation .message-content p { margin: 0 0 0.5em 0; }
|
|
3020
|
+
.llm-meta-conversation .message-content p:last-child { margin-bottom: 0; }
|
|
3021
|
+
.llm-meta-conversation .message-content h1,
|
|
3022
|
+
.llm-meta-conversation .message-content h2,
|
|
3023
|
+
.llm-meta-conversation .message-content h3,
|
|
3024
|
+
.llm-meta-conversation .message-content h4,
|
|
3025
|
+
.llm-meta-conversation .message-content h5,
|
|
3026
|
+
.llm-meta-conversation .message-content h6 {
|
|
3027
|
+
margin: 0.5em 0 0.25em 0;
|
|
3028
|
+
line-height: 1.25;
|
|
3029
|
+
}
|
|
3030
|
+
.llm-meta-conversation .message-content ul,
|
|
3031
|
+
.llm-meta-conversation .message-content ol {
|
|
3032
|
+
margin: 0.25em 0 0.5em 1.25em;
|
|
3033
|
+
padding: 0;
|
|
3034
|
+
}
|
|
3035
|
+
.llm-meta-conversation .message-content li { margin: 0.1em 0; }
|
|
3036
|
+
.llm-meta-conversation .message-content blockquote {
|
|
3037
|
+
border-left: 3px solid #d1d5db;
|
|
3038
|
+
margin: 0.5em 0;
|
|
3039
|
+
padding: 0.1em 0.75em;
|
|
3040
|
+
color: #4b5563;
|
|
3041
|
+
}
|
|
3042
|
+
.llm-meta-conversation .message-content code {
|
|
3043
|
+
background-color: #f3f4f6;
|
|
3044
|
+
padding: 1px 4px;
|
|
3045
|
+
border-radius: 4px;
|
|
3046
|
+
font-size: 0.9em;
|
|
3047
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
3048
|
+
}
|
|
3049
|
+
.llm-meta-conversation .message-content pre {
|
|
3050
|
+
background-color: #f3f4f6;
|
|
3051
|
+
border: 1px solid #e5e7eb;
|
|
3052
|
+
border-radius: 6px;
|
|
3053
|
+
padding: 8px 10px;
|
|
3054
|
+
margin: 0.5em 0;
|
|
3055
|
+
overflow-x: auto;
|
|
3056
|
+
max-width: 100%;
|
|
3057
|
+
white-space: pre;
|
|
3058
|
+
}
|
|
3059
|
+
.llm-meta-conversation .message-content pre code { background: none; padding: 0; }
|
|
3060
|
+
.llm-meta-conversation .message-content table {
|
|
3061
|
+
border-collapse: collapse;
|
|
3062
|
+
margin: 0.5em 0;
|
|
3063
|
+
display: block;
|
|
3064
|
+
overflow-x: auto;
|
|
3065
|
+
max-width: 100%;
|
|
3066
|
+
}
|
|
3067
|
+
.llm-meta-conversation .message-content th,
|
|
3068
|
+
.llm-meta-conversation .message-content td {
|
|
3069
|
+
border: 1px solid #d1d5db;
|
|
3070
|
+
padding: 4px 8px;
|
|
3071
|
+
}
|
|
3072
|
+
.llm-meta-conversation .message-content th { background-color: #f3f4f6; }
|
|
3073
|
+
.llm-meta-conversation .message-content a {
|
|
3074
|
+
color: #2563eb;
|
|
3075
|
+
text-decoration: underline;
|
|
3076
|
+
}
|
|
3077
|
+
.llm-meta-conversation .message-content img { max-width: 100%; height: auto; }
|
|
3078
|
+
|
|
3079
|
+
/* Tool-use footer inside an assistant bubble \u2014 compact chips naming the
|
|
3080
|
+
* tools the LLM invoked this turn. Gives users visual confirmation of
|
|
3081
|
+
* what actually happened, particularly when the LLM's own text is thin. */
|
|
3082
|
+
.llm-meta-conversation .lmw-tool-chips {
|
|
3083
|
+
margin-top: 0.5em;
|
|
3084
|
+
padding-top: 0.5em;
|
|
3085
|
+
border-top: 1px dashed #e5e7eb;
|
|
3086
|
+
display: flex; flex-wrap: wrap; gap: 4px;
|
|
3087
|
+
}
|
|
3088
|
+
.llm-meta-conversation .lmw-tool-chip {
|
|
3089
|
+
display: inline-block;
|
|
3090
|
+
padding: 2px 8px;
|
|
3091
|
+
background: #eef2ff;
|
|
3092
|
+
color: #3730a3;
|
|
3093
|
+
border: 1px solid #c7d2fe;
|
|
3094
|
+
border-radius: 10px;
|
|
3095
|
+
font-size: 0.8em;
|
|
3096
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
3097
|
+
line-height: 1.4;
|
|
3098
|
+
}
|
|
3099
|
+
.llm-meta-conversation .lmw-tool-chip.error {
|
|
3100
|
+
background: #fee2e2;
|
|
3101
|
+
color: #991b1b;
|
|
3102
|
+
border-color: #fecaca;
|
|
3103
|
+
}
|
|
3104
|
+
|
|
3105
|
+
/* Thinking-token collapsible block \u2014 same shape as llm_meta_chat's
|
|
3106
|
+
* .message-thinking. Streams during reasoning, collapses to a
|
|
3107
|
+
* "(finished)" summary once text starts flowing. */
|
|
3108
|
+
.llm-meta-conversation .message-thinking {
|
|
3109
|
+
margin: 0.5em 0;
|
|
3110
|
+
padding: 0.5em 0.75em;
|
|
3111
|
+
background: #f3f4f6;
|
|
3112
|
+
border-left: 3px solid #9ca3af;
|
|
3113
|
+
border-radius: 4px;
|
|
3114
|
+
font-size: 0.85em;
|
|
3115
|
+
}
|
|
3116
|
+
.llm-meta-conversation .message-thinking summary {
|
|
3117
|
+
cursor: pointer;
|
|
3118
|
+
color: #6b7280;
|
|
3119
|
+
font-weight: 500;
|
|
3120
|
+
user-select: none;
|
|
3121
|
+
}
|
|
3122
|
+
.llm-meta-conversation .message-thinking-content {
|
|
3123
|
+
margin-top: 0.5em;
|
|
3124
|
+
color: #4b5563;
|
|
3125
|
+
white-space: pre-wrap;
|
|
3126
|
+
font-family: ui-monospace, "SF Mono", monospace;
|
|
3127
|
+
font-size: 0.9em;
|
|
3128
|
+
line-height: 1.5;
|
|
3129
|
+
max-height: 10em;
|
|
3130
|
+
overflow-y: auto;
|
|
3131
|
+
}
|
|
3132
|
+
|
|
3133
|
+
/* ---- "working" signals, ported from llm_meta_chat's chats.css ------------
|
|
3134
|
+
*
|
|
3135
|
+
* The wait is often long on local models \u2014 a tool round re-processes the
|
|
3136
|
+
* whole prompt plus the tool results before the first token \u2014 so a still
|
|
3137
|
+
* label reads as a hang. Same class names as the chat app, so the two
|
|
3138
|
+
* surfaces behave identically. */
|
|
3139
|
+
.message-role .role-spinner {
|
|
3140
|
+
display: inline-block;
|
|
3141
|
+
animation: role-working-spin 1.8s linear infinite;
|
|
3142
|
+
transform-origin: 50% 50%;
|
|
3143
|
+
}
|
|
3144
|
+
|
|
3145
|
+
@keyframes role-working-spin {
|
|
3146
|
+
from { transform: rotate(0deg); }
|
|
3147
|
+
to { transform: rotate(360deg); }
|
|
3148
|
+
}
|
|
3149
|
+
|
|
3150
|
+
/* Staggered three-dot indicator on the reasoning summary while thinking is
|
|
3151
|
+
* still streaming. Hidden unless .thinking-active is present. */
|
|
3152
|
+
.thinking-dots {
|
|
3153
|
+
display: none;
|
|
3154
|
+
margin-left: 0.35em;
|
|
3155
|
+
letter-spacing: 0.15em;
|
|
3156
|
+
font-weight: bold;
|
|
3157
|
+
}
|
|
3158
|
+
|
|
3159
|
+
.message-thinking.thinking-active .thinking-dots { display: inline-block; }
|
|
3160
|
+
|
|
3161
|
+
.message-thinking.thinking-active .thinking-dots span {
|
|
3162
|
+
display: inline-block;
|
|
3163
|
+
opacity: 0.25;
|
|
3164
|
+
animation: thinking-dot 1.4s ease-in-out infinite;
|
|
3165
|
+
}
|
|
3166
|
+
|
|
3167
|
+
.message-thinking.thinking-active .thinking-dots span:nth-child(2) { animation-delay: 0.2s; }
|
|
3168
|
+
.message-thinking.thinking-active .thinking-dots span:nth-child(3) { animation-delay: 0.4s; }
|
|
3169
|
+
|
|
3170
|
+
@keyframes thinking-dot {
|
|
3171
|
+
0%, 60%, 100% { opacity: 0.25; }
|
|
3172
|
+
30% { opacity: 1; }
|
|
3173
|
+
}
|
|
3174
|
+
|
|
3175
|
+
/* Respect a reader's motion preference: keep the label, drop the movement. */
|
|
3176
|
+
@media (prefers-reduced-motion: reduce) {
|
|
3177
|
+
.message-role .role-spinner,
|
|
3178
|
+
.message-thinking.thinking-active .thinking-dots span { animation: none; }
|
|
3179
|
+
}
|
|
3180
|
+
`;
|
|
3181
|
+
|
|
3182
|
+
// app/assets/stylesheets/llm_meta_widget/panel.css
|
|
3183
|
+
var panel_default = `/* The chat panel's own styles. Extracted from _chat_panel.html.erb when the
|
|
3184
|
+
panel became a custom element; bundled into the element so a non-Rails host
|
|
3185
|
+
serves no stylesheet of its own. Conversation-surface styles (bubbles, role
|
|
3186
|
+
labels, thinking blocks) stay in conversation.css. */
|
|
3187
|
+
/* Widget-specific chrome \u2014 floating container, header, scroll region,
|
|
3188
|
+
* input area. Everything BUBBLE / THINKING / ROLE-related lives in the
|
|
3189
|
+
* shared conversation.css above. */
|
|
3190
|
+
#llm-meta-widget-chat {
|
|
3191
|
+
/* Positioned via JS on open (see setOpen()) so top/left are set in
|
|
3192
|
+
* pixels \u2014 required for CSS \`resize: both\` to grow correctly
|
|
3193
|
+
* (bottom/right anchoring blocks the resize handle from being
|
|
3194
|
+
* dragged outward from viewport corner). */
|
|
3195
|
+
position: fixed;
|
|
3196
|
+
width: 40em; height: 55vh;
|
|
3197
|
+
min-width: 24em; min-height: 16em;
|
|
3198
|
+
max-width: calc(100vw - 2em); max-height: calc(100vh - 2em);
|
|
3199
|
+
background: #ffffff;
|
|
3200
|
+
border: 1px solid #e5e7eb;
|
|
3201
|
+
border-radius: 8px;
|
|
3202
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
|
|
3203
|
+
padding: 10px;
|
|
3204
|
+
z-index: 9999;
|
|
3205
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
3206
|
+
font-size: 14px;
|
|
3207
|
+
color: #1f2937;
|
|
3208
|
+
display: flex; flex-direction: column;
|
|
3209
|
+
/* Native drag-to-resize handle appears at bottom-right when
|
|
3210
|
+
* overflow != visible. overflow:hidden clips at widget edge; the
|
|
3211
|
+
* inner .lmw-messages has its own scroll so no content is lost. */
|
|
3212
|
+
resize: both;
|
|
3213
|
+
overflow: hidden;
|
|
3214
|
+
}
|
|
3215
|
+
#llm-meta-widget-chat.lmw-collapsed { display: none; }
|
|
3216
|
+
|
|
3217
|
+
/* Collapsed-state toggle \u2014 a small floating chat-bubble button in the
|
|
3218
|
+
* bottom-right corner. Always in the DOM; visible only when the chat
|
|
3219
|
+
* panel is collapsed. Click to open the chat panel; the panel's \xD7 button
|
|
3220
|
+
* collapses back to this. State persists across page loads via
|
|
3221
|
+
* localStorage (see the JS below). */
|
|
3222
|
+
#llm-meta-widget-toggle {
|
|
3223
|
+
position: fixed; bottom: 1em; right: 1em;
|
|
3224
|
+
width: 52px; height: 52px;
|
|
3225
|
+
border-radius: 26px;
|
|
3226
|
+
background-color: #3b82f6;
|
|
3227
|
+
color: white;
|
|
3228
|
+
border: none;
|
|
3229
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
|
|
3230
|
+
cursor: pointer;
|
|
3231
|
+
z-index: 9999;
|
|
3232
|
+
display: flex; align-items: center; justify-content: center;
|
|
3233
|
+
transition: background-color 0.15s, transform 0.1s;
|
|
3234
|
+
}
|
|
3235
|
+
#llm-meta-widget-toggle:hover { background-color: #2563eb; }
|
|
3236
|
+
#llm-meta-widget-toggle:active { transform: scale(0.96); }
|
|
3237
|
+
#llm-meta-widget-toggle.lmw-hidden { display: none; }
|
|
3238
|
+
#llm-meta-widget-toggle svg { display: block; }
|
|
3239
|
+
|
|
3240
|
+
/* Header hide/close: a subtle "\u2212" button that collapses to the toggle
|
|
3241
|
+
* bubble. Sits between the "clear" button and title. */
|
|
3242
|
+
#llm-meta-widget-chat .lmw-hide {
|
|
3243
|
+
background: none; border: none;
|
|
3244
|
+
color: #6b7280;
|
|
3245
|
+
font: inherit; font-size: 18px; line-height: 1;
|
|
3246
|
+
cursor: pointer;
|
|
3247
|
+
padding: 0 8px;
|
|
3248
|
+
transition: color 0.15s;
|
|
3249
|
+
}
|
|
3250
|
+
#llm-meta-widget-chat .lmw-hide:hover { color: #1f2937; }
|
|
3251
|
+
#llm-meta-widget-chat .lmw-header {
|
|
3252
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
3253
|
+
gap: 8px;
|
|
3254
|
+
padding-bottom: 8px; margin-bottom: 8px;
|
|
3255
|
+
border-bottom: 2px solid #e5e7eb;
|
|
3256
|
+
}
|
|
3257
|
+
#llm-meta-widget-chat .lmw-header-right { display: flex; align-items: center; gap: 4px; flex-shrink: 0; }
|
|
3258
|
+
|
|
3259
|
+
/* Level-1 pickers row \u2014 matches chat.aibranch.org's input-container
|
|
3260
|
+
* convention (attach / model / tools / settings in a horizontal row
|
|
3261
|
+
* below the textarea). We only render model + tools for now. Uses
|
|
3262
|
+
* relative positioning so the tools <details> panel can drop down
|
|
3263
|
+
* as a floating overlay instead of pushing the input area around. */
|
|
3264
|
+
#llm-meta-widget-chat .lmw-input-controls {
|
|
3265
|
+
display: flex;
|
|
3266
|
+
align-items: center;
|
|
3267
|
+
gap: 6px;
|
|
3268
|
+
margin-top: 6px;
|
|
3269
|
+
position: relative;
|
|
3270
|
+
min-height: 26px;
|
|
3271
|
+
}
|
|
3272
|
+
#llm-meta-widget-chat .lmw-title { font-weight: 600; color: #1f2937; }
|
|
3273
|
+
|
|
3274
|
+
/* Level-1 model picker \u2014 compact <select> next to the title.
|
|
3275
|
+
* Populated by JS on widget open from GET /api/llms (anon path
|
|
3276
|
+
* returns Ollama-only). Hidden entirely if enable_model_picker
|
|
3277
|
+
* is false at the helper call site. */
|
|
3278
|
+
#llm-meta-widget-chat .lmw-model-picker {
|
|
3279
|
+
font: inherit; font-size: 12px;
|
|
3280
|
+
padding: 3px 6px;
|
|
3281
|
+
border: 1px solid #d1d5db;
|
|
3282
|
+
border-radius: 4px;
|
|
3283
|
+
background: #f9fafb;
|
|
3284
|
+
color: #374151;
|
|
3285
|
+
cursor: pointer;
|
|
3286
|
+
max-width: 12em;
|
|
3287
|
+
text-overflow: ellipsis;
|
|
3288
|
+
}
|
|
3289
|
+
#llm-meta-widget-chat .lmw-model-picker:focus {
|
|
3290
|
+
outline: none;
|
|
3291
|
+
border-color: #3b82f6;
|
|
3292
|
+
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.15);
|
|
3293
|
+
}
|
|
3294
|
+
|
|
3295
|
+
/* Level-1 tools picker \u2014 inline chip in the input-controls row (same
|
|
3296
|
+
* convention as chat.aibranch.org). The tools list drops down as a
|
|
3297
|
+
* floating overlay above the input, so opening the panel doesn't
|
|
3298
|
+
* push the textarea around. Populated by JS on widget open from
|
|
3299
|
+
* GET /api/mcp_servers (anon path returns public_to_anonymous MCP
|
|
3300
|
+
* servers with their tools inline). One checkbox per SERVER \u2014
|
|
3301
|
+
* toggling enables/disables ALL of its tools. */
|
|
3302
|
+
#llm-meta-widget-chat .lmw-tools-picker {
|
|
3303
|
+
position: relative; /* anchor for the absolutely-positioned list */
|
|
3304
|
+
}
|
|
3305
|
+
#llm-meta-widget-chat .lmw-tools-picker > summary {
|
|
3306
|
+
list-style: none;
|
|
3307
|
+
cursor: pointer;
|
|
3308
|
+
padding: 3px 8px;
|
|
3309
|
+
font-size: 12px;
|
|
3310
|
+
color: #374151;
|
|
3311
|
+
background: #f9fafb;
|
|
3312
|
+
border: 1px solid #d1d5db;
|
|
3313
|
+
border-radius: 4px;
|
|
3314
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
3315
|
+
}
|
|
3316
|
+
#llm-meta-widget-chat .lmw-tools-picker > summary:hover { background: #f3f4f6; }
|
|
3317
|
+
#llm-meta-widget-chat .lmw-tools-picker > summary::-webkit-details-marker { display: none; }
|
|
3318
|
+
#llm-meta-widget-chat .lmw-tools-picker[open] > summary {
|
|
3319
|
+
background: #eff6ff;
|
|
3320
|
+
border-color: #93c5fd;
|
|
3321
|
+
}
|
|
3322
|
+
#llm-meta-widget-chat .lmw-tools-count {
|
|
3323
|
+
display: inline-block;
|
|
3324
|
+
background: #0ea5e9;
|
|
3325
|
+
color: #fff;
|
|
3326
|
+
font-size: 10px;
|
|
3327
|
+
padding: 0 5px;
|
|
3328
|
+
border-radius: 8px;
|
|
3329
|
+
min-width: 14px;
|
|
3330
|
+
text-align: center;
|
|
3331
|
+
}
|
|
3332
|
+
#llm-meta-widget-chat .lmw-tools-count.lmw-tools-count-zero {
|
|
3333
|
+
background: #d1d5db;
|
|
3334
|
+
color: #6b7280;
|
|
3335
|
+
}
|
|
3336
|
+
/* Floating dropdown panel \u2014 sits above the input area when open so
|
|
3337
|
+
* expanding never shifts the textarea. Bottom-anchored so it opens
|
|
3338
|
+
* UPWARD (natural direction since we're at the panel's bottom). */
|
|
3339
|
+
#llm-meta-widget-chat .lmw-tools-list {
|
|
3340
|
+
position: absolute;
|
|
3341
|
+
bottom: calc(100% + 4px);
|
|
3342
|
+
left: 0;
|
|
3343
|
+
min-width: 220px;
|
|
3344
|
+
max-height: 12em;
|
|
3345
|
+
overflow-y: auto;
|
|
3346
|
+
background: #ffffff;
|
|
3347
|
+
border: 1px solid #d1d5db;
|
|
3348
|
+
border-radius: 6px;
|
|
3349
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.10);
|
|
3350
|
+
padding: 4px 0;
|
|
3351
|
+
z-index: 10;
|
|
3352
|
+
}
|
|
3353
|
+
#llm-meta-widget-chat .lmw-tools-item {
|
|
3354
|
+
display: flex; align-items: center; gap: 6px;
|
|
3355
|
+
padding: 4px 10px;
|
|
3356
|
+
font-size: 12px;
|
|
3357
|
+
color: #374151;
|
|
3358
|
+
cursor: pointer;
|
|
3359
|
+
}
|
|
3360
|
+
#llm-meta-widget-chat .lmw-tools-item:hover { background: #eff6ff; }
|
|
3361
|
+
#llm-meta-widget-chat .lmw-tools-item input[type="checkbox"] { margin: 0; }
|
|
3362
|
+
#llm-meta-widget-chat .lmw-tools-empty {
|
|
3363
|
+
padding: 6px 10px;
|
|
3364
|
+
font-size: 12px;
|
|
3365
|
+
color: #94a3b8;
|
|
3366
|
+
font-style: italic;
|
|
3367
|
+
}
|
|
3368
|
+
/* Two-level tool selection \u2014 mirrors chat.aibranch.org's tool_selector.
|
|
3369
|
+
* Server row (bulk checkbox + name + count + caret) is clickable to
|
|
3370
|
+
* expand its children. Individual tool rows are indented one level. */
|
|
3371
|
+
#llm-meta-widget-chat .lmw-tools-server + .lmw-tools-server {
|
|
3372
|
+
border-top: 1px solid #f1f5f9;
|
|
3373
|
+
}
|
|
3374
|
+
#llm-meta-widget-chat .lmw-tools-server-row {
|
|
3375
|
+
display: flex; align-items: center; gap: 6px;
|
|
3376
|
+
padding: 4px 10px;
|
|
3377
|
+
font-size: 12px;
|
|
3378
|
+
font-weight: 500;
|
|
3379
|
+
color: #1f2937;
|
|
3380
|
+
cursor: pointer;
|
|
3381
|
+
}
|
|
3382
|
+
#llm-meta-widget-chat .lmw-tools-server-row:hover { background: #eff6ff; }
|
|
3383
|
+
#llm-meta-widget-chat .lmw-tools-server-name { flex: 1; }
|
|
3384
|
+
#llm-meta-widget-chat .lmw-tools-server-caret {
|
|
3385
|
+
color: #94a3b8;
|
|
3386
|
+
font-size: 10px;
|
|
3387
|
+
line-height: 1;
|
|
3388
|
+
flex-shrink: 0;
|
|
3389
|
+
}
|
|
3390
|
+
#llm-meta-widget-chat .lmw-tools-item-child {
|
|
3391
|
+
padding-left: 26px; /* align tool checkboxes past the server bulk checkbox */
|
|
3392
|
+
font-size: 12px;
|
|
3393
|
+
color: #4b5563;
|
|
3394
|
+
}
|
|
3395
|
+
#llm-meta-widget-chat .lmw-clear {
|
|
3396
|
+
background: #ef4444; color: white; border: none;
|
|
3397
|
+
padding: 4px 10px; border-radius: 6px; cursor: pointer;
|
|
3398
|
+
font: inherit; font-size: 12px;
|
|
3399
|
+
transition: background-color 0.15s;
|
|
3400
|
+
}
|
|
3401
|
+
#llm-meta-widget-chat .lmw-clear:hover { background: #dc2626; }
|
|
3402
|
+
|
|
3403
|
+
#llm-meta-widget-chat .lmw-messages {
|
|
3404
|
+
flex: 1; overflow-y: auto;
|
|
3405
|
+
padding: 10px;
|
|
3406
|
+
background-color: #f9fafb;
|
|
3407
|
+
border-radius: 8px;
|
|
3408
|
+
margin-bottom: 8px;
|
|
3409
|
+
min-height: 6em;
|
|
3410
|
+
/* No max-height \u2014 messages grows with the widget (which the user
|
|
3411
|
+
* can drag to any size). A hard cap here would leave an ugly gap
|
|
3412
|
+
* between the messages area and the input on tall widget sizes. */
|
|
3413
|
+
}
|
|
3414
|
+
#llm-meta-widget-chat .lmw-messages::-webkit-scrollbar { width: 8px; }
|
|
3415
|
+
#llm-meta-widget-chat .lmw-messages::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 4px; }
|
|
3416
|
+
#llm-meta-widget-chat .lmw-messages::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 4px; }
|
|
3417
|
+
#llm-meta-widget-chat .lmw-messages::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
|
|
3418
|
+
|
|
3419
|
+
/* Input area \u2014 mirrors llm_meta_chat's .chat-input-container +
|
|
3420
|
+
* .chat-input + .send-button so it reads as the same UI. Multi-line
|
|
3421
|
+
* textarea with an absolutely-positioned paper-plane send button
|
|
3422
|
+
* (SVG inlined so no external icon-font dep). */
|
|
3423
|
+
#llm-meta-widget-chat .lmw-input-container {
|
|
3424
|
+
background-color: white;
|
|
3425
|
+
border: 2px solid #e5e7eb;
|
|
3426
|
+
border-radius: 8px;
|
|
3427
|
+
padding: 8px;
|
|
3428
|
+
}
|
|
3429
|
+
#llm-meta-widget-chat .message-content details > summary {
|
|
3430
|
+
cursor: pointer;
|
|
3431
|
+
font-weight: 600;
|
|
3432
|
+
}
|
|
3433
|
+
#llm-meta-widget-chat .lmw-sent-text {
|
|
3434
|
+
margin-top: 6px;
|
|
3435
|
+
font-size: 12px;
|
|
3436
|
+
opacity: 0.8;
|
|
3437
|
+
white-space: pre-wrap;
|
|
3438
|
+
}
|
|
3439
|
+
|
|
3440
|
+
/* Opening state: greeting + the offers themselves. Shown until the first
|
|
3441
|
+
* real turn, and again after Clear. */
|
|
3442
|
+
#llm-meta-widget-chat .lmw-welcome { padding: 4px 2px 2px; }
|
|
3443
|
+
#llm-meta-widget-chat .lmw-welcome-hello {
|
|
3444
|
+
margin: 0 0 12px;
|
|
3445
|
+
font-size: 14px;
|
|
3446
|
+
line-height: 1.5;
|
|
3447
|
+
color: #374151;
|
|
3448
|
+
}
|
|
3449
|
+
#llm-meta-widget-chat .lmw-welcome-card {
|
|
3450
|
+
background-color: #f9fafb;
|
|
3451
|
+
border: 1px solid #e5e7eb;
|
|
3452
|
+
border-radius: 8px;
|
|
3453
|
+
padding: 10px 12px;
|
|
3454
|
+
margin-bottom: 8px;
|
|
3455
|
+
}
|
|
3456
|
+
#llm-meta-widget-chat .lmw-welcome-start {
|
|
3457
|
+
background-color: #eff6ff;
|
|
3458
|
+
color: #1d4ed8;
|
|
3459
|
+
border: 1px solid #bfdbfe;
|
|
3460
|
+
border-radius: 999px;
|
|
3461
|
+
padding: 4px 12px;
|
|
3462
|
+
font-size: 13px;
|
|
3463
|
+
font-family: inherit;
|
|
3464
|
+
cursor: pointer;
|
|
3465
|
+
}
|
|
3466
|
+
#llm-meta-widget-chat .lmw-welcome-start:hover { background-color: #dbeafe; }
|
|
3467
|
+
#llm-meta-widget-chat .lmw-welcome-why {
|
|
3468
|
+
margin: 8px 0 0;
|
|
3469
|
+
font-size: 13px;
|
|
3470
|
+
line-height: 1.45;
|
|
3471
|
+
color: #4b5563;
|
|
3472
|
+
}
|
|
3473
|
+
#llm-meta-widget-chat .lmw-welcome-slots {
|
|
3474
|
+
margin: 8px 0 0;
|
|
3475
|
+
padding-left: 16px;
|
|
3476
|
+
font-size: 12px;
|
|
3477
|
+
color: #6b7280;
|
|
3478
|
+
}
|
|
3479
|
+
#llm-meta-widget-chat .lmw-welcome-slots li { margin-bottom: 2px; }
|
|
3480
|
+
#llm-meta-widget-chat .lmw-welcome-slots li.filled { color: #047857; }
|
|
3481
|
+
|
|
3482
|
+
/* Server-offered prompt templates. The row is display:none in markup and
|
|
3483
|
+
* gets its display back only when a prompt actually exists, so the inline
|
|
3484
|
+
* style and this rule have to agree on "flex". */
|
|
3485
|
+
#llm-meta-widget-chat .lmw-prompts {
|
|
3486
|
+
display: flex;
|
|
3487
|
+
flex-wrap: wrap;
|
|
3488
|
+
gap: 6px;
|
|
3489
|
+
margin-bottom: 8px;
|
|
3490
|
+
}
|
|
3491
|
+
#llm-meta-widget-chat .lmw-prompt {
|
|
3492
|
+
background-color: #eff6ff;
|
|
3493
|
+
color: #1d4ed8;
|
|
3494
|
+
border: 1px solid #bfdbfe;
|
|
3495
|
+
border-radius: 999px;
|
|
3496
|
+
padding: 4px 12px;
|
|
3497
|
+
font-size: 12px;
|
|
3498
|
+
font-family: inherit;
|
|
3499
|
+
cursor: pointer;
|
|
3500
|
+
transition: background-color 0.2s, border-color 0.2s;
|
|
3501
|
+
}
|
|
3502
|
+
#llm-meta-widget-chat .lmw-prompt:hover:not(:disabled) {
|
|
3503
|
+
background-color: #dbeafe;
|
|
3504
|
+
border-color: #93c5fd;
|
|
3505
|
+
}
|
|
3506
|
+
#llm-meta-widget-chat .lmw-prompt:disabled { opacity: 0.5; cursor: default; }
|
|
3507
|
+
#llm-meta-widget-chat .lmw-form { display: flex; flex-direction: column; gap: 8px; }
|
|
3508
|
+
#llm-meta-widget-chat .lmw-input-wrapper { position: relative; flex: 1; }
|
|
3509
|
+
#llm-meta-widget-chat .lmw-input {
|
|
3510
|
+
width: 100%;
|
|
3511
|
+
padding: 10px 48px 10px 10px;
|
|
3512
|
+
border: 1px solid #d1d5db;
|
|
3513
|
+
border-radius: 6px;
|
|
3514
|
+
font-size: 14px;
|
|
3515
|
+
font-family: inherit;
|
|
3516
|
+
resize: vertical;
|
|
3517
|
+
min-height: 44px;
|
|
3518
|
+
box-sizing: border-box;
|
|
3519
|
+
transition: border-color 0.2s, box-shadow 0.2s;
|
|
3520
|
+
}
|
|
3521
|
+
#llm-meta-widget-chat .lmw-input:focus {
|
|
3522
|
+
outline: none;
|
|
3523
|
+
border-color: #3b82f6;
|
|
3524
|
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
|
3525
|
+
}
|
|
3526
|
+
#llm-meta-widget-chat .lmw-send {
|
|
3527
|
+
position: absolute;
|
|
3528
|
+
bottom: 6px;
|
|
3529
|
+
right: 6px;
|
|
3530
|
+
background-color: #3b82f6;
|
|
3531
|
+
color: white;
|
|
3532
|
+
border: none;
|
|
3533
|
+
width: 32px;
|
|
3534
|
+
height: 32px;
|
|
3535
|
+
padding: 0;
|
|
3536
|
+
border-radius: 6px;
|
|
3537
|
+
cursor: pointer;
|
|
3538
|
+
display: flex;
|
|
3539
|
+
align-items: center;
|
|
3540
|
+
justify-content: center;
|
|
3541
|
+
transition: background-color 0.15s, opacity 0.15s;
|
|
3542
|
+
}
|
|
3543
|
+
#llm-meta-widget-chat .lmw-send:hover { background-color: #2563eb; }
|
|
3544
|
+
#llm-meta-widget-chat .lmw-send:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
3545
|
+
#llm-meta-widget-chat .lmw-send svg { display: block; }
|
|
3546
|
+
`;
|
|
3547
|
+
|
|
3548
|
+
// app/assets/javascripts/llm_meta_widget/element.js
|
|
3549
|
+
marked.setOptions({ gfm: true, breaks: true });
|
|
3550
|
+
var MARKUP = `
|
|
3551
|
+
<button type="button" id="llm-meta-widget-toggle" title="Open AI assistant" aria-label="Open AI assistant">
|
|
3552
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true">
|
|
3553
|
+
<path d="M2 4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v6a2 2 0 0 1-2 2h-3l-3.5 3.5V12H4a2 2 0 0 1-2-2V4Zm3 2a1 1 0 1 0 0 2 1 1 0 0 0 0-2Zm3 0a1 1 0 1 0 0 2 1 1 0 0 0 0-2Zm3 0a1 1 0 1 0 0 2 1 1 0 0 0 0-2Z"/>
|
|
3554
|
+
</svg>
|
|
3555
|
+
</button>
|
|
3556
|
+
|
|
3557
|
+
<div id="llm-meta-widget-chat" class="llm-meta-conversation">
|
|
3558
|
+
<div class="lmw-header">
|
|
3559
|
+
<span class="lmw-title">AI assistant</span>
|
|
3560
|
+
<div class="lmw-header-right">
|
|
3561
|
+
<button type="button" class="lmw-clear" title="Clear conversation">clear</button>
|
|
3562
|
+
<button type="button" class="lmw-hide" title="Hide">\u2212</button>
|
|
3563
|
+
</div>
|
|
3564
|
+
</div>
|
|
3565
|
+
<div class="lmw-messages"></div>
|
|
3566
|
+
<div class="lmw-input-container">
|
|
3567
|
+
host MCP server actually offers one, so a tools-only server leaves
|
|
3568
|
+
the widget exactly as it was. %>
|
|
3569
|
+
<div class="lmw-prompts" style="display:none"></div>
|
|
3570
|
+
<form class="lmw-form">
|
|
3571
|
+
<div class="lmw-input-wrapper">
|
|
3572
|
+
<textarea class="lmw-input" placeholder="Enter your message..." rows="2" autocomplete="off"></textarea>
|
|
3573
|
+
<button type="submit" class="lmw-send" title="Send message">
|
|
3574
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" aria-hidden="true">
|
|
3575
|
+
<path d="M15.964.686a.5.5 0 0 0-.65-.65L.293 6.011a.513.513 0 0 0 .002.947l4.708 1.878 8.94-6.94-6.94 8.94 1.879 4.708c.163.407.756.416.951.016l6.13-13.884Z"/>
|
|
3576
|
+
</svg>
|
|
3577
|
+
</button>
|
|
3578
|
+
</div>
|
|
3579
|
+
<% if enable_model_picker || enable_tool_picker %>
|
|
3580
|
+
<div class="lmw-input-controls">
|
|
3581
|
+
<% if enable_model_picker %>
|
|
3582
|
+
<select class="lmw-model-picker" title="Select model" aria-label="Select model" style="display:none">
|
|
3583
|
+
</select>
|
|
3584
|
+
<% end %>
|
|
3585
|
+
<% if enable_tool_picker %>
|
|
3586
|
+
<details class="lmw-tools-picker">
|
|
3587
|
+
<summary>
|
|
3588
|
+
\u{1F527} Tools
|
|
3589
|
+
<span class="lmw-tools-count lmw-tools-count-zero">0</span>
|
|
3590
|
+
</summary>
|
|
3591
|
+
<div class="lmw-tools-list">
|
|
3592
|
+
<div class="lmw-tools-empty">Loading\u2026</div>
|
|
3593
|
+
</div>
|
|
3594
|
+
</details>
|
|
3595
|
+
<% end %>
|
|
3596
|
+
</div>
|
|
3597
|
+
<% end %>
|
|
3598
|
+
</form>
|
|
3599
|
+
</div>
|
|
3600
|
+
</div>
|
|
3601
|
+
`;
|
|
3602
|
+
var stylesInjected = false;
|
|
3603
|
+
function injectStylesOnce(doc) {
|
|
3604
|
+
if (stylesInjected) return;
|
|
3605
|
+
stylesInjected = true;
|
|
3606
|
+
const style = doc.createElement("style");
|
|
3607
|
+
style.setAttribute("data-llm-meta-widget", "styles");
|
|
3608
|
+
style.textContent = conversation_default + "\n" + panel_default;
|
|
3609
|
+
doc.head.appendChild(style);
|
|
3610
|
+
}
|
|
3611
|
+
var LlmMetaWidgetElement = class extends HTMLElement {
|
|
3612
|
+
connectedCallback() {
|
|
3613
|
+
if (this.__lmwWired) return;
|
|
3614
|
+
this.__lmwWired = true;
|
|
3615
|
+
if (!this.getAttribute("llm-url") || !this.getAttribute("model")) {
|
|
3616
|
+
console.error("[llm-meta-widget] llm-url and model are both required attributes; not starting.");
|
|
3617
|
+
return;
|
|
3618
|
+
}
|
|
3619
|
+
if (document.getElementById("llm-meta-widget-chat")) {
|
|
3620
|
+
console.warn("[llm-meta-widget] a widget is already on this page; ignoring this one.");
|
|
3621
|
+
return;
|
|
3622
|
+
}
|
|
3623
|
+
injectStylesOnce(document);
|
|
3624
|
+
this.insertAdjacentHTML("beforeend", MARKUP);
|
|
3625
|
+
boot(readConfig(this));
|
|
3626
|
+
}
|
|
3627
|
+
};
|
|
3628
|
+
if (typeof customElements !== "undefined" && !customElements.get("llm-meta-widget")) {
|
|
3629
|
+
customElements.define("llm-meta-widget", LlmMetaWidgetElement);
|
|
3630
|
+
}
|
|
3631
|
+
function boot(cfg) {
|
|
3632
|
+
var LLM_BASE = cfg.LLM_BASE;
|
|
3633
|
+
var TOOL_HUB_BASE = cfg.TOOL_HUB_BASE;
|
|
3634
|
+
var API_KEY_UUID = cfg.API_KEY_UUID;
|
|
3635
|
+
var MODEL = cfg.MODEL;
|
|
3636
|
+
var ACTIONS_SCHEMA_ID = cfg.ACTIONS_SCHEMA_ID;
|
|
3637
|
+
var GREETING = cfg.GREETING;
|
|
3638
|
+
var STATE_GLOBAL = cfg.STATE_GLOBAL;
|
|
3639
|
+
var ACTIONS_GLOBAL = cfg.ACTIONS_GLOBAL;
|
|
3640
|
+
var REMOTE_TOOLS_SCHEMA_ID = cfg.REMOTE_TOOLS_SCHEMA_ID;
|
|
3641
|
+
var MAX_ROUNDS = cfg.MAX_ROUNDS;
|
|
3642
|
+
var WELL_KNOWN_URLS = cfg.WELL_KNOWN_URLS;
|
|
3643
|
+
var LLM_PROVIDER = cfg.LLM_PROVIDER;
|
|
3644
|
+
var ENABLE_MODEL_PICKER = cfg.ENABLE_MODEL_PICKER;
|
|
3645
|
+
var ENABLE_TOOL_PICKER = cfg.ENABLE_TOOL_PICKER;
|
|
3646
|
+
var MODEL_ALLOWLIST = cfg.MODEL_ALLOWLIST;
|
|
3647
|
+
var HUB_TOOLS_ALLOWLIST = cfg.HUB_TOOLS_ALLOWLIST;
|
|
3648
|
+
var root = document.getElementById("llm-meta-widget-chat");
|
|
3649
|
+
var toggleBtn = document.getElementById("llm-meta-widget-toggle");
|
|
3650
|
+
var historyEl = root.querySelector(".lmw-messages");
|
|
3651
|
+
var formEl = root.querySelector(".lmw-form");
|
|
3652
|
+
var inputEl = root.querySelector(".lmw-input");
|
|
3653
|
+
var clearBtn = root.querySelector(".lmw-clear");
|
|
3654
|
+
var hideBtn = root.querySelector(".lmw-hide");
|
|
3655
|
+
var modelPicker = root.querySelector(".lmw-model-picker");
|
|
3656
|
+
var toolsPicker = root.querySelector(".lmw-tools-picker");
|
|
3657
|
+
var toolsListEl = root.querySelector(".lmw-tools-list");
|
|
3658
|
+
var toolsCountEl = root.querySelector(".lmw-tools-count");
|
|
3659
|
+
var STORAGE_KEY_OPEN = "llm_meta_widget:open";
|
|
3660
|
+
var STORAGE_KEY_SIZE = "llm_meta_widget:size";
|
|
3661
|
+
var MIN_SAVED_WIDTH = 320;
|
|
3662
|
+
var MIN_SAVED_HEIGHT = 240;
|
|
3663
|
+
function plausibleSize(w, h) {
|
|
3664
|
+
return w >= MIN_SAVED_WIDTH && h >= MIN_SAVED_HEIGHT && w <= window.innerWidth * 2 && h <= window.innerHeight * 2;
|
|
3665
|
+
}
|
|
3666
|
+
function applyStoredSize() {
|
|
3667
|
+
try {
|
|
3668
|
+
var raw = localStorage.getItem(STORAGE_KEY_SIZE);
|
|
3669
|
+
if (!raw) return;
|
|
3670
|
+
var parts = raw.split("x");
|
|
3671
|
+
var w = parseInt(parts[0], 10), h = parseInt(parts[1], 10);
|
|
3672
|
+
if (!plausibleSize(w, h)) {
|
|
3673
|
+
localStorage.removeItem(STORAGE_KEY_SIZE);
|
|
3674
|
+
return;
|
|
3675
|
+
}
|
|
3676
|
+
root.style.width = w + "px";
|
|
3677
|
+
root.style.height = h + "px";
|
|
3678
|
+
} catch (e) {
|
|
3679
|
+
}
|
|
3680
|
+
}
|
|
3681
|
+
function saveCurrentSize() {
|
|
3682
|
+
try {
|
|
3683
|
+
var rect = root.getBoundingClientRect();
|
|
3684
|
+
var w = Math.round(rect.width), h = Math.round(rect.height);
|
|
3685
|
+
if (!plausibleSize(w, h)) return;
|
|
3686
|
+
localStorage.setItem(STORAGE_KEY_SIZE, w + "x" + h);
|
|
3687
|
+
} catch (e) {
|
|
3688
|
+
}
|
|
3689
|
+
}
|
|
3690
|
+
function positionAtBottomRight(attempt) {
|
|
3691
|
+
var margin = 16;
|
|
3692
|
+
var rect = root.getBoundingClientRect();
|
|
3693
|
+
var top = Math.max(margin, window.innerHeight - rect.height - margin);
|
|
3694
|
+
var left = Math.max(margin, window.innerWidth - rect.width - margin);
|
|
3695
|
+
root.style.top = top + "px";
|
|
3696
|
+
root.style.left = left + "px";
|
|
3697
|
+
var tries = attempt || 0;
|
|
3698
|
+
if (rect.height < MIN_SAVED_HEIGHT && tries < 3 && typeof requestAnimationFrame === "function") {
|
|
3699
|
+
requestAnimationFrame(function() {
|
|
3700
|
+
positionAtBottomRight(tries + 1);
|
|
3701
|
+
});
|
|
3702
|
+
}
|
|
3703
|
+
}
|
|
3704
|
+
function setOpen(open) {
|
|
3705
|
+
if (open) {
|
|
3706
|
+
applyStoredSize();
|
|
3707
|
+
root.classList.remove("lmw-collapsed");
|
|
3708
|
+
toggleBtn.classList.add("lmw-hidden");
|
|
3709
|
+
positionAtBottomRight();
|
|
3710
|
+
try {
|
|
3711
|
+
inputEl.focus();
|
|
3712
|
+
} catch (e) {
|
|
3713
|
+
}
|
|
3714
|
+
ensurePickerLoaded().catch(function(e) {
|
|
3715
|
+
console.warn("[widget] picker load failed:", e);
|
|
3716
|
+
});
|
|
3717
|
+
} else {
|
|
3718
|
+
saveCurrentSize();
|
|
3719
|
+
root.classList.add("lmw-collapsed");
|
|
3720
|
+
toggleBtn.classList.remove("lmw-hidden");
|
|
3721
|
+
}
|
|
3722
|
+
try {
|
|
3723
|
+
localStorage.setItem(STORAGE_KEY_OPEN, open ? "1" : "0");
|
|
3724
|
+
} catch (e) {
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3727
|
+
var initialOpen = false;
|
|
3728
|
+
try {
|
|
3729
|
+
initialOpen = localStorage.getItem(STORAGE_KEY_OPEN) === "1";
|
|
3730
|
+
} catch (e) {
|
|
3731
|
+
}
|
|
3732
|
+
setOpen(initialOpen);
|
|
3733
|
+
window.addEventListener("resize", function() {
|
|
3734
|
+
if (!root.classList.contains("lmw-collapsed")) positionAtBottomRight();
|
|
3735
|
+
});
|
|
3736
|
+
toggleBtn.addEventListener("click", function() {
|
|
3737
|
+
setOpen(true);
|
|
3738
|
+
});
|
|
3739
|
+
hideBtn.addEventListener("click", function() {
|
|
3740
|
+
setOpen(false);
|
|
3741
|
+
});
|
|
3742
|
+
if (typeof ResizeObserver === "function") {
|
|
3743
|
+
var sawFirstResizeTick = false;
|
|
3744
|
+
var ro = new ResizeObserver(function() {
|
|
3745
|
+
if (root.classList.contains("lmw-collapsed")) return;
|
|
3746
|
+
positionAtBottomRight();
|
|
3747
|
+
if (!sawFirstResizeTick) {
|
|
3748
|
+
sawFirstResizeTick = true;
|
|
3749
|
+
return;
|
|
3750
|
+
}
|
|
3751
|
+
saveCurrentSize();
|
|
3752
|
+
});
|
|
3753
|
+
ro.observe(root);
|
|
3754
|
+
}
|
|
3755
|
+
var conversation = [];
|
|
3756
|
+
var conversationStore = createConversationStore({
|
|
3757
|
+
storage: window.sessionStorage,
|
|
3758
|
+
key: conversationKeyFor(window.location)
|
|
3759
|
+
});
|
|
3760
|
+
function persistConversation() {
|
|
3761
|
+
conversationStore.save({
|
|
3762
|
+
turns: conversation,
|
|
3763
|
+
open: !root.classList.contains("lmw-collapsed"),
|
|
3764
|
+
model: MODEL
|
|
3765
|
+
});
|
|
3766
|
+
}
|
|
3767
|
+
var actionsSchemaEl = document.getElementById(ACTIONS_SCHEMA_ID);
|
|
3768
|
+
var localTools = actionsSchemaEl ? JSON.parse(actionsSchemaEl.textContent) : [];
|
|
3769
|
+
var remoteToolsEl = document.getElementById(REMOTE_TOOLS_SCHEMA_ID);
|
|
3770
|
+
var remoteTools = remoteToolsEl ? JSON.parse(remoteToolsEl.textContent) : [];
|
|
3771
|
+
var hubMcpServers = [];
|
|
3772
|
+
var selectedToolIds = /* @__PURE__ */ new Set();
|
|
3773
|
+
function anyAllowedByAllowlist(name, allowlist) {
|
|
3774
|
+
return allowlist === null || allowlist.indexOf(name) >= 0;
|
|
3775
|
+
}
|
|
3776
|
+
function refreshRemoteToolsFromPicker() {
|
|
3777
|
+
var flat = [];
|
|
3778
|
+
hubMcpServers.forEach(function(s) {
|
|
3779
|
+
(s.tools || []).forEach(function(t) {
|
|
3780
|
+
if (!selectedToolIds.has(t.id)) return;
|
|
3781
|
+
flat.push({
|
|
3782
|
+
id: t.id,
|
|
3783
|
+
name: t.name,
|
|
3784
|
+
description: t.description,
|
|
3785
|
+
input_schema: t.input_schema
|
|
3786
|
+
});
|
|
3787
|
+
});
|
|
3788
|
+
});
|
|
3789
|
+
remoteTools = flat;
|
|
3790
|
+
if (toolsCountEl) {
|
|
3791
|
+
toolsCountEl.textContent = flat.length;
|
|
3792
|
+
toolsCountEl.classList.toggle("lmw-tools-count-zero", flat.length === 0);
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
3795
|
+
function refreshServerBulkCheckbox(server, bulkCheckbox) {
|
|
3796
|
+
var tools = server.tools || [];
|
|
3797
|
+
var n = tools.length;
|
|
3798
|
+
if (n === 0) {
|
|
3799
|
+
bulkCheckbox.checked = false;
|
|
3800
|
+
bulkCheckbox.indeterminate = false;
|
|
3801
|
+
return;
|
|
3802
|
+
}
|
|
3803
|
+
var onCount = 0;
|
|
3804
|
+
tools.forEach(function(t) {
|
|
3805
|
+
if (selectedToolIds.has(t.id)) onCount++;
|
|
3806
|
+
});
|
|
3807
|
+
bulkCheckbox.checked = onCount === n;
|
|
3808
|
+
bulkCheckbox.indeterminate = onCount > 0 && onCount < n;
|
|
3809
|
+
}
|
|
3810
|
+
async function loadHubResourcesForPickers() {
|
|
3811
|
+
var tasks = [];
|
|
3812
|
+
if (ENABLE_MODEL_PICKER && modelPicker && LLM_PROVIDER === "ollama") {
|
|
3813
|
+
tasks.push(fetchOllamaModels({ baseUrl: LLM_BASE }).then(function(names) {
|
|
3814
|
+
var flat = names.filter(function(n) {
|
|
3815
|
+
return anyAllowedByAllowlist(n, MODEL_ALLOWLIST);
|
|
3816
|
+
}).map(function(n) {
|
|
3817
|
+
return { value: n, label: n };
|
|
3818
|
+
});
|
|
3819
|
+
if (!flat.some(function(m) {
|
|
3820
|
+
return m.value === MODEL;
|
|
3821
|
+
})) {
|
|
3822
|
+
flat.unshift({ value: MODEL, label: MODEL });
|
|
3823
|
+
}
|
|
3824
|
+
modelPicker.innerHTML = "";
|
|
3825
|
+
flat.forEach(function(m) {
|
|
3826
|
+
var opt = document.createElement("option");
|
|
3827
|
+
opt.value = m.value;
|
|
3828
|
+
opt.textContent = m.label;
|
|
3829
|
+
if (m.value === MODEL) opt.selected = true;
|
|
3830
|
+
modelPicker.appendChild(opt);
|
|
3831
|
+
});
|
|
3832
|
+
if (flat.length > 1) modelPicker.style.display = "";
|
|
3833
|
+
}));
|
|
3834
|
+
} else if (ENABLE_MODEL_PICKER && modelPicker) {
|
|
3835
|
+
tasks.push(fetch(LLM_BASE + "/api/llms", { headers: { "Accept": "application/json" } }).then(function(r) {
|
|
3836
|
+
return r.ok ? r.json() : { llms: [] };
|
|
3837
|
+
}).then(function(payload) {
|
|
3838
|
+
var flat = [];
|
|
3839
|
+
(payload.llms || []).forEach(function(llm) {
|
|
3840
|
+
if (llm.family !== "ollama") return;
|
|
3841
|
+
(llm.available_models || []).forEach(function(m) {
|
|
3842
|
+
if (anyAllowedByAllowlist(m.value, MODEL_ALLOWLIST)) {
|
|
3843
|
+
flat.push({ value: m.value, label: m.label });
|
|
3844
|
+
}
|
|
3845
|
+
});
|
|
3846
|
+
});
|
|
3847
|
+
if (!flat.some(function(m) {
|
|
3848
|
+
return m.value === MODEL;
|
|
3849
|
+
})) {
|
|
3850
|
+
flat.unshift({ value: MODEL, label: MODEL });
|
|
3851
|
+
}
|
|
3852
|
+
modelPicker.innerHTML = "";
|
|
3853
|
+
flat.forEach(function(m) {
|
|
3854
|
+
var opt = document.createElement("option");
|
|
3855
|
+
opt.value = m.value;
|
|
3856
|
+
opt.textContent = m.label;
|
|
3857
|
+
if (m.value === MODEL) opt.selected = true;
|
|
3858
|
+
modelPicker.appendChild(opt);
|
|
3859
|
+
});
|
|
3860
|
+
if (flat.length > 1) modelPicker.style.display = "";
|
|
3861
|
+
}).catch(function(e) {
|
|
3862
|
+
console.warn("[widget] model list fetch failed:", e);
|
|
3863
|
+
}));
|
|
3864
|
+
}
|
|
3865
|
+
if (ENABLE_TOOL_PICKER && toolsListEl) {
|
|
3866
|
+
tasks.push(fetch(TOOL_HUB_BASE + "/api/mcp_servers", { headers: { "Accept": "application/json" } }).then(function(r) {
|
|
3867
|
+
return r.ok ? r.json() : { mcp_servers: [] };
|
|
3868
|
+
}).then(function(payload) {
|
|
3869
|
+
hubMcpServers = (payload.mcp_servers || []).filter(function(s) {
|
|
3870
|
+
return anyAllowedByAllowlist(s.name, HUB_TOOLS_ALLOWLIST);
|
|
3871
|
+
});
|
|
3872
|
+
toolsListEl.innerHTML = "";
|
|
3873
|
+
if (hubMcpServers.length === 0) {
|
|
3874
|
+
var empty = document.createElement("div");
|
|
3875
|
+
empty.className = "lmw-tools-empty";
|
|
3876
|
+
empty.textContent = "No hub-registered tools available.";
|
|
3877
|
+
toolsListEl.appendChild(empty);
|
|
3878
|
+
return;
|
|
3879
|
+
}
|
|
3880
|
+
hubMcpServers.forEach(function(s) {
|
|
3881
|
+
var serverBlock = document.createElement("div");
|
|
3882
|
+
serverBlock.className = "lmw-tools-server";
|
|
3883
|
+
var headerRow = document.createElement("div");
|
|
3884
|
+
headerRow.className = "lmw-tools-server-row";
|
|
3885
|
+
var bulkCb = document.createElement("input");
|
|
3886
|
+
bulkCb.type = "checkbox";
|
|
3887
|
+
bulkCb.className = "lmw-tools-server-bulk";
|
|
3888
|
+
bulkCb.title = "Enable all tools on this server";
|
|
3889
|
+
bulkCb.addEventListener("click", function(e) {
|
|
3890
|
+
e.stopPropagation();
|
|
3891
|
+
});
|
|
3892
|
+
bulkCb.addEventListener("change", function() {
|
|
3893
|
+
var shouldOn = bulkCb.checked;
|
|
3894
|
+
(s.tools || []).forEach(function(t) {
|
|
3895
|
+
if (shouldOn) selectedToolIds.add(t.id);
|
|
3896
|
+
else selectedToolIds.delete(t.id);
|
|
3897
|
+
});
|
|
3898
|
+
bulkCb.indeterminate = false;
|
|
3899
|
+
childList.querySelectorAll('input[type="checkbox"]').forEach(function(cb) {
|
|
3900
|
+
cb.checked = shouldOn;
|
|
3901
|
+
});
|
|
3902
|
+
refreshRemoteToolsFromPicker();
|
|
3903
|
+
});
|
|
3904
|
+
var nameSpan = document.createElement("span");
|
|
3905
|
+
nameSpan.className = "lmw-tools-server-name";
|
|
3906
|
+
var toolCount = (s.tools || []).length;
|
|
3907
|
+
nameSpan.textContent = s.name + " (" + toolCount + " tool" + (toolCount === 1 ? "" : "s") + ")";
|
|
3908
|
+
var caret2 = document.createElement("span");
|
|
3909
|
+
caret2.className = "lmw-tools-server-caret";
|
|
3910
|
+
caret2.textContent = "\u25B8";
|
|
3911
|
+
headerRow.appendChild(bulkCb);
|
|
3912
|
+
headerRow.appendChild(nameSpan);
|
|
3913
|
+
headerRow.appendChild(caret2);
|
|
3914
|
+
var childList = document.createElement("div");
|
|
3915
|
+
childList.className = "lmw-tools-server-children";
|
|
3916
|
+
childList.style.display = "none";
|
|
3917
|
+
(s.tools || []).forEach(function(t) {
|
|
3918
|
+
var toolLabel = document.createElement("label");
|
|
3919
|
+
toolLabel.className = "lmw-tools-item lmw-tools-item-child";
|
|
3920
|
+
toolLabel.title = t.description || t.name;
|
|
3921
|
+
var cb = document.createElement("input");
|
|
3922
|
+
cb.type = "checkbox";
|
|
3923
|
+
cb.value = t.id;
|
|
3924
|
+
cb.checked = selectedToolIds.has(t.id);
|
|
3925
|
+
cb.addEventListener("change", function() {
|
|
3926
|
+
if (cb.checked) selectedToolIds.add(t.id);
|
|
3927
|
+
else selectedToolIds.delete(t.id);
|
|
3928
|
+
refreshServerBulkCheckbox(s, bulkCb);
|
|
3929
|
+
refreshRemoteToolsFromPicker();
|
|
3930
|
+
});
|
|
3931
|
+
var span = document.createElement("span");
|
|
3932
|
+
span.textContent = t.name;
|
|
3933
|
+
toolLabel.appendChild(cb);
|
|
3934
|
+
toolLabel.appendChild(span);
|
|
3935
|
+
childList.appendChild(toolLabel);
|
|
3936
|
+
});
|
|
3937
|
+
headerRow.addEventListener("click", function() {
|
|
3938
|
+
var isOpen = childList.style.display !== "none";
|
|
3939
|
+
childList.style.display = isOpen ? "none" : "block";
|
|
3940
|
+
caret2.textContent = isOpen ? "\u25B8" : "\u25BE";
|
|
3941
|
+
});
|
|
3942
|
+
serverBlock.appendChild(headerRow);
|
|
3943
|
+
serverBlock.appendChild(childList);
|
|
3944
|
+
toolsListEl.appendChild(serverBlock);
|
|
3945
|
+
refreshServerBulkCheckbox(s, bulkCb);
|
|
3946
|
+
});
|
|
3947
|
+
refreshRemoteToolsFromPicker();
|
|
3948
|
+
}).catch(function(e) {
|
|
3949
|
+
console.warn("[widget] MCP server list fetch failed:", e);
|
|
3950
|
+
}));
|
|
3951
|
+
}
|
|
3952
|
+
await Promise.all(tasks);
|
|
3953
|
+
}
|
|
3954
|
+
if (modelPicker) {
|
|
3955
|
+
modelPicker.addEventListener("change", function() {
|
|
3956
|
+
MODEL = modelPicker.value;
|
|
3957
|
+
});
|
|
3958
|
+
}
|
|
3959
|
+
if (toolsPicker) {
|
|
3960
|
+
document.addEventListener("click", function(e) {
|
|
3961
|
+
if (!toolsPicker.open) return;
|
|
3962
|
+
if (toolsPicker.contains(e.target)) return;
|
|
3963
|
+
toolsPicker.open = false;
|
|
3964
|
+
});
|
|
3965
|
+
}
|
|
3966
|
+
var pickerLoaded = false;
|
|
3967
|
+
async function ensurePickerLoaded() {
|
|
3968
|
+
if (pickerLoaded) return;
|
|
3969
|
+
pickerLoaded = true;
|
|
3970
|
+
try {
|
|
3971
|
+
await loadHubResourcesForPickers();
|
|
3972
|
+
} catch (e) {
|
|
3973
|
+
pickerLoaded = false;
|
|
3974
|
+
throw e;
|
|
3975
|
+
}
|
|
3976
|
+
}
|
|
3977
|
+
var hostWideTools = [];
|
|
3978
|
+
var hostWidePrompts = [];
|
|
3979
|
+
var resourceContext = null;
|
|
3980
|
+
var resourcePlan = null;
|
|
3981
|
+
var RESOURCE_BUDGET_BYTES = 8e3;
|
|
3982
|
+
restoreConversation();
|
|
3983
|
+
var wellKnownReady = (async function() {
|
|
3984
|
+
var urls = WELL_KNOWN_URLS === null ? [window.location.origin + "/.well-known/mcp.json"] : WELL_KNOWN_URLS;
|
|
3985
|
+
for (var i = 0; i < urls.length; i++) {
|
|
3986
|
+
var tools = await fetchMcpManifest(urls[i]);
|
|
3987
|
+
hostWideTools = hostWideTools.concat(tools);
|
|
3988
|
+
}
|
|
3989
|
+
var endpoints = [];
|
|
3990
|
+
hostWideTools.forEach(function(t) {
|
|
3991
|
+
if (t.endpoint && endpoints.indexOf(t.endpoint) === -1) endpoints.push(t.endpoint);
|
|
3992
|
+
});
|
|
3993
|
+
for (var j = 0; j < endpoints.length; j++) {
|
|
3994
|
+
var endpoint = endpoints[j];
|
|
3995
|
+
var prompts = await listMcpPrompts({ endpoint });
|
|
3996
|
+
hostWidePrompts = hostWidePrompts.concat(prompts);
|
|
3997
|
+
if (resourcePlan === null) {
|
|
3998
|
+
var loaded = await loadHostResource({
|
|
3999
|
+
endpoint,
|
|
4000
|
+
budgetBytes: RESOURCE_BUDGET_BYTES,
|
|
4001
|
+
list: listMcpResources,
|
|
4002
|
+
read: readMcpResource,
|
|
4003
|
+
onSkip: function(plan) {
|
|
4004
|
+
console.info("[llm_meta_widget] not attaching " + plan.uri + " (" + plan.reason + ", sizeBytes=" + plan.sizeBytes + ")");
|
|
4005
|
+
}
|
|
4006
|
+
});
|
|
4007
|
+
if (loaded && loaded.plan && loaded.plan.fetch) {
|
|
4008
|
+
resourceContext = loaded.context;
|
|
4009
|
+
resourcePlan = loaded.plan;
|
|
4010
|
+
}
|
|
4011
|
+
}
|
|
4012
|
+
}
|
|
4013
|
+
renderPromptButtons();
|
|
4014
|
+
renderWelcome();
|
|
4015
|
+
})();
|
|
4016
|
+
var promptsEl = root.querySelector(".lmw-prompts");
|
|
4017
|
+
function renderPromptButtons() {
|
|
4018
|
+
if (!promptsEl) return;
|
|
4019
|
+
promptsEl.textContent = "";
|
|
4020
|
+
if (!hostWidePrompts.length) {
|
|
4021
|
+
promptsEl.style.display = "none";
|
|
4022
|
+
return;
|
|
4023
|
+
}
|
|
4024
|
+
hostWidePrompts.forEach(function(prompt) {
|
|
4025
|
+
var props = promptButtonProps(prompt);
|
|
4026
|
+
var button = document.createElement("button");
|
|
4027
|
+
button.type = "button";
|
|
4028
|
+
button.className = "lmw-prompt";
|
|
4029
|
+
button.textContent = props.label;
|
|
4030
|
+
button.title = props.title;
|
|
4031
|
+
button.addEventListener("click", function() {
|
|
4032
|
+
runPromptTemplate(prompt, button);
|
|
4033
|
+
});
|
|
4034
|
+
promptsEl.appendChild(button);
|
|
4035
|
+
});
|
|
4036
|
+
promptsEl.style.display = "";
|
|
4037
|
+
}
|
|
4038
|
+
function restoreConversation() {
|
|
4039
|
+
var saved = conversationStore.load();
|
|
4040
|
+
if (!saved || !saved.turns.length) return false;
|
|
4041
|
+
conversation = saved.turns;
|
|
4042
|
+
saved.turns.forEach(function(turn) {
|
|
4043
|
+
var body = appendTurn(turn.role, turn.role === "assistant" ? "" : turn.content);
|
|
4044
|
+
if (turn.role === "assistant") renderMarkdownInto(body, turn.content || "");
|
|
4045
|
+
if (!turn.tools || !turn.tools.length) return;
|
|
4046
|
+
var row = document.createElement("div");
|
|
4047
|
+
row.className = "lmw-tool-chips";
|
|
4048
|
+
turn.tools.forEach(function(tool) {
|
|
4049
|
+
var chip = document.createElement("span");
|
|
4050
|
+
chip.className = "lmw-tool-chip" + (tool.error ? " error" : "");
|
|
4051
|
+
chip.textContent = (tool.error ? "\u274C " : "\u{1F527} ") + tool.name;
|
|
4052
|
+
row.appendChild(chip);
|
|
4053
|
+
});
|
|
4054
|
+
if (body.parentNode) body.parentNode.appendChild(row);
|
|
4055
|
+
});
|
|
4056
|
+
historyEl.scrollTop = historyEl.scrollHeight;
|
|
4057
|
+
return true;
|
|
4058
|
+
}
|
|
4059
|
+
function renderWelcome() {
|
|
4060
|
+
if (!historyEl || historyEl.querySelector(".message")) return;
|
|
4061
|
+
historyEl.textContent = "";
|
|
4062
|
+
var box = document.createElement("div");
|
|
4063
|
+
box.className = "lmw-welcome";
|
|
4064
|
+
var hello = document.createElement("p");
|
|
4065
|
+
hello.className = "lmw-welcome-hello";
|
|
4066
|
+
hello.textContent = GREETING || "Hi \u2014 I can work this page for you. Tell me what you need in your own words" + (hostWidePrompts.length ? ", or start with one of these:" : ".");
|
|
4067
|
+
box.appendChild(hello);
|
|
4068
|
+
var state = window[STATE_GLOBAL] || {};
|
|
4069
|
+
hostWidePrompts.forEach(function(prompt) {
|
|
4070
|
+
var props = promptButtonProps(prompt);
|
|
4071
|
+
var card = document.createElement("div");
|
|
4072
|
+
card.className = "lmw-welcome-card";
|
|
4073
|
+
var start = document.createElement("button");
|
|
4074
|
+
start.type = "button";
|
|
4075
|
+
start.className = "lmw-welcome-start";
|
|
4076
|
+
start.textContent = props.label;
|
|
4077
|
+
start.addEventListener("click", function() {
|
|
4078
|
+
runPromptTemplate(prompt, start);
|
|
4079
|
+
});
|
|
4080
|
+
card.appendChild(start);
|
|
4081
|
+
if (prompt.description) {
|
|
4082
|
+
var why = document.createElement("p");
|
|
4083
|
+
why.className = "lmw-welcome-why";
|
|
4084
|
+
why.textContent = prompt.description;
|
|
4085
|
+
card.appendChild(why);
|
|
4086
|
+
}
|
|
4087
|
+
var summary = promptArgumentSummary(prompt, state);
|
|
4088
|
+
if (summary.length) {
|
|
4089
|
+
var slots = document.createElement("ul");
|
|
4090
|
+
slots.className = "lmw-welcome-slots";
|
|
4091
|
+
summary.forEach(function(slot) {
|
|
4092
|
+
var li = document.createElement("li");
|
|
4093
|
+
li.className = slot.filled ? "filled" : "empty";
|
|
4094
|
+
li.textContent = slot.filled ? slot.name + ": " + (slot.value.length > 60 ? slot.value.slice(0, 60) + "\u2026" : slot.value) : slot.name + ": not set yet \u2014 I'll ask, or work it out";
|
|
4095
|
+
slots.appendChild(li);
|
|
4096
|
+
});
|
|
4097
|
+
card.appendChild(slots);
|
|
4098
|
+
}
|
|
4099
|
+
box.appendChild(card);
|
|
4100
|
+
});
|
|
4101
|
+
historyEl.appendChild(box);
|
|
4102
|
+
}
|
|
4103
|
+
async function runPromptTemplate(prompt, button) {
|
|
4104
|
+
var resolved = resolvePromptArguments(prompt, window[STATE_GLOBAL] || {});
|
|
4105
|
+
var args = resolved.args;
|
|
4106
|
+
var missing = resolved.missing;
|
|
4107
|
+
if (missing.length) {
|
|
4108
|
+
appendTurn("error", 'Cannot run "' + (prompt.title || prompt.name) + '" yet \u2014 nothing to use for: ' + missing.join(", ") + ". Fill that in on the page first.");
|
|
4109
|
+
return;
|
|
4110
|
+
}
|
|
4111
|
+
button.disabled = true;
|
|
4112
|
+
try {
|
|
4113
|
+
var result = await getMcpPrompt({ endpoint: prompt.endpoint, name: prompt.name, args });
|
|
4114
|
+
var text = promptMessagesToText(result);
|
|
4115
|
+
if (!text) {
|
|
4116
|
+
appendTurn("error", "The server returned an empty prompt.");
|
|
4117
|
+
return;
|
|
4118
|
+
}
|
|
4119
|
+
inputEl.value = text;
|
|
4120
|
+
pendingTurnLabel = promptButtonProps(prompt).label;
|
|
4121
|
+
if (typeof formEl.requestSubmit === "function") formEl.requestSubmit();
|
|
4122
|
+
else formEl.dispatchEvent(new Event("submit", { cancelable: true }));
|
|
4123
|
+
} catch (e) {
|
|
4124
|
+
appendTurn("error", "Prompt failed: " + (e && e.message ? e.message : e));
|
|
4125
|
+
} finally {
|
|
4126
|
+
button.disabled = false;
|
|
4127
|
+
}
|
|
4128
|
+
}
|
|
4129
|
+
var pendingTurnLabel = null;
|
|
4130
|
+
function appendTurn(role, text, turnLabel) {
|
|
4131
|
+
var welcome = historyEl.querySelector(".lmw-welcome");
|
|
4132
|
+
if (welcome) welcome.remove();
|
|
4133
|
+
var div = document.createElement("div");
|
|
4134
|
+
div.className = "message " + role;
|
|
4135
|
+
var label = document.createElement("div");
|
|
4136
|
+
label.className = "message-role";
|
|
4137
|
+
label.textContent = roleLabel(role);
|
|
4138
|
+
var body = document.createElement("div");
|
|
4139
|
+
body.className = "message-content";
|
|
4140
|
+
if (turnLabel) {
|
|
4141
|
+
var details = document.createElement("details");
|
|
4142
|
+
var summary = document.createElement("summary");
|
|
4143
|
+
summary.textContent = turnLabel;
|
|
4144
|
+
var full = document.createElement("div");
|
|
4145
|
+
full.className = "lmw-sent-text";
|
|
4146
|
+
full.textContent = text;
|
|
4147
|
+
details.appendChild(summary);
|
|
4148
|
+
details.appendChild(full);
|
|
4149
|
+
body.appendChild(details);
|
|
4150
|
+
} else {
|
|
4151
|
+
body.textContent = text;
|
|
4152
|
+
}
|
|
4153
|
+
div.appendChild(label);
|
|
4154
|
+
div.appendChild(document.createTextNode(" "));
|
|
4155
|
+
div.appendChild(body);
|
|
4156
|
+
historyEl.appendChild(div);
|
|
4157
|
+
historyEl.scrollTop = historyEl.scrollHeight;
|
|
4158
|
+
body.roleLabel = label;
|
|
4159
|
+
return body;
|
|
4160
|
+
}
|
|
4161
|
+
function renderMarkdownInto(el, mdText) {
|
|
4162
|
+
try {
|
|
4163
|
+
el.innerHTML = marked.parse(mdText);
|
|
4164
|
+
} catch (e) {
|
|
4165
|
+
el.textContent = mdText;
|
|
4166
|
+
}
|
|
4167
|
+
}
|
|
4168
|
+
function roleLabel(role) {
|
|
4169
|
+
if (role === "user") return "\u{1F464} You";
|
|
4170
|
+
if (role === "assistant") return "\u{1F916} " + MODEL;
|
|
4171
|
+
if (role === "system") return "\u2022 system";
|
|
4172
|
+
if (role === "error") return "\u26A0 error";
|
|
4173
|
+
return role;
|
|
4174
|
+
}
|
|
4175
|
+
function markWorking(label) {
|
|
4176
|
+
if (!label || label.classList.contains("is-working")) return;
|
|
4177
|
+
label.innerHTML = '<span class="role-spinner" aria-hidden="true">\u2699\uFE0F</span> Working\u2026';
|
|
4178
|
+
label.classList.add("is-working");
|
|
4179
|
+
}
|
|
4180
|
+
function markDone(label) {
|
|
4181
|
+
if (!label || !label.classList.contains("is-working")) return;
|
|
4182
|
+
label.classList.remove("is-working");
|
|
4183
|
+
label.textContent = roleLabel("assistant");
|
|
4184
|
+
}
|
|
4185
|
+
var liveChips = null;
|
|
4186
|
+
var pendingChips = [];
|
|
4187
|
+
function chipRow() {
|
|
4188
|
+
if (liveChips && liveChips.parentNode) return liveChips;
|
|
4189
|
+
if (!activeAssistantBody || !activeAssistantBody.parentNode) return null;
|
|
4190
|
+
liveChips = document.createElement("div");
|
|
4191
|
+
liveChips.className = "lmw-tool-chips";
|
|
4192
|
+
activeAssistantBody.parentNode.appendChild(liveChips);
|
|
4193
|
+
return liveChips;
|
|
4194
|
+
}
|
|
4195
|
+
function announceToolCall(toolCall) {
|
|
4196
|
+
var row = chipRow();
|
|
4197
|
+
if (!row) return;
|
|
4198
|
+
var chip = document.createElement("span");
|
|
4199
|
+
chip.className = "lmw-tool-chip running";
|
|
4200
|
+
chip.textContent = "\u23F3 " + toolCall.name;
|
|
4201
|
+
chip.dataset.tool = toolCall.name;
|
|
4202
|
+
row.appendChild(chip);
|
|
4203
|
+
pendingChips.push(chip);
|
|
4204
|
+
historyEl.scrollTop = historyEl.scrollHeight;
|
|
4205
|
+
}
|
|
4206
|
+
function resolveToolCall(outcome) {
|
|
4207
|
+
var idx = pendingChips.findIndex(function(c) {
|
|
4208
|
+
return c.dataset.tool === outcome.toolCall.name;
|
|
4209
|
+
});
|
|
4210
|
+
var chip = idx === -1 ? null : pendingChips.splice(idx, 1)[0];
|
|
4211
|
+
if (!chip) return;
|
|
4212
|
+
chip.className = "lmw-tool-chip" + (outcome.error ? " error" : "");
|
|
4213
|
+
chip.textContent = (outcome.error ? "\u274C " : "\u{1F527} ") + outcome.toolCall.name;
|
|
4214
|
+
if (outcome.error) chip.title = outcome.error.message || String(outcome.error);
|
|
4215
|
+
}
|
|
4216
|
+
var currentThinkingBlock = null;
|
|
4217
|
+
var currentThinkingBody = null;
|
|
4218
|
+
var activeAssistantBody = null;
|
|
4219
|
+
function ensureThinkingBlock() {
|
|
4220
|
+
if (currentThinkingBody) return currentThinkingBody;
|
|
4221
|
+
var details = document.createElement("details");
|
|
4222
|
+
details.className = "message-thinking";
|
|
4223
|
+
details.open = true;
|
|
4224
|
+
details.classList.add("thinking-active");
|
|
4225
|
+
var summary = document.createElement("summary");
|
|
4226
|
+
summary.textContent = "\u{1F914} thinking\u2026";
|
|
4227
|
+
var dots = document.createElement("span");
|
|
4228
|
+
dots.className = "thinking-dots";
|
|
4229
|
+
for (var i = 0; i < 3; i++) {
|
|
4230
|
+
var dot = document.createElement("span");
|
|
4231
|
+
dot.textContent = ".";
|
|
4232
|
+
dots.appendChild(dot);
|
|
4233
|
+
}
|
|
4234
|
+
summary.appendChild(dots);
|
|
4235
|
+
var body = document.createElement("div");
|
|
4236
|
+
body.className = "message-thinking-content";
|
|
4237
|
+
details.appendChild(summary);
|
|
4238
|
+
details.appendChild(body);
|
|
4239
|
+
if (activeAssistantBody && activeAssistantBody.parentNode) {
|
|
4240
|
+
activeAssistantBody.parentNode.insertBefore(details, activeAssistantBody);
|
|
4241
|
+
} else {
|
|
4242
|
+
historyEl.appendChild(details);
|
|
4243
|
+
}
|
|
4244
|
+
historyEl.scrollTop = historyEl.scrollHeight;
|
|
4245
|
+
currentThinkingBlock = details;
|
|
4246
|
+
currentThinkingBody = body;
|
|
4247
|
+
return body;
|
|
4248
|
+
}
|
|
4249
|
+
function collapseThinkingBlock() {
|
|
4250
|
+
if (currentThinkingBlock) {
|
|
4251
|
+
currentThinkingBlock.classList.remove("thinking-active");
|
|
4252
|
+
currentThinkingBlock.open = false;
|
|
4253
|
+
var summary = currentThinkingBlock.querySelector("summary");
|
|
4254
|
+
if (summary) summary.textContent = "\u{1F914} thinking (finished)";
|
|
4255
|
+
}
|
|
4256
|
+
currentThinkingBlock = null;
|
|
4257
|
+
currentThinkingBody = null;
|
|
4258
|
+
}
|
|
4259
|
+
function currentPageState() {
|
|
4260
|
+
var reader = window[STATE_GLOBAL] || {};
|
|
4261
|
+
var out = {};
|
|
4262
|
+
Object.keys(reader).forEach(function(k) {
|
|
4263
|
+
try {
|
|
4264
|
+
out[k] = reader[k]();
|
|
4265
|
+
} catch (e) {
|
|
4266
|
+
out[k] = "<error: " + e.message + ">";
|
|
4267
|
+
}
|
|
4268
|
+
});
|
|
4269
|
+
return out;
|
|
4270
|
+
}
|
|
4271
|
+
function currentSystemPrompt(resourceLines) {
|
|
4272
|
+
return [
|
|
4273
|
+
"You are integrated into a web page as an AI assistant. You have tools available to change page state or fetch information.",
|
|
4274
|
+
"",
|
|
4275
|
+
"RULES for tool use:",
|
|
4276
|
+
"1. If the user's question can be answered from the Current page state below, answer directly with a plain-text response \u2014 do NOT invoke a tool.",
|
|
4277
|
+
'2. If the user requests a state change, or needs information not in the page state, invoke the matching tool via a function call. Do not describe your intent in text without actually invoking (a textual promise like "I will add X" is a failure).',
|
|
4278
|
+
"3. After a tool returns a result, use it: either take the next step the task needs, or \u2014 if the task is done \u2014 answer in plain text. Do not stop silently after a tool call.",
|
|
4279
|
+
"4. NEVER repeat a call you have already made with the same or similar arguments \u2014 its result is already in the conversation history.",
|
|
4280
|
+
"",
|
|
4281
|
+
"Current page state:",
|
|
4282
|
+
JSON.stringify(currentPageState(), null, 2)
|
|
4283
|
+
].concat(resourceLines || []).join("\n");
|
|
4284
|
+
}
|
|
4285
|
+
async function resourceLinesForThisTurn() {
|
|
4286
|
+
var turn = await resourceLinesForTurn({
|
|
4287
|
+
plan: resourcePlan,
|
|
4288
|
+
cached: resourceContext,
|
|
4289
|
+
endpoint: resourcePlan && resourcePlan.endpoint,
|
|
4290
|
+
read: readMcpResource,
|
|
4291
|
+
budgetBytes: RESOURCE_BUDGET_BYTES
|
|
4292
|
+
});
|
|
4293
|
+
resourceContext = turn.cached;
|
|
4294
|
+
return turn.lines;
|
|
4295
|
+
}
|
|
4296
|
+
var currentAbort = null;
|
|
4297
|
+
clearBtn.addEventListener("click", function() {
|
|
4298
|
+
if (currentAbort) {
|
|
4299
|
+
try {
|
|
4300
|
+
currentAbort.abort();
|
|
4301
|
+
} catch (e) {
|
|
4302
|
+
}
|
|
4303
|
+
}
|
|
4304
|
+
conversation = [];
|
|
4305
|
+
conversationStore.clear();
|
|
4306
|
+
historyEl.innerHTML = "";
|
|
4307
|
+
renderWelcome();
|
|
4308
|
+
});
|
|
4309
|
+
inputEl.addEventListener("keydown", function(e) {
|
|
4310
|
+
if (e.key === "Enter" && !e.shiftKey && !e.isComposing) {
|
|
4311
|
+
e.preventDefault();
|
|
4312
|
+
if (typeof formEl.requestSubmit === "function") formEl.requestSubmit();
|
|
4313
|
+
else formEl.dispatchEvent(new Event("submit", { cancelable: true }));
|
|
4314
|
+
}
|
|
4315
|
+
});
|
|
4316
|
+
formEl.addEventListener("submit", async function(event) {
|
|
4317
|
+
event.preventDefault();
|
|
4318
|
+
var userText = inputEl.value.trim();
|
|
4319
|
+
if (!userText) return;
|
|
4320
|
+
inputEl.value = "";
|
|
4321
|
+
appendTurn("user", userText, pendingTurnLabel);
|
|
4322
|
+
pendingTurnLabel = null;
|
|
4323
|
+
if (currentAbort) {
|
|
4324
|
+
try {
|
|
4325
|
+
currentAbort.abort();
|
|
4326
|
+
} catch (e) {
|
|
4327
|
+
}
|
|
4328
|
+
}
|
|
4329
|
+
currentAbort = new AbortController();
|
|
4330
|
+
var assistantBody = appendTurn("assistant", "");
|
|
4331
|
+
activeAssistantBody = assistantBody;
|
|
4332
|
+
liveChips = null;
|
|
4333
|
+
pendingChips = [];
|
|
4334
|
+
markWorking(assistantBody.roleLabel);
|
|
4335
|
+
var assistantMarkdown = "";
|
|
4336
|
+
try {
|
|
4337
|
+
await wellKnownReady;
|
|
4338
|
+
var resourceLines = await resourceLinesForThisTurn();
|
|
4339
|
+
var messages = [{ role: "system", content: currentSystemPrompt(resourceLines) }].concat(conversation.map(function(t) {
|
|
4340
|
+
return { role: t.role, content: t.content };
|
|
4341
|
+
})).concat([{ role: "user", content: userText }]);
|
|
4342
|
+
var result = await runChatLoop({
|
|
4343
|
+
baseUrl: LLM_BASE,
|
|
4344
|
+
toolHubUrl: TOOL_HUB_BASE,
|
|
4345
|
+
apiKeyUuid: API_KEY_UUID,
|
|
4346
|
+
modelName: MODEL,
|
|
4347
|
+
messages,
|
|
4348
|
+
localTools,
|
|
4349
|
+
remoteTools,
|
|
4350
|
+
hostWideTools,
|
|
4351
|
+
aiActions: window[ACTIONS_GLOBAL] || {},
|
|
4352
|
+
maxRounds: MAX_ROUNDS,
|
|
4353
|
+
provider: LLM_PROVIDER === "ollama" ? "ollama" : "hub",
|
|
4354
|
+
signal: currentAbort.signal,
|
|
4355
|
+
onToolCall: function(toolCall) {
|
|
4356
|
+
announceToolCall(toolCall);
|
|
4357
|
+
},
|
|
4358
|
+
onToolDispatched: function(outcome) {
|
|
4359
|
+
resolveToolCall(outcome);
|
|
4360
|
+
},
|
|
4361
|
+
onPhase: function(name) {
|
|
4362
|
+
if (name === "responding") markDone(assistantBody.roleLabel);
|
|
4363
|
+
else markWorking(assistantBody.roleLabel);
|
|
4364
|
+
},
|
|
4365
|
+
onRoundStart: function(roundIdx) {
|
|
4366
|
+
markWorking(assistantBody.roleLabel);
|
|
4367
|
+
collapseThinkingBlock();
|
|
4368
|
+
},
|
|
4369
|
+
onThinkingDelta: function(delta) {
|
|
4370
|
+
var body = ensureThinkingBlock();
|
|
4371
|
+
body.appendChild(document.createTextNode(delta));
|
|
4372
|
+
body.scrollTop = body.scrollHeight;
|
|
4373
|
+
historyEl.scrollTop = historyEl.scrollHeight;
|
|
4374
|
+
},
|
|
4375
|
+
onTextDelta: function(delta) {
|
|
4376
|
+
markDone(assistantBody.roleLabel);
|
|
4377
|
+
collapseThinkingBlock();
|
|
4378
|
+
assistantMarkdown += delta;
|
|
4379
|
+
renderMarkdownInto(assistantBody, assistantMarkdown);
|
|
4380
|
+
historyEl.scrollTop = historyEl.scrollHeight;
|
|
4381
|
+
}
|
|
4382
|
+
});
|
|
4383
|
+
conversation.push({ role: "user", content: userText });
|
|
4384
|
+
conversation.push({
|
|
4385
|
+
role: "assistant",
|
|
4386
|
+
content: result.content,
|
|
4387
|
+
// Kept so a restored transcript still shows what ran — losing
|
|
4388
|
+
// that record is what made a navigating action unbearable.
|
|
4389
|
+
tools: result.dispatched.map(function(d) {
|
|
4390
|
+
return d.error ? { name: d.toolCall.name, error: true } : { name: d.toolCall.name };
|
|
4391
|
+
})
|
|
4392
|
+
});
|
|
4393
|
+
persistConversation();
|
|
4394
|
+
result.dispatched.forEach(function(d) {
|
|
4395
|
+
resolveToolCall(d);
|
|
4396
|
+
});
|
|
4397
|
+
pendingChips.forEach(function(chip) {
|
|
4398
|
+
chip.className = "lmw-tool-chip";
|
|
4399
|
+
chip.textContent = "\u{1F527} " + chip.dataset.tool;
|
|
4400
|
+
});
|
|
4401
|
+
pendingChips = [];
|
|
4402
|
+
if (result.skipped.length > 0) {
|
|
4403
|
+
appendTurn("system", "Not available on this page: " + result.skipped.map(function(t) {
|
|
4404
|
+
return t.name;
|
|
4405
|
+
}).join(", "));
|
|
4406
|
+
}
|
|
4407
|
+
if (result.stopped_reason && result.stopped_reason !== "duplicate_tool_calls") {
|
|
4408
|
+
appendTurn(
|
|
4409
|
+
"system",
|
|
4410
|
+
"The assistant reached its tool-use limit before finishing. The reply above may be incomplete \u2014 try asking again or rephrasing."
|
|
4411
|
+
);
|
|
4412
|
+
}
|
|
4413
|
+
} catch (err) {
|
|
4414
|
+
if (err.name === "AbortError" || /aborted/i.test(err.message || "")) {
|
|
4415
|
+
appendTurn("system", "\u23F9 stopped");
|
|
4416
|
+
} else {
|
|
4417
|
+
appendTurn("error", err.message);
|
|
4418
|
+
}
|
|
4419
|
+
} finally {
|
|
4420
|
+
markDone(assistantBody.roleLabel);
|
|
4421
|
+
collapseThinkingBlock();
|
|
4422
|
+
activeAssistantBody = null;
|
|
4423
|
+
currentAbort = null;
|
|
4424
|
+
}
|
|
4425
|
+
});
|
|
4426
|
+
}
|
|
4427
|
+
export {
|
|
4428
|
+
LlmMetaWidgetElement
|
|
4429
|
+
};
|