@linxin666/dsh-remote-web-ui 0.1.17 → 0.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +5 -2
- package/README.zh.md +2 -2
- package/lib/client.js +82 -66
- package/lib/client.js.map +1 -1
- package/lib/index.js +18 -18
- package/lib/mobile.js +649 -45
- package/lib/mobile.js.map +1 -1
- package/lib/types/client/PluginSettingsCard.d.ts +11 -0
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PluginSettingsCard.js +11 -5
- package/lib/types/client/RemoteSettingsCard.d.ts +5 -0
- package/lib/types/client/RemoteSettingsCard.d.ts.map +1 -1
- package/lib/types/client/RemoteSettingsCard.js +7 -0
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +13 -7
- package/lib/types/client/settings-form.d.ts +8 -0
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/client/settings-form.js +15 -1
- package/lib/types/gate.d.ts +4 -9
- package/lib/types/gate.d.ts.map +1 -1
- package/lib/types/gate.js +5 -32
- package/lib/types/loopback.d.ts +25 -0
- package/lib/types/loopback.d.ts.map +1 -0
- package/lib/types/loopback.js +68 -0
- package/lib/types/mobile/markdown.d.ts +26 -0
- package/lib/types/mobile/markdown.d.ts.map +1 -0
- package/lib/types/mobile/markdown.js +272 -0
- package/lib/types/mobile/markdown.test.d.ts +2 -0
- package/lib/types/mobile/markdown.test.d.ts.map +1 -0
- package/lib/types/mobile/markdown.test.js +67 -0
- package/lib/types/mobile/messages.d.ts +14 -0
- package/lib/types/mobile/messages.d.ts.map +1 -1
- package/lib/types/mobile/messages.js +54 -2
- package/lib/types/mobile/messages.test.js +99 -0
- package/lib/types/mobile/mobile-styles.d.ts +1 -1
- package/lib/types/mobile/mobile-styles.d.ts.map +1 -1
- package/lib/types/mobile/mobile-styles.js +167 -0
- package/lib/types/mobile/views/ChatView.d.ts.map +1 -1
- package/lib/types/mobile/views/ChatView.js +81 -5
- package/lib/types/mobile/views/ChatView.test.js +105 -0
- package/package.json +1 -1
- package/src/client/PluginSettingsCard.tsx +50 -47
- package/src/client/RemoteSettingsCard.tsx +8 -0
- package/src/client/index.ts +13 -7
- package/src/client/settings-card.module.css +9 -0
- package/src/client/settings-form.ts +15 -1
- package/src/gate.ts +5 -30
- package/src/loopback.ts +63 -0
- package/src/mobile/markdown.test.ts +77 -0
- package/src/mobile/markdown.ts +278 -0
- package/src/mobile/messages.test.ts +106 -0
- package/src/mobile/messages.ts +66 -2
- package/src/mobile/mobile-styles.ts +167 -0
- package/src/mobile/views/ChatView.test.tsx +121 -0
- package/src/mobile/views/ChatView.tsx +164 -6
package/lib/mobile.js
CHANGED
|
@@ -13151,6 +13151,11 @@ function applyEvent(state, event) {
|
|
|
13151
13151
|
case "tool/call":
|
|
13152
13152
|
applyToolCall(state, event);
|
|
13153
13153
|
break;
|
|
13154
|
+
case "request/context": {
|
|
13155
|
+
const window = pickNumber((isRecord$1(event.data) ? event.data : {})["contextWindow"]);
|
|
13156
|
+
if (window !== void 0) state.contextWindow = window;
|
|
13157
|
+
break;
|
|
13158
|
+
}
|
|
13154
13159
|
default: break;
|
|
13155
13160
|
}
|
|
13156
13161
|
}
|
|
@@ -13158,10 +13163,12 @@ function applyUserMessage(state, event) {
|
|
|
13158
13163
|
const data = isRecord$1(event.data) ? event.data : {};
|
|
13159
13164
|
const id = pickString(data["id"]) ?? syntheticId("user", event.seq);
|
|
13160
13165
|
const text = textFromContent(data["content"]);
|
|
13166
|
+
const sourceKind = pickString((isRecord$1(data["source"]) ? data["source"] : {})["kind"]);
|
|
13161
13167
|
const existing = state.byId.get(id);
|
|
13162
13168
|
if (existing !== void 0) {
|
|
13163
13169
|
replaceMessage(state, existing, {
|
|
13164
13170
|
...existing,
|
|
13171
|
+
...sourceKind !== void 0 ? { sourceKind } : {},
|
|
13165
13172
|
text,
|
|
13166
13173
|
seq: event.seq,
|
|
13167
13174
|
time: event.time
|
|
@@ -13172,6 +13179,7 @@ function applyUserMessage(state, event) {
|
|
|
13172
13179
|
id,
|
|
13173
13180
|
kind: "user",
|
|
13174
13181
|
text,
|
|
13182
|
+
...sourceKind !== void 0 ? { sourceKind } : {},
|
|
13175
13183
|
seq: event.seq,
|
|
13176
13184
|
time: event.time
|
|
13177
13185
|
};
|
|
@@ -13187,6 +13195,8 @@ function applyAssistantMessage(state, event) {
|
|
|
13187
13195
|
const finalText = textFromContent(messageData["content"]);
|
|
13188
13196
|
const finalReasoning = reasoningFromContent(messageData["content"]);
|
|
13189
13197
|
const key = tsKey(turn, step);
|
|
13198
|
+
const usage = usageFromData(data);
|
|
13199
|
+
const contextWindow = state.contextWindow;
|
|
13190
13200
|
let target = state.byId.get(id);
|
|
13191
13201
|
if (target === void 0 && key !== void 0) target = state.pendingByTurnStep.get(key);
|
|
13192
13202
|
if (target !== void 0) {
|
|
@@ -13195,6 +13205,8 @@ function applyAssistantMessage(state, event) {
|
|
|
13195
13205
|
id,
|
|
13196
13206
|
text: finalText,
|
|
13197
13207
|
...finalReasoning !== "" ? { reasoning: finalReasoning } : {},
|
|
13208
|
+
...usage !== void 0 ? { usage } : {},
|
|
13209
|
+
...usage !== void 0 && contextWindow !== void 0 ? { contextWindow } : {},
|
|
13198
13210
|
seq: event.seq,
|
|
13199
13211
|
time: event.time,
|
|
13200
13212
|
pending: false
|
|
@@ -13209,6 +13221,8 @@ function applyAssistantMessage(state, event) {
|
|
|
13209
13221
|
kind: "assistant",
|
|
13210
13222
|
text: finalText,
|
|
13211
13223
|
...finalReasoning !== "" ? { reasoning: finalReasoning } : {},
|
|
13224
|
+
...usage !== void 0 ? { usage } : {},
|
|
13225
|
+
...usage !== void 0 && contextWindow !== void 0 ? { contextWindow } : {},
|
|
13212
13226
|
seq: event.seq,
|
|
13213
13227
|
time: event.time
|
|
13214
13228
|
};
|
|
@@ -13220,6 +13234,27 @@ function applyAssistantMessage(state, event) {
|
|
|
13220
13234
|
}
|
|
13221
13235
|
if (turn !== void 0) state.messageTurn.set(id, turn);
|
|
13222
13236
|
}
|
|
13237
|
+
/**
|
|
13238
|
+
* Extract token usage from an assistant event payload. Only attaches when the
|
|
13239
|
+
* wire carries finite `inputTokens` AND `outputTokens`; the cache fields are
|
|
13240
|
+
* included only for finite numbers.
|
|
13241
|
+
*/
|
|
13242
|
+
function usageFromData(data) {
|
|
13243
|
+
const usageData = data["usage"];
|
|
13244
|
+
if (!isRecord$1(usageData)) return void 0;
|
|
13245
|
+
const inputTokens = pickNumber(usageData["inputTokens"]);
|
|
13246
|
+
const outputTokens = pickNumber(usageData["outputTokens"]);
|
|
13247
|
+
if (inputTokens === void 0 || outputTokens === void 0) return void 0;
|
|
13248
|
+
const usage = {
|
|
13249
|
+
inputTokens,
|
|
13250
|
+
outputTokens
|
|
13251
|
+
};
|
|
13252
|
+
const cacheReadTokens = pickNumber(usageData["cacheReadTokens"]);
|
|
13253
|
+
const cacheWriteTokens = pickNumber(usageData["cacheWriteTokens"]);
|
|
13254
|
+
if (cacheReadTokens !== void 0) usage.cacheReadTokens = cacheReadTokens;
|
|
13255
|
+
if (cacheWriteTokens !== void 0) usage.cacheWriteTokens = cacheWriteTokens;
|
|
13256
|
+
return usage;
|
|
13257
|
+
}
|
|
13223
13258
|
function applyChunk(state, event) {
|
|
13224
13259
|
const target = chunkTarget(event.data);
|
|
13225
13260
|
if (target === null) return;
|
|
@@ -13397,6 +13432,238 @@ function foldEvents(events, existing) {
|
|
|
13397
13432
|
return [...state.messages].sort((a, b) => a.seq - b.seq || (a.id < b.id ? -1 : 1));
|
|
13398
13433
|
}
|
|
13399
13434
|
//#endregion
|
|
13435
|
+
//#region src/mobile/markdown.ts
|
|
13436
|
+
/**
|
|
13437
|
+
* A compact GFM-subset markdown renderer for the mobile chat: headings,
|
|
13438
|
+
* paragraphs, fenced + inline code, bold/italic/strikethrough, links,
|
|
13439
|
+
* images, lists, blockquotes, hr, and tables. All HTML is escaped before
|
|
13440
|
+
* transformation — the output only ever contains the renderer's own tags.
|
|
13441
|
+
* Dependency-free on purpose (the mobile bundle stays at ~456 KB); the
|
|
13442
|
+
* escape-first + protocol allow-list design mirrors the desktop panel's
|
|
13443
|
+
* preview renderer (dsh-aionui-panel/src/client/preview/markdown.ts).
|
|
13444
|
+
* Pure and exported for tests.
|
|
13445
|
+
* @module dsh-remote-web-ui/mobile/markdown
|
|
13446
|
+
*/
|
|
13447
|
+
/** Escape HTML special characters. */
|
|
13448
|
+
function escapeHtml(text) {
|
|
13449
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
13450
|
+
}
|
|
13451
|
+
/**
|
|
13452
|
+
* Guard a raw link/image target against dangerous protocols. Returns the
|
|
13453
|
+
* (trimmed) raw string when safe, else null. Only http:, https:, mailto:
|
|
13454
|
+
* and fragment anchors (#...) are allowed; anything with another scheme
|
|
13455
|
+
* — javascript:, data:, vbscript:, etc. — is rejected so the value never
|
|
13456
|
+
* reaches dangerouslySetInnerHTML.
|
|
13457
|
+
*/
|
|
13458
|
+
function safeUrl(raw) {
|
|
13459
|
+
const trimmed = raw.trim();
|
|
13460
|
+
if (trimmed === "") return null;
|
|
13461
|
+
if (trimmed.startsWith("#")) return trimmed;
|
|
13462
|
+
const scheme = /^([a-zA-Z][a-zA-Z0-9+.-]*):/.exec(trimmed);
|
|
13463
|
+
if (scheme === null) return trimmed;
|
|
13464
|
+
const name = scheme[1].toLowerCase();
|
|
13465
|
+
return name === "http" || name === "https" || name === "mailto" ? trimmed : null;
|
|
13466
|
+
}
|
|
13467
|
+
/** Find the ')' closing a link/image target, skipping nested parens. */
|
|
13468
|
+
function findCloseParen(text, from) {
|
|
13469
|
+
let depth = 0;
|
|
13470
|
+
for (let i = from; i < text.length; i += 1) {
|
|
13471
|
+
const char = text[i];
|
|
13472
|
+
if (char === "(") depth += 1;
|
|
13473
|
+
else if (char === ")") {
|
|
13474
|
+
if (depth === 0) return i;
|
|
13475
|
+
depth -= 1;
|
|
13476
|
+
}
|
|
13477
|
+
}
|
|
13478
|
+
return -1;
|
|
13479
|
+
}
|
|
13480
|
+
/** Inline pass: code spans, bold, italic, strikethrough, images, links. */
|
|
13481
|
+
function renderInline(text) {
|
|
13482
|
+
let out = "";
|
|
13483
|
+
let i = 0;
|
|
13484
|
+
const n = text.length;
|
|
13485
|
+
while (i < n) {
|
|
13486
|
+
const char = text[i];
|
|
13487
|
+
if (char === "`") {
|
|
13488
|
+
const end = text.indexOf("`", i + 1);
|
|
13489
|
+
if (end !== -1) {
|
|
13490
|
+
out += "<code>" + escapeHtml(text.slice(i + 1, end)) + "</code>";
|
|
13491
|
+
i = end + 1;
|
|
13492
|
+
continue;
|
|
13493
|
+
}
|
|
13494
|
+
}
|
|
13495
|
+
if (char === "!" && text[i + 1] === "[") {
|
|
13496
|
+
const close = text.indexOf("](", i + 2);
|
|
13497
|
+
if (close !== -1) {
|
|
13498
|
+
const parenEnd = findCloseParen(text, close + 2);
|
|
13499
|
+
if (parenEnd !== -1) {
|
|
13500
|
+
const alt = text.slice(i + 2, close);
|
|
13501
|
+
const safe = safeUrl(text.slice(close + 2, parenEnd));
|
|
13502
|
+
if (safe === null) out += escapeHtml(alt);
|
|
13503
|
+
else {
|
|
13504
|
+
const srcEsc = escapeHtml(safe).replace(/\s+/g, "%20");
|
|
13505
|
+
out += "<img alt=\"" + escapeHtml(alt) + "\" src=\"" + srcEsc + "\" />";
|
|
13506
|
+
}
|
|
13507
|
+
i = parenEnd + 1;
|
|
13508
|
+
continue;
|
|
13509
|
+
}
|
|
13510
|
+
}
|
|
13511
|
+
}
|
|
13512
|
+
if (char === "[") {
|
|
13513
|
+
const close = text.indexOf("](", i + 1);
|
|
13514
|
+
if (close !== -1) {
|
|
13515
|
+
const parenEnd = findCloseParen(text, close + 2);
|
|
13516
|
+
if (parenEnd !== -1) {
|
|
13517
|
+
const label = text.slice(i + 1, close);
|
|
13518
|
+
const safe = safeUrl(text.slice(close + 2, parenEnd));
|
|
13519
|
+
if (safe === null) out += renderInline(label);
|
|
13520
|
+
else out += "<a href=\"" + escapeHtml(safe) + "\" target=\"_blank\" rel=\"noopener noreferrer\">" + renderInline(label) + "</a>";
|
|
13521
|
+
i = parenEnd + 1;
|
|
13522
|
+
continue;
|
|
13523
|
+
}
|
|
13524
|
+
}
|
|
13525
|
+
}
|
|
13526
|
+
if (char === "*" && text[i + 1] === "*") {
|
|
13527
|
+
const end = text.indexOf("**", i + 2);
|
|
13528
|
+
if (end !== -1) {
|
|
13529
|
+
out += "<strong>" + renderInline(text.slice(i + 2, end)) + "</strong>";
|
|
13530
|
+
i = end + 2;
|
|
13531
|
+
continue;
|
|
13532
|
+
}
|
|
13533
|
+
}
|
|
13534
|
+
if (char === "*" && text[i - 1] !== "*" && text[i + 1] !== "*") {
|
|
13535
|
+
const end = text.indexOf("*", i + 1);
|
|
13536
|
+
if (end !== -1 && text[end + 1] !== "*") {
|
|
13537
|
+
out += "<em>" + renderInline(text.slice(i + 1, end)) + "</em>";
|
|
13538
|
+
i = end + 1;
|
|
13539
|
+
continue;
|
|
13540
|
+
}
|
|
13541
|
+
}
|
|
13542
|
+
if (char === "~" && text[i + 1] === "~") {
|
|
13543
|
+
const end = text.indexOf("~~", i + 2);
|
|
13544
|
+
if (end !== -1) {
|
|
13545
|
+
out += "<del>" + renderInline(text.slice(i + 2, end)) + "</del>";
|
|
13546
|
+
i = end + 2;
|
|
13547
|
+
continue;
|
|
13548
|
+
}
|
|
13549
|
+
}
|
|
13550
|
+
out += escapeHtml(char);
|
|
13551
|
+
i += 1;
|
|
13552
|
+
}
|
|
13553
|
+
return out;
|
|
13554
|
+
}
|
|
13555
|
+
/** Render a markdown document to HTML (block pass). */
|
|
13556
|
+
function renderMarkdown(source) {
|
|
13557
|
+
const lines = source.replace(/\r\n/g, "\n").split("\n");
|
|
13558
|
+
const out = [];
|
|
13559
|
+
let i = 0;
|
|
13560
|
+
const n = lines.length;
|
|
13561
|
+
const flushParagraph = (buffer) => {
|
|
13562
|
+
if (buffer.length === 0) return;
|
|
13563
|
+
out.push("<p>" + renderInline(buffer.join("\n")) + "</p>");
|
|
13564
|
+
buffer.length = 0;
|
|
13565
|
+
};
|
|
13566
|
+
let paragraph = [];
|
|
13567
|
+
while (i < n) {
|
|
13568
|
+
const line = lines[i];
|
|
13569
|
+
const fence = /^```([\w+-]*)\s*$/.exec(line);
|
|
13570
|
+
if (fence !== null) {
|
|
13571
|
+
flushParagraph(paragraph);
|
|
13572
|
+
const lang = fence[1] ?? "";
|
|
13573
|
+
i += 1;
|
|
13574
|
+
const code = [];
|
|
13575
|
+
while (i < n && !/^```\s*$/.test(lines[i])) {
|
|
13576
|
+
code.push(lines[i]);
|
|
13577
|
+
i += 1;
|
|
13578
|
+
}
|
|
13579
|
+
i += 1;
|
|
13580
|
+
const langAttr = lang === "" ? "" : " class=\"language-" + escapeHtml(lang) + "\"";
|
|
13581
|
+
out.push("<pre" + langAttr + "><code>" + escapeHtml(code.join("\n")) + "</code></pre>");
|
|
13582
|
+
continue;
|
|
13583
|
+
}
|
|
13584
|
+
const heading = /^(#{1,6})\s+(.*)$/.exec(line);
|
|
13585
|
+
if (heading !== null) {
|
|
13586
|
+
flushParagraph(paragraph);
|
|
13587
|
+
const level = heading[1].length;
|
|
13588
|
+
out.push("<h" + level + ">" + renderInline(heading[2] ?? "") + "</h" + level + ">");
|
|
13589
|
+
i += 1;
|
|
13590
|
+
continue;
|
|
13591
|
+
}
|
|
13592
|
+
if (/^\s*(---+|\*\*\*+|___+)\s*$/.test(line)) {
|
|
13593
|
+
flushParagraph(paragraph);
|
|
13594
|
+
out.push("<hr />");
|
|
13595
|
+
i += 1;
|
|
13596
|
+
continue;
|
|
13597
|
+
}
|
|
13598
|
+
if (line.includes("|") && i + 1 < n && /^\s*\|?[\s:|-]+\|?\s*$/.test(lines[i + 1]) && lines[i + 1].includes("-")) {
|
|
13599
|
+
flushParagraph(paragraph);
|
|
13600
|
+
const headerCells = splitTableRow(line);
|
|
13601
|
+
i += 2;
|
|
13602
|
+
const rows = [];
|
|
13603
|
+
while (i < n && lines[i].includes("|")) {
|
|
13604
|
+
rows.push(splitTableRow(lines[i]));
|
|
13605
|
+
i += 1;
|
|
13606
|
+
}
|
|
13607
|
+
out.push("<table>");
|
|
13608
|
+
out.push("<thead><tr>" + headerCells.map((cell) => "<th>" + renderInline(cell) + "</th>").join("") + "</tr></thead>");
|
|
13609
|
+
if (rows.length > 0) out.push("<tbody>" + rows.map((row) => "<tr>" + row.map((cell) => "<td>" + renderInline(cell) + "</td>").join("") + "</tr>").join("") + "</tbody>");
|
|
13610
|
+
out.push("</table>");
|
|
13611
|
+
continue;
|
|
13612
|
+
}
|
|
13613
|
+
if (/^>\s?(.*)$/.exec(line) !== null) {
|
|
13614
|
+
flushParagraph(paragraph);
|
|
13615
|
+
const body = [];
|
|
13616
|
+
while (i < n) {
|
|
13617
|
+
const q = /^>\s?(.*)$/.exec(lines[i]);
|
|
13618
|
+
if (q === null) break;
|
|
13619
|
+
body.push(q[1] ?? "");
|
|
13620
|
+
i += 1;
|
|
13621
|
+
}
|
|
13622
|
+
out.push("<blockquote><p>" + body.map((bodyLine) => renderInline(bodyLine)).join("<br />") + "</p></blockquote>");
|
|
13623
|
+
continue;
|
|
13624
|
+
}
|
|
13625
|
+
if (/^\s*([-*+])\s+(.*)$/.exec(line) !== null) {
|
|
13626
|
+
flushParagraph(paragraph);
|
|
13627
|
+
const items = [];
|
|
13628
|
+
while (i < n) {
|
|
13629
|
+
const item = /^\s*([-*+])\s+(.*)$/.exec(lines[i]);
|
|
13630
|
+
if (item === null) break;
|
|
13631
|
+
items.push("<li>" + renderInline(item[2] ?? "") + "</li>");
|
|
13632
|
+
i += 1;
|
|
13633
|
+
}
|
|
13634
|
+
out.push("<ul>" + items.join("") + "</ul>");
|
|
13635
|
+
continue;
|
|
13636
|
+
}
|
|
13637
|
+
if (/^\s*\d+[.)]\s+(.*)$/.exec(line) !== null) {
|
|
13638
|
+
flushParagraph(paragraph);
|
|
13639
|
+
const items = [];
|
|
13640
|
+
while (i < n) {
|
|
13641
|
+
const item = /^\s*\d+[.)]\s+(.*)$/.exec(lines[i]);
|
|
13642
|
+
if (item === null) break;
|
|
13643
|
+
items.push("<li>" + renderInline(item[1] ?? "") + "</li>");
|
|
13644
|
+
i += 1;
|
|
13645
|
+
}
|
|
13646
|
+
out.push("<ol>" + items.join("") + "</ol>");
|
|
13647
|
+
continue;
|
|
13648
|
+
}
|
|
13649
|
+
if (line.trim() === "") {
|
|
13650
|
+
flushParagraph(paragraph);
|
|
13651
|
+
i += 1;
|
|
13652
|
+
continue;
|
|
13653
|
+
}
|
|
13654
|
+
paragraph.push(line);
|
|
13655
|
+
i += 1;
|
|
13656
|
+
}
|
|
13657
|
+
flushParagraph(paragraph);
|
|
13658
|
+
return out.join("\n");
|
|
13659
|
+
}
|
|
13660
|
+
/** Split one table row into cells (respecting the leading/trailing pipes). */
|
|
13661
|
+
function splitTableRow(line) {
|
|
13662
|
+
const trimmed = line.trim();
|
|
13663
|
+
const inner = trimmed.startsWith("|") ? trimmed.slice(1) : trimmed;
|
|
13664
|
+
return (inner.endsWith("|") ? inner.slice(0, -1) : inner).split("|").map((cell) => cell.trim());
|
|
13665
|
+
}
|
|
13666
|
+
//#endregion
|
|
13400
13667
|
//#region src/mobile/mobile-theme.ts
|
|
13401
13668
|
const STORAGE_KEY = "dsh.remote.theme";
|
|
13402
13669
|
const THEME_COLOR_META_NAME = "theme-color";
|
|
@@ -13549,6 +13816,26 @@ function ThemeToggle() {
|
|
|
13549
13816
|
children: dark ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SunIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MoonIcon, {})
|
|
13550
13817
|
});
|
|
13551
13818
|
}
|
|
13819
|
+
/** localStorage key for the tool-call display toggle (persisted on the /m origin). */
|
|
13820
|
+
const SHOW_TOOL_CALLS_KEY = "dsh.mobile.showToolCalls";
|
|
13821
|
+
/** localStorage key for the injected-system-message display toggle. */
|
|
13822
|
+
const SHOW_SYSTEM_MESSAGES_KEY = "dsh.mobile.showSystemMessages";
|
|
13823
|
+
/** Read a boolean from localStorage defensively; falls back to the default. */
|
|
13824
|
+
function readStoredBoolean(key, fallback) {
|
|
13825
|
+
try {
|
|
13826
|
+
const raw = localStorage.getItem(key);
|
|
13827
|
+
if (raw === null) return fallback;
|
|
13828
|
+
return raw === "1" || raw.toLowerCase() === "true";
|
|
13829
|
+
} catch {
|
|
13830
|
+
return fallback;
|
|
13831
|
+
}
|
|
13832
|
+
}
|
|
13833
|
+
/** Persist a boolean toggle; storage failures are ignored (feature stays non-persistent). */
|
|
13834
|
+
function writeStoredBoolean(key, value) {
|
|
13835
|
+
try {
|
|
13836
|
+
localStorage.setItem(key, value ? "1" : "0");
|
|
13837
|
+
} catch {}
|
|
13838
|
+
}
|
|
13552
13839
|
/** Extract the raw event from one history entry (the fold consumes events only). */
|
|
13553
13840
|
function eventOf(entry) {
|
|
13554
13841
|
return entry.event;
|
|
@@ -13624,6 +13911,10 @@ function ChatView({ session, mux, onBack }) {
|
|
|
13624
13911
|
const [currentModel, setCurrentModel] = (0, import_react.useState)(void 0);
|
|
13625
13912
|
/** Which bottom sheet is open. */
|
|
13626
13913
|
const [sheet, setSheet] = (0, import_react.useState)(null);
|
|
13914
|
+
/** Show tool-call disclosures (default on, persisted on the /m origin). */
|
|
13915
|
+
const [showToolCalls, setShowToolCalls] = (0, import_react.useState)(() => readStoredBoolean(SHOW_TOOL_CALLS_KEY, true));
|
|
13916
|
+
/** Show injected system messages (default off, persisted on the /m origin). */
|
|
13917
|
+
const [showSystemMessages, setShowSystemMessages] = (0, import_react.useState)(() => readStoredBoolean(SHOW_SYSTEM_MESSAGES_KEY, false));
|
|
13627
13918
|
/**
|
|
13628
13919
|
* Composer preference from the plugin's host settings (default true keeps
|
|
13629
13920
|
* the legacy Enter-to-send behavior until the preference loads).
|
|
@@ -13767,6 +14058,17 @@ function ChatView({ session, mux, onBack }) {
|
|
|
13767
14058
|
]);
|
|
13768
14059
|
const modelLabel = currentModel?.model ?? "模型";
|
|
13769
14060
|
const permissionLabel = permissions === void 0 ? void 0 : permissions.options.find((option) => option.value === permissions.currentValue)?.name ?? displayName(permissions.currentValue);
|
|
14061
|
+
const contextUsage = (0, import_react.useMemo)(() => {
|
|
14062
|
+
for (let index = messages.length - 1; index >= 0; index--) {
|
|
14063
|
+
const message = messages[index];
|
|
14064
|
+
const usage = message.usage;
|
|
14065
|
+
if (message.kind !== "assistant" || usage === void 0) continue;
|
|
14066
|
+
const window = message.contextWindow;
|
|
14067
|
+
if (window === void 0 || window <= 0) continue;
|
|
14068
|
+
const tokens = usage.inputTokens + (usage.cacheReadTokens ?? 0) + (usage.cacheWriteTokens ?? 0);
|
|
14069
|
+
return { pct: Math.round(tokens / window * 100) };
|
|
14070
|
+
}
|
|
14071
|
+
}, [messages]);
|
|
13770
14072
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
13771
14073
|
className: "chat",
|
|
13772
14074
|
children: [
|
|
@@ -13806,7 +14108,11 @@ function ChatView({ session, mux, onBack }) {
|
|
|
13806
14108
|
},
|
|
13807
14109
|
children: loading ? "加载中…" : "加载更早的消息"
|
|
13808
14110
|
}),
|
|
13809
|
-
messages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MessageRow, {
|
|
14111
|
+
messages.map((message) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MessageRow, {
|
|
14112
|
+
message,
|
|
14113
|
+
showToolCalls,
|
|
14114
|
+
showSystemMessages
|
|
14115
|
+
}, message.id)),
|
|
13810
14116
|
loading && messages.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
13811
14117
|
className: "chat-typing",
|
|
13812
14118
|
children: "加载中…"
|
|
@@ -13819,51 +14125,78 @@ function ChatView({ session, mux, onBack }) {
|
|
|
13819
14125
|
}),
|
|
13820
14126
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
13821
14127
|
className: "chat-tools",
|
|
13822
|
-
children: [
|
|
13823
|
-
|
|
13824
|
-
|
|
13825
|
-
|
|
13826
|
-
|
|
13827
|
-
|
|
13828
|
-
|
|
13829
|
-
|
|
13830
|
-
|
|
13831
|
-
|
|
13832
|
-
|
|
13833
|
-
|
|
13834
|
-
|
|
13835
|
-
|
|
13836
|
-
|
|
13837
|
-
|
|
13838
|
-
|
|
13839
|
-
|
|
13840
|
-
|
|
13841
|
-
|
|
13842
|
-
|
|
13843
|
-
|
|
13844
|
-
|
|
13845
|
-
|
|
13846
|
-
|
|
13847
|
-
|
|
13848
|
-
|
|
13849
|
-
|
|
13850
|
-
|
|
13851
|
-
|
|
13852
|
-
|
|
14128
|
+
children: [
|
|
14129
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
|
|
14130
|
+
type: "button",
|
|
14131
|
+
className: "chat-chip",
|
|
14132
|
+
onClick: () => {
|
|
14133
|
+
setSheet("model");
|
|
14134
|
+
},
|
|
14135
|
+
"aria-haspopup": "dialog",
|
|
14136
|
+
children: [
|
|
14137
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
14138
|
+
className: "chat-chip-label",
|
|
14139
|
+
children: "模型"
|
|
14140
|
+
}),
|
|
14141
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
14142
|
+
className: "chat-chip-value",
|
|
14143
|
+
children: modelLabel
|
|
14144
|
+
}),
|
|
14145
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
14146
|
+
className: "chat-chip-chevron",
|
|
14147
|
+
"aria-hidden": true,
|
|
14148
|
+
children: "›"
|
|
14149
|
+
})
|
|
14150
|
+
]
|
|
14151
|
+
}),
|
|
14152
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
|
|
14153
|
+
type: "button",
|
|
14154
|
+
className: "chat-chip",
|
|
14155
|
+
onClick: () => {
|
|
14156
|
+
setSheet("display");
|
|
14157
|
+
},
|
|
14158
|
+
"aria-haspopup": "dialog",
|
|
14159
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
13853
14160
|
className: "chat-chip-label",
|
|
13854
|
-
children: "
|
|
13855
|
-
}),
|
|
13856
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
13857
|
-
className: "chat-chip-value",
|
|
13858
|
-
children: permissionLabel
|
|
13859
|
-
}),
|
|
13860
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
14161
|
+
children: "显示"
|
|
14162
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
13861
14163
|
className: "chat-chip-chevron",
|
|
13862
14164
|
"aria-hidden": true,
|
|
13863
14165
|
children: "›"
|
|
13864
|
-
})
|
|
13865
|
-
|
|
13866
|
-
|
|
14166
|
+
})]
|
|
14167
|
+
}),
|
|
14168
|
+
permissionLabel !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
|
|
14169
|
+
type: "button",
|
|
14170
|
+
className: "chat-chip",
|
|
14171
|
+
onClick: () => {
|
|
14172
|
+
setSheet("permission");
|
|
14173
|
+
},
|
|
14174
|
+
"aria-haspopup": "dialog",
|
|
14175
|
+
children: [
|
|
14176
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
14177
|
+
className: "chat-chip-label",
|
|
14178
|
+
children: "权限"
|
|
14179
|
+
}),
|
|
14180
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
14181
|
+
className: "chat-chip-value",
|
|
14182
|
+
children: permissionLabel
|
|
14183
|
+
}),
|
|
14184
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
14185
|
+
className: "chat-chip-chevron",
|
|
14186
|
+
"aria-hidden": true,
|
|
14187
|
+
children: "›"
|
|
14188
|
+
})
|
|
14189
|
+
]
|
|
14190
|
+
}),
|
|
14191
|
+
contextUsage !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
14192
|
+
className: "chat-context" + (contextUsage.pct >= 80 ? " chat-context-warn" : ""),
|
|
14193
|
+
children: [
|
|
14194
|
+
"上下文 ",
|
|
14195
|
+
contextUsage.pct,
|
|
14196
|
+
"%"
|
|
14197
|
+
]
|
|
14198
|
+
})
|
|
14199
|
+
]
|
|
13867
14200
|
}),
|
|
13868
14201
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
13869
14202
|
className: "chat-inputbar",
|
|
@@ -13914,12 +14247,28 @@ function ChatView({ session, mux, onBack }) {
|
|
|
13914
14247
|
onClose: () => {
|
|
13915
14248
|
setSheet(null);
|
|
13916
14249
|
}
|
|
14250
|
+
}),
|
|
14251
|
+
sheet === "display" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DisplaySheet, {
|
|
14252
|
+
showToolCalls,
|
|
14253
|
+
showSystemMessages,
|
|
14254
|
+
onToolCalls: (value) => {
|
|
14255
|
+
setShowToolCalls(value);
|
|
14256
|
+
writeStoredBoolean(SHOW_TOOL_CALLS_KEY, value);
|
|
14257
|
+
},
|
|
14258
|
+
onSystemMessages: (value) => {
|
|
14259
|
+
setShowSystemMessages(value);
|
|
14260
|
+
writeStoredBoolean(SHOW_SYSTEM_MESSAGES_KEY, value);
|
|
14261
|
+
},
|
|
14262
|
+
onClose: () => {
|
|
14263
|
+
setSheet(null);
|
|
14264
|
+
}
|
|
13917
14265
|
})
|
|
13918
14266
|
]
|
|
13919
14267
|
});
|
|
13920
14268
|
}
|
|
13921
14269
|
/** One rendered message row (user bubble or assistant bubble with folds). */
|
|
13922
|
-
function MessageRow({ message }) {
|
|
14270
|
+
function MessageRow({ message, showToolCalls, showSystemMessages }) {
|
|
14271
|
+
if (message.kind === "user" && message.sourceKind !== void 0 && message.sourceKind !== "user" && !showSystemMessages) return null;
|
|
13923
14272
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
13924
14273
|
className: `chat-msg chat-msg-${message.kind}${message.pending === true ? " chat-msg-pending" : ""}${message.failed === true ? " chat-msg-failed" : ""}`,
|
|
13925
14274
|
children: [
|
|
@@ -13927,8 +14276,8 @@ function MessageRow({ message }) {
|
|
|
13927
14276
|
text: message.reasoning,
|
|
13928
14277
|
pending: message.pending === true
|
|
13929
14278
|
}),
|
|
13930
|
-
message.kind === "assistant" && message.tools !== void 0 && message.tools.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ToolDisclosure, { tools: message.tools }),
|
|
13931
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CollapsibleText, { text: message.text }),
|
|
14279
|
+
showToolCalls && message.kind === "assistant" && message.tools !== void 0 && message.tools.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ToolDisclosure, { tools: message.tools }),
|
|
14280
|
+
message.kind === "assistant" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MarkdownText, { text: message.text }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CollapsibleText, { text: message.text }),
|
|
13932
14281
|
message.failed === true && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
13933
14282
|
className: "chat-msg-failtag",
|
|
13934
14283
|
children: "本次回复失败"
|
|
@@ -14022,6 +14371,32 @@ function ToolDisclosure({ tools }) {
|
|
|
14022
14371
|
})]
|
|
14023
14372
|
});
|
|
14024
14373
|
}
|
|
14374
|
+
/**
|
|
14375
|
+
* Assistant text rendered as GFM markdown (escape-first, protocol
|
|
14376
|
+
* allow-list — see markdown.ts). Long replies collapse by clamping the
|
|
14377
|
+
* rendered block height instead of slicing the source, so half-cut code
|
|
14378
|
+
* fences or tables never leak malformed markup into the DOM. User
|
|
14379
|
+
* messages stay plain text (CollapsibleText).
|
|
14380
|
+
*/
|
|
14381
|
+
function MarkdownText({ text }) {
|
|
14382
|
+
const [open, setOpen] = (0, import_react.useState)(false);
|
|
14383
|
+
const html = (0, import_react.useMemo)(() => renderMarkdown(text), [text]);
|
|
14384
|
+
const long = text.length > LONG_TEXT_LIMIT;
|
|
14385
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
14386
|
+
className: "chat-msg-text chat-md" + (long && !open ? " chat-md-collapsed" : ""),
|
|
14387
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
14388
|
+
className: "chat-md-body",
|
|
14389
|
+
dangerouslySetInnerHTML: { __html: html }
|
|
14390
|
+
}), long && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
14391
|
+
type: "button",
|
|
14392
|
+
className: "chat-msg-toggle",
|
|
14393
|
+
onClick: () => {
|
|
14394
|
+
setOpen((value) => !value);
|
|
14395
|
+
},
|
|
14396
|
+
children: open ? "收起" : "展开全文(" + text.length + " 字)"
|
|
14397
|
+
})]
|
|
14398
|
+
});
|
|
14399
|
+
}
|
|
14025
14400
|
/** Long assistant text collapses behind an explicit expand toggle. */
|
|
14026
14401
|
function CollapsibleText({ text }) {
|
|
14027
14402
|
const [open, setOpen] = (0, import_react.useState)(false);
|
|
@@ -14372,6 +14747,68 @@ function PermissionSheet({ sessionId, value, onChanged, onClose }) {
|
|
|
14372
14747
|
})]
|
|
14373
14748
|
});
|
|
14374
14749
|
}
|
|
14750
|
+
/** The display-options sheet: tool calls and injected system messages toggles. */
|
|
14751
|
+
function DisplaySheet({ showToolCalls, showSystemMessages, onToolCalls, onSystemMessages, onClose }) {
|
|
14752
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Sheet, {
|
|
14753
|
+
title: "显示",
|
|
14754
|
+
onClose,
|
|
14755
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
14756
|
+
role: "group",
|
|
14757
|
+
"aria-label": "显示选项",
|
|
14758
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
14759
|
+
className: "sheet-toggle-row",
|
|
14760
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
14761
|
+
className: "sheet-toggle-copy",
|
|
14762
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
14763
|
+
className: "sheet-toggle-title",
|
|
14764
|
+
children: "工具调用"
|
|
14765
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
14766
|
+
className: "sheet-toggle-desc",
|
|
14767
|
+
children: "显示助手使用的工具调用"
|
|
14768
|
+
})]
|
|
14769
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
14770
|
+
type: "button",
|
|
14771
|
+
role: "switch",
|
|
14772
|
+
"aria-label": "工具调用",
|
|
14773
|
+
"aria-checked": showToolCalls,
|
|
14774
|
+
className: `sheet-toggle-switch${showToolCalls ? " sheet-toggle-switch-on" : ""}`,
|
|
14775
|
+
onClick: () => {
|
|
14776
|
+
onToolCalls(!showToolCalls);
|
|
14777
|
+
},
|
|
14778
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
14779
|
+
className: "sheet-toggle-switch-knob",
|
|
14780
|
+
"aria-hidden": true
|
|
14781
|
+
})
|
|
14782
|
+
})]
|
|
14783
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
14784
|
+
className: "sheet-toggle-row",
|
|
14785
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
14786
|
+
className: "sheet-toggle-copy",
|
|
14787
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
14788
|
+
className: "sheet-toggle-title",
|
|
14789
|
+
children: "系统提示词"
|
|
14790
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
14791
|
+
className: "sheet-toggle-desc",
|
|
14792
|
+
children: "显示注入到对话中的系统消息"
|
|
14793
|
+
})]
|
|
14794
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
14795
|
+
type: "button",
|
|
14796
|
+
role: "switch",
|
|
14797
|
+
"aria-label": "系统提示词",
|
|
14798
|
+
"aria-checked": showSystemMessages,
|
|
14799
|
+
className: `sheet-toggle-switch${showSystemMessages ? " sheet-toggle-switch-on" : ""}`,
|
|
14800
|
+
onClick: () => {
|
|
14801
|
+
onSystemMessages(!showSystemMessages);
|
|
14802
|
+
},
|
|
14803
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
14804
|
+
className: "sheet-toggle-switch-knob",
|
|
14805
|
+
"aria-hidden": true
|
|
14806
|
+
})
|
|
14807
|
+
})]
|
|
14808
|
+
})]
|
|
14809
|
+
})
|
|
14810
|
+
});
|
|
14811
|
+
}
|
|
14375
14812
|
//#endregion
|
|
14376
14813
|
//#region src/mobile/views/SessionListView.tsx
|
|
14377
14814
|
/**
|
|
@@ -15206,6 +15643,85 @@ body {
|
|
|
15206
15643
|
cursor: pointer;
|
|
15207
15644
|
}
|
|
15208
15645
|
|
|
15646
|
+
/* Assistant markdown content (GFM subset; renderer in markdown.ts). */
|
|
15647
|
+
.chat-md {
|
|
15648
|
+
white-space: normal;
|
|
15649
|
+
}
|
|
15650
|
+
.chat-md-collapsed {
|
|
15651
|
+
max-height: 45vh;
|
|
15652
|
+
overflow: hidden;
|
|
15653
|
+
position: relative;
|
|
15654
|
+
}
|
|
15655
|
+
.chat-md-collapsed::after {
|
|
15656
|
+
content: '';
|
|
15657
|
+
position: absolute;
|
|
15658
|
+
left: 0;
|
|
15659
|
+
right: 0;
|
|
15660
|
+
bottom: 0;
|
|
15661
|
+
height: 28px;
|
|
15662
|
+
background: linear-gradient(transparent, var(--m-bg-raised));
|
|
15663
|
+
pointer-events: none;
|
|
15664
|
+
}
|
|
15665
|
+
.chat-md-body p { margin: 0 0 8px; }
|
|
15666
|
+
.chat-md-body p:last-child { margin-bottom: 0; }
|
|
15667
|
+
.chat-md-body h1, .chat-md-body h2, .chat-md-body h3,
|
|
15668
|
+
.chat-md-body h4, .chat-md-body h5, .chat-md-body h6 {
|
|
15669
|
+
margin: 12px 0 6px;
|
|
15670
|
+
font-weight: 650;
|
|
15671
|
+
line-height: 1.3;
|
|
15672
|
+
}
|
|
15673
|
+
.chat-md-body h1 { font-size: 1.35em; }
|
|
15674
|
+
.chat-md-body h2 { font-size: 1.25em; }
|
|
15675
|
+
.chat-md-body h3 { font-size: 1.15em; }
|
|
15676
|
+
.chat-md-body h4, .chat-md-body h5, .chat-md-body h6 { font-size: 1.05em; }
|
|
15677
|
+
.chat-md-body code {
|
|
15678
|
+
font-family: var(--m-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
|
|
15679
|
+
font-size: 0.9em;
|
|
15680
|
+
background: var(--m-accent-soft);
|
|
15681
|
+
padding: 1px 5px;
|
|
15682
|
+
border-radius: 4px;
|
|
15683
|
+
}
|
|
15684
|
+
.chat-md-body pre {
|
|
15685
|
+
margin: 8px 0;
|
|
15686
|
+
padding: 10px;
|
|
15687
|
+
background: #101726;
|
|
15688
|
+
color: #dbe4f5;
|
|
15689
|
+
border-radius: 8px;
|
|
15690
|
+
overflow-x: auto;
|
|
15691
|
+
font-size: 12.5px;
|
|
15692
|
+
line-height: 1.5;
|
|
15693
|
+
}
|
|
15694
|
+
.chat-md-body pre code {
|
|
15695
|
+
background: transparent;
|
|
15696
|
+
padding: 0;
|
|
15697
|
+
color: inherit;
|
|
15698
|
+
}
|
|
15699
|
+
.chat-md-body ul, .chat-md-body ol { margin: 4px 0 8px; padding-left: 22px; }
|
|
15700
|
+
.chat-md-body li { margin: 2px 0; }
|
|
15701
|
+
.chat-md-body blockquote {
|
|
15702
|
+
margin: 8px 0;
|
|
15703
|
+
padding: 4px 10px;
|
|
15704
|
+
border-left: 3px solid var(--m-accent);
|
|
15705
|
+
background: var(--m-accent-soft);
|
|
15706
|
+
border-radius: 0 6px 6px 0;
|
|
15707
|
+
color: var(--m-text-secondary);
|
|
15708
|
+
}
|
|
15709
|
+
.chat-md-body table {
|
|
15710
|
+
margin: 8px 0;
|
|
15711
|
+
border-collapse: collapse;
|
|
15712
|
+
display: block;
|
|
15713
|
+
overflow-x: auto;
|
|
15714
|
+
font-size: 13px;
|
|
15715
|
+
}
|
|
15716
|
+
.chat-md-body th, .chat-md-body td {
|
|
15717
|
+
border: 1px solid var(--m-border);
|
|
15718
|
+
padding: 4px 8px;
|
|
15719
|
+
}
|
|
15720
|
+
.chat-md-body th { background: var(--m-accent-soft); }
|
|
15721
|
+
.chat-md-body a { color: var(--m-accent); }
|
|
15722
|
+
.chat-md-body hr { border: none; border-top: 1px solid var(--m-border); margin: 10px 0; }
|
|
15723
|
+
.chat-md-body img { max-width: 100%; border-radius: 8px; }
|
|
15724
|
+
|
|
15209
15725
|
/* ── message disclosures (reasoning / tools) ─────────────────────────── */
|
|
15210
15726
|
|
|
15211
15727
|
.chat-disclosure {
|
|
@@ -15637,11 +16153,97 @@ body {
|
|
|
15637
16153
|
.chat-disclosure-head:focus-visible,
|
|
15638
16154
|
.sheet-option:focus-visible,
|
|
15639
16155
|
.sheet-confirm-danger:focus-visible,
|
|
16156
|
+
.sheet-toggle-switch:focus-visible,
|
|
15640
16157
|
.chat-input:focus-visible {
|
|
15641
16158
|
outline: none;
|
|
15642
16159
|
box-shadow: 0 0 0 2px var(--m-bg), 0 0 0 4px var(--m-accent);
|
|
15643
16160
|
}
|
|
15644
16161
|
|
|
16162
|
+
/* ── chat context usage chip (chat-tools row) ───────────────────────── */
|
|
16163
|
+
|
|
16164
|
+
.chat-context {
|
|
16165
|
+
flex: none;
|
|
16166
|
+
align-self: center;
|
|
16167
|
+
padding: 2px 10px;
|
|
16168
|
+
border: 1px solid var(--m-border);
|
|
16169
|
+
border-radius: 999px;
|
|
16170
|
+
background: color-mix(in srgb, var(--m-text-tertiary) 10%, transparent);
|
|
16171
|
+
color: var(--m-text-tertiary);
|
|
16172
|
+
font-size: 11px;
|
|
16173
|
+
line-height: 1.6;
|
|
16174
|
+
white-space: nowrap;
|
|
16175
|
+
}
|
|
16176
|
+
|
|
16177
|
+
.chat-context-warn {
|
|
16178
|
+
border-color: var(--m-danger);
|
|
16179
|
+
background: color-mix(in srgb, var(--m-danger) 14%, transparent);
|
|
16180
|
+
color: var(--m-danger);
|
|
16181
|
+
}
|
|
16182
|
+
|
|
16183
|
+
/* ── display sheet toggle rows ──────────────────────────────────────── */
|
|
16184
|
+
|
|
16185
|
+
.sheet-toggle-row {
|
|
16186
|
+
display: flex;
|
|
16187
|
+
align-items: center;
|
|
16188
|
+
gap: 12px;
|
|
16189
|
+
padding: 12px 8px;
|
|
16190
|
+
border-bottom: 1px solid var(--m-border);
|
|
16191
|
+
}
|
|
16192
|
+
|
|
16193
|
+
.sheet-toggle-row:last-child {
|
|
16194
|
+
border-bottom: none;
|
|
16195
|
+
}
|
|
16196
|
+
|
|
16197
|
+
.sheet-toggle-copy {
|
|
16198
|
+
flex: 1;
|
|
16199
|
+
min-width: 0;
|
|
16200
|
+
display: flex;
|
|
16201
|
+
flex-direction: column;
|
|
16202
|
+
gap: 2px;
|
|
16203
|
+
}
|
|
16204
|
+
|
|
16205
|
+
.sheet-toggle-title {
|
|
16206
|
+
font-size: 14px;
|
|
16207
|
+
}
|
|
16208
|
+
|
|
16209
|
+
.sheet-toggle-desc {
|
|
16210
|
+
color: var(--m-text-tertiary);
|
|
16211
|
+
font-size: 12px;
|
|
16212
|
+
line-height: 1.4;
|
|
16213
|
+
}
|
|
16214
|
+
|
|
16215
|
+
.sheet-toggle-switch {
|
|
16216
|
+
position: relative;
|
|
16217
|
+
flex: none;
|
|
16218
|
+
width: 44px;
|
|
16219
|
+
height: 26px;
|
|
16220
|
+
border: none;
|
|
16221
|
+
border-radius: 999px;
|
|
16222
|
+
background: var(--m-border);
|
|
16223
|
+
cursor: pointer;
|
|
16224
|
+
transition: background-color 0.15s ease;
|
|
16225
|
+
}
|
|
16226
|
+
|
|
16227
|
+
.sheet-toggle-switch-on {
|
|
16228
|
+
background: var(--m-accent);
|
|
16229
|
+
}
|
|
16230
|
+
|
|
16231
|
+
.sheet-toggle-switch-knob {
|
|
16232
|
+
position: absolute;
|
|
16233
|
+
top: 3px;
|
|
16234
|
+
left: 3px;
|
|
16235
|
+
width: 20px;
|
|
16236
|
+
height: 20px;
|
|
16237
|
+
border-radius: 50%;
|
|
16238
|
+
background: #fff;
|
|
16239
|
+
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.2);
|
|
16240
|
+
transition: transform 0.15s ease;
|
|
16241
|
+
}
|
|
16242
|
+
|
|
16243
|
+
.sheet-toggle-switch-on .sheet-toggle-switch-knob {
|
|
16244
|
+
transform: translateX(18px);
|
|
16245
|
+
}
|
|
16246
|
+
|
|
15645
16247
|
/* Accessibility: collapse every interaction/enter animation for
|
|
15646
16248
|
reduced-motion users; surface layout itself stays static. */
|
|
15647
16249
|
@media (prefers-reduced-motion: reduce) {
|
|
@@ -15661,6 +16263,8 @@ body {
|
|
|
15661
16263
|
.sheet,
|
|
15662
16264
|
.sheet-option,
|
|
15663
16265
|
.sheet-confirm-danger,
|
|
16266
|
+
.sheet-toggle-switch,
|
|
16267
|
+
.sheet-toggle-switch-knob,
|
|
15664
16268
|
.chat-input {
|
|
15665
16269
|
animation: none;
|
|
15666
16270
|
transition: none;
|