pi-web-ui 0.11.5 → 0.12.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.
@@ -200,6 +200,32 @@ function looksLikeText(buf) {
200
200
  }
201
201
  return control / Math.max(text.length, 1) < 0.02;
202
202
  }
203
+ /**
204
+ * Cheap per-message discriminator for the serialization cache key. Persisted
205
+ * message content never changes, so this is stable across snapshots, while
206
+ * several same-role messages created within one millisecond (attachment
207
+ * asides) get distinct keys. Text blocks are fingerprinted by a short hash of
208
+ * their head (paths embedded in <file> tags can share long prefixes — e.g.
209
+ * uploads created in the same millisecond differ only at the tail); image
210
+ * payloads by data length (identical lengths within the same ms are far too
211
+ * unlikely to matter).
212
+ */
213
+ function contentFingerprint(m) {
214
+ const content = m.content;
215
+ if (!Array.isArray(content) || content.length === 0)
216
+ return "empty";
217
+ const first = content[0];
218
+ if (first?.type === "image") {
219
+ return `img:${(first.data ?? "").length}`;
220
+ }
221
+ const text = typeof first?.text === "string" ? first.text : "";
222
+ // djb2 — fast enough to run per snapshot, distinct enough for asides.
223
+ let h = 5381;
224
+ for (let i = 0; i < text.length && i < 512; i++) {
225
+ h = ((h << 5) + h + text.charCodeAt(i)) >>> 0;
226
+ }
227
+ return `txt:${h.toString(36)}:${text.length}`;
228
+ }
203
229
  /** First few KB of binary data as a classic hex + ASCII dump (preview only). */
204
230
  function hexDump(buf, maxBytes = 4096) {
205
231
  const data = buf.subarray(0, Math.min(buf.length, maxBytes));
@@ -899,9 +925,15 @@ export class ClientSession {
899
925
  /** Serialize a persisted message with a STABLE id + cached object reference. */
900
926
  serializeCached(m) {
901
927
  const conv = this.conv;
928
+ // toolResult messages are keyed by toolCallId; everything else by
929
+ // role+timestamp. A single prompt can emit several same-role messages
930
+ // within the SAME millisecond (multiple attachment asides), so the
931
+ // timestamp alone collides in the cache and only the first one renders
932
+ // — append a cheap content fingerprint to keep them distinct while
933
+ // staying stable across snapshots (content never changes once persisted).
902
934
  const key = m.role === "toolResult"
903
935
  ? `t:${m.toolCallId}`
904
- : `${m.role}:${m.timestamp}`;
936
+ : `${m.role}:${m.timestamp}:${contentFingerprint(m)}`;
905
937
  let n = conv.msgIds.get(key);
906
938
  if (n === undefined) {
907
939
  n = conv.nextMsgId++;
@@ -981,7 +1013,12 @@ export class ClientSession {
981
1013
  : null,
982
1014
  isStreaming: this.session.isStreaming,
983
1015
  model: model
984
- ? { id: model.id, name: model.name, provider: model.provider }
1016
+ ? {
1017
+ id: model.id,
1018
+ name: model.name,
1019
+ provider: model.provider,
1020
+ vision: model.input?.includes("image") ?? false,
1021
+ }
985
1022
  : null,
986
1023
  thinkingLevel: state.thinkingLevel,
987
1024
  // Only the levels the current model actually supports — the SDK clamps
@@ -1610,13 +1647,17 @@ export class ClientSession {
1610
1647
  * model sees them immediately; large files are passed as a <file path="...">
1611
1648
  * reference and the model reads them on demand with its read tool (which has
1612
1649
  * built-in truncation). Images are always passed as image content. Mode
1613
- * "lines" inlines only a 1-based inclusive line range of the file.
1650
+ * "lines" inlines only a 1-based inclusive line range of the file. Raw
1651
+ * pasted/dropped/uploaded images (attachment.imageData) skip the workspace
1652
+ * path entirely and go straight to the model as image content. Raw uploaded
1653
+ * files (attachment.fileData) are persisted under <dataDir>/uploads/ and
1654
+ * attached as absolute-path references (small text ones are inlined).
1614
1655
  */
1615
1656
  async buildAttachmentMessages(attachments) {
1616
1657
  if (!attachments || attachments.length === 0)
1617
1658
  return [];
1618
1659
  const fs = await import("node:fs/promises");
1619
- const { resolve, sep, relative, extname } = await import("node:path");
1660
+ const { resolve, sep, relative, extname, join } = await import("node:path");
1620
1661
  const root = resolve(this.cwd);
1621
1662
  const MAX_ATTACHMENT_BYTES = 200 * 1024;
1622
1663
  // Files at or below this size are inlined; larger files are referenced by
@@ -1644,6 +1685,128 @@ export class ClientSession {
1644
1685
  /** Cap for reading a file in "lines" mode (selected slice is inlined). */
1645
1686
  const MAX_LINES_READ_BYTES = 2 * 1024 * 1024;
1646
1687
  for (const att of attachments) {
1688
+ // Raw pasted/dropped/uploaded image — no workspace path involved (the
1689
+ // browser downscales client-side; this guard only prevents abuse).
1690
+ if (att.imageData) {
1691
+ const raw = att.imageData.replace(/^data:[^;]*;base64,/, "");
1692
+ const mimeType = att.mimeType?.startsWith("image/") ? att.mimeType : "image/png";
1693
+ const bytes = Buffer.byteLength(raw, "base64");
1694
+ const MAX_PASTED_IMAGE_BYTES = 2 * 1024 * 1024;
1695
+ if (bytes === 0) {
1696
+ this.emit({
1697
+ type: "notice",
1698
+ level: "error",
1699
+ text: `图片数据为空,已跳过`,
1700
+ });
1701
+ continue;
1702
+ }
1703
+ if (bytes > MAX_PASTED_IMAGE_BYTES) {
1704
+ this.emit({
1705
+ type: "notice",
1706
+ level: "warning",
1707
+ text: `图片过大已跳过(>2MB):${att.name ?? "粘贴图片"}`,
1708
+ });
1709
+ continue;
1710
+ }
1711
+ out.push({
1712
+ message: {
1713
+ customType: "file",
1714
+ content: [{ type: "image", data: raw, mimeType }],
1715
+ display: true,
1716
+ details: {
1717
+ name: att.name ?? "image.png",
1718
+ // No workspace path — the card renders without the path line.
1719
+ path: undefined,
1720
+ mode: "image",
1721
+ size: bytes,
1722
+ },
1723
+ },
1724
+ });
1725
+ continue;
1726
+ }
1727
+ // Raw uploaded file (base64) — no workspace path involved. The bytes are
1728
+ // persisted under <dataDir>/uploads/<clientId>/ so the model can read
1729
+ // them on demand with its read tool (absolute path, no traversal guard
1730
+ // needed — the path is server-generated). Small text uploads are inlined
1731
+ // so the model sees them immediately; everything else becomes a path
1732
+ // reference.
1733
+ if (att.fileData) {
1734
+ const buf = Buffer.from(att.fileData, "base64");
1735
+ const MAX_UPLOAD_BYTES = 20 * 1024 * 1024;
1736
+ if (buf.length === 0) {
1737
+ this.emit({
1738
+ type: "notice",
1739
+ level: "error",
1740
+ text: `文件数据为空,已跳过`,
1741
+ });
1742
+ continue;
1743
+ }
1744
+ if (buf.length > MAX_UPLOAD_BYTES) {
1745
+ this.emit({
1746
+ type: "notice",
1747
+ level: "warning",
1748
+ text: `文件过大已跳过(>20MB):${att.name ?? "上传文件"}`,
1749
+ });
1750
+ continue;
1751
+ }
1752
+ // Uploaded files live in a GLOBAL per-user dir (not inside the project
1753
+ // or the per-client session store) so browsing a repo never picks up
1754
+ // uploaded junk: <home>/.pi-web/uploads/<clientId>/.
1755
+ const { homedir } = await import("node:os");
1756
+ const uploadsDir = join(homedir(), ".pi-web", "uploads", this.clientId);
1757
+ const safeName = (att.name ?? "file")
1758
+ .replace(/[\\/:*?"<>|\x00-\x1f]/g, "_")
1759
+ .slice(0, 80);
1760
+ const abs = join(uploadsDir, `${Date.now()}-${safeName}`);
1761
+ await fs.mkdir(uploadsDir, { recursive: true });
1762
+ await fs.writeFile(abs, buf);
1763
+ // Wire format: forward-slash absolute path (the read tool accepts
1764
+ // absolute paths; Windows uses "C:/..." — safe inside the XML-ish tag).
1765
+ const wirePath = abs.split(sep).join("/");
1766
+ if (buf.length <= MAX_INLINE_BYTES && looksLikeText(buf)) {
1767
+ const lines = countLines(buf);
1768
+ out.push({
1769
+ message: {
1770
+ customType: "file",
1771
+ content: [
1772
+ {
1773
+ type: "text",
1774
+ text: `\n<file path="${wirePath}">\n\`\`\`\n${buf.toString("utf8")}\n\`\`\`\n</file>`,
1775
+ },
1776
+ ],
1777
+ display: true,
1778
+ details: {
1779
+ name: safeName,
1780
+ path: wirePath,
1781
+ mode: "inline",
1782
+ size: buf.length,
1783
+ lines,
1784
+ },
1785
+ },
1786
+ });
1787
+ }
1788
+ else {
1789
+ out.push({
1790
+ message: {
1791
+ customType: "file",
1792
+ content: [
1793
+ {
1794
+ type: "text",
1795
+ text: `<file path="${wirePath}" size="${buf.length}" />`,
1796
+ },
1797
+ ],
1798
+ display: true,
1799
+ details: {
1800
+ name: safeName,
1801
+ path: wirePath,
1802
+ mode: "reference",
1803
+ size: buf.length,
1804
+ },
1805
+ },
1806
+ });
1807
+ }
1808
+ continue;
1809
+ }
1647
1810
  const abs = resolve(root, att.path);
1648
1811
  const rawRel = relative(root, abs);
1649
1812
  if (rawRel.startsWith("..") || rawRel.includes(`${sep}..`)) {
@@ -2543,6 +2706,7 @@ export class ClientSession {
2543
2706
  name: m.name,
2544
2707
  provider: m.provider,
2545
2708
  reasoning: m.reasoning,
2709
+ vision: m.input?.includes("image") ?? false,
2546
2710
  }));
2547
2711
  this.emit({ type: "models", models });
2548
2712
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-web-ui",
3
- "version": "0.11.5",
3
+ "version": "0.12.0",
4
4
  "description": "Web chat interface for the pi coding agent, powered by the pi SDK (@earendil-works/pi-coding-agent) — one-command run, Docker/systemd/launchd deployable",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Copyright (c) 2014 The xterm.js authors. All rights reserved.
3
+ * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
4
+ * https://github.com/chjj/term.js
5
+ * @license MIT
6
+ *
7
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ * of this software and associated documentation files (the "Software"), to deal
9
+ * in the Software without restriction, including without limitation the rights
10
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ * copies of the Software, and to permit persons to whom the Software is
12
+ * furnished to do so, subject to the following conditions:
13
+ *
14
+ * The above copyright notice and this permission notice shall be included in
15
+ * all copies or substantial portions of the Software.
16
+ *
17
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ * THE SOFTWARE.
24
+ *
25
+ * Originally forked from (with the author's permission):
26
+ * Fabrice Bellard's javascript vt100 for jslinux:
27
+ * http://bellard.org/jslinux/
28
+ * Copyright (c) 2011 Fabrice Bellard
29
+ * The original design remains. The terminal itself
30
+ * has been extended to include xterm CSI codes, among
31
+ * other features.
32
+ */.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{font-family:monospace;-webkit-user-select:text;user-select:text;white-space:pre}.xterm .xterm-accessibility-tree>div{transform-origin:left;width:fit-content}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}.xterm .xterm-scrollable-element>.scrollbar{cursor:default}.xterm .xterm-scrollable-element>.scrollbar>.scra{cursor:pointer;font-size:11px!important}.xterm .xterm-scrollable-element>.visible{opacity:1;background:#0000;transition:opacity .1s linear;z-index:11}.xterm .xterm-scrollable-element>.invisible{opacity:0;pointer-events:none}.xterm .xterm-scrollable-element>.invisible.fade{transition:opacity .8s linear}.xterm .xterm-scrollable-element>.shadow{position:absolute;display:none}.xterm .xterm-scrollable-element>.shadow.top{display:block;top:0;left:3px;height:3px;width:100%;box-shadow:var(--vscode-scrollbar-shadow, #000) 0 6px 6px -6px inset}.xterm .xterm-scrollable-element>.shadow.left{display:block;top:3px;left:0;height:100%;width:3px;box-shadow:var(--vscode-scrollbar-shadow, #000) 6px 0 6px -6px inset}.xterm .xterm-scrollable-element>.shadow.top-left-corner{display:block;top:0;left:0;height:3px;width:3px}.xterm .xterm-scrollable-element>.shadow.top.left{box-shadow:var(--vscode-scrollbar-shadow, #000) 6px 0 6px -6px inset}:root{--bg: #0d0e12;--bg-elev: #14161c;--bg-elev2: #1a1d26;--border: #262a35;--border-soft: #1e2230;--text: #e6e8ef;--text-dim: #9aa1b4;--text-faint: #6b7284;--accent: #8b5cf6;--accent-soft: rgba(139, 92, 246, .14);--green: #34d399;--green-soft: rgba(52, 211, 153, .12);--red: #f87171;--red-soft: rgba(248, 113, 113, .12);--amber: #fbbf24;--mono: "SF Mono", "JetBrains Mono", ui-monospace, Menlo, Consolas, monospace;--sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", Roboto, Helvetica, Arial, sans-serif}*{box-sizing:border-box}html,body,#root{height:100%;margin:0}body{background:var(--bg);color:var(--text);font-family:var(--sans);font-size:14px;line-height:1.6}.app{display:flex;flex-direction:column;height:100%}.layout{flex:1;display:flex;min-height:0}.main{flex:1;display:flex;flex-direction:column;min-width:0;min-height:0}.panel{display:flex;flex-direction:column;width:240px;flex-shrink:0;min-height:0;background:var(--bg-elev);border-right:1px solid var(--border);position:relative}.panel-right{border-right:none;border-left:1px solid var(--border)}.panel-header{display:flex;align-items:center;justify-content:space-between;padding:10px 12px 8px;border-bottom:1px solid var(--border-soft);flex-shrink:0}.panel-title{font-size:12px;font-weight:700;letter-spacing:.6px;text-transform:uppercase;color:var(--text-faint)}.panel-new,.panel-refresh{display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--border);border-radius:6px;background:var(--bg-elev2);color:var(--text-dim);cursor:pointer;padding:3px 8px;font-size:12px}.panel-new:hover,.panel-refresh:hover{color:var(--text);border-color:var(--accent)}.panel-fab{position:absolute;right:14px;bottom:14px;display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;border-radius:50%;border:1px solid var(--border);background:var(--bg-elev2);color:var(--text-dim);font-size:14px;cursor:pointer;box-shadow:0 6px 20px #00000073;z-index:2}.panel-fab:hover{color:var(--accent);border-color:var(--accent);box-shadow:0 8px 24px #00000080}.panel-fab:active{transform:scale(.94)}.panel-right .panel-body,.sessions-scroll{padding-bottom:56px}.panel-convs{flex-shrink:0;max-height:32%;overflow-y:auto;padding:6px 6px 0}.panel-sessions{flex:1;min-height:0;display:flex;flex-direction:column;padding:0 6px}.panel-sessions .panel-section-title{flex-shrink:0;padding-top:8px}.sessions-scroll{flex:1;overflow-y:auto;min-height:0}.panel-body{flex:1;overflow-y:auto;padding:6px}.panel-footer{padding:8px 12px;border-top:1px solid var(--border-soft);flex-shrink:0}.panel-widgets{flex-shrink:0;max-height:40%;overflow-y:auto;padding:8px;border-top:1px solid var(--border-soft);background:#00000026}.widget{border:1px solid var(--border-soft);border-radius:8px;background:var(--bg-elev2);margin-bottom:6px;overflow:hidden}.widget:last-child{margin-bottom:0}.widget-title{padding:4px 10px;font-size:10.5px;font-weight:700;letter-spacing:.5px;text-transform:uppercase;color:var(--text-faint);border-bottom:1px solid var(--border-soft)}.widget-lines{margin:0;padding:6px 10px;font-family:var(--mono);font-size:11px;line-height:1.5;color:var(--text-dim);white-space:pre;overflow-x:auto}.pc-item{display:flex;align-items:flex-start;gap:8px;width:100%;padding:5px 8px;border:none;border-radius:6px;background:transparent;color:var(--text-dim);font-size:12px;text-align:left;cursor:pointer}.pc-item:hover,.pc-item.sel{background:var(--accent-soft);color:var(--text)}.pc-icon{flex-shrink:0;margin-top:1px;color:var(--text-faint)}.pc-item.sel .pc-icon{color:var(--accent)}.pc-body{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px}.pc-name{font-family:var(--mono);font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pc-path{font-size:10.5px;line-height:1.4;color:var(--text-faint);font-family:var(--mono);word-break:break-all;overflow-wrap:anywhere}.panel-empty{padding:16px 8px;text-align:center;font-size:12px;color:var(--text-faint)}.files-truncated{color:var(--amber);border-top:1px dashed var(--border);margin-top:8px;padding-top:12px}.session-item{display:flex;align-items:center;gap:8px;width:100%;padding:8px 10px;border:none;border-radius:8px;background:transparent;color:var(--text-dim);font-size:12.5px;text-align:left;cursor:pointer;margin-bottom:2px}.session-item:hover{background:var(--bg-elev2);color:var(--text)}.session-item.active{background:var(--accent-soft);color:var(--text);border:1px solid rgba(139,92,246,.3);padding:7px 9px}.session-icon{flex-shrink:0;color:var(--text-faint)}.session-item.active .session-icon{color:var(--accent)}.session-info{flex:1;min-width:0;display:flex;flex-direction:column}.session-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.session-sub{font-size:11px;color:var(--text-faint)}.session-src{margin-left:6px;font-size:9.5px;padding:0 5px;border-radius:999px;border:1px solid var(--border);color:var(--text-faint);text-transform:uppercase;letter-spacing:.4px;vertical-align:1px}.session-time{font-size:10.5px;color:var(--text-faint);flex-shrink:0}.panel-projects{flex-shrink:0;max-height:45%;overflow-y:auto;padding:6px 6px 8px;border-bottom:2px solid var(--border)}.panel-section-title{padding:4px 10px 6px;font-size:11px;font-weight:700;letter-spacing:.6px;text-transform:uppercase;color:var(--text-faint)}.panel-section-divider{height:1px;background:var(--border);margin:6px 10px}.conv-streaming{width:7px;height:7px;border-radius:50%;flex-shrink:0;background:var(--green);box-shadow:0 0 4px var(--green);animation:conv-pulse 1.2s ease-in-out infinite}@keyframes conv-pulse{0%,to{opacity:1}50%{opacity:.25}}.project-item{display:flex;align-items:center;gap:8px;width:100%;padding:7px 10px;border:none;border-radius:8px;background:transparent;color:var(--text-dim);font-size:12.5px;text-align:left;cursor:pointer;margin-bottom:2px}.project-item:hover{background:var(--bg-elev2);color:var(--text)}.project-item.active{background:var(--accent-soft);color:var(--text);border:1px solid rgba(139,92,246,.3);padding:6px 9px}.project-icon{flex-shrink:0;color:var(--text-faint)}.project-item.active .project-icon{color:var(--accent)}.project-info{flex:1;min-width:0;display:flex;flex-direction:column}.project-name{font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.project-path{font-size:11px;color:var(--text-faint);font-family:var(--mono);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.project-time{font-size:10.5px;color:var(--text-faint);flex-shrink:0}.panel-crumbs{display:flex;align-items:center;flex-wrap:wrap;gap:2px;padding:6px 8px;border-bottom:1px solid var(--border-soft);flex-shrink:0;font-size:12px}.crumb{border:none;background:transparent;color:var(--text-faint);cursor:pointer;padding:2px 4px;border-radius:4px;font-size:12px;max-width:90px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.crumb:hover{color:var(--text);background:var(--bg-elev2)}.crumb.active{color:var(--accent)}.crumb-seg{display:inline-flex;align-items:center;color:var(--text-faint)}.file-item{display:flex;align-items:center;gap:7px;width:100%;padding:5px 8px;border:none;border-radius:6px;background:transparent;color:var(--text-dim);font-size:12.5px;text-align:left;cursor:pointer}.file-item.dir{cursor:default}.file-dir-main{flex:1;min-width:0;display:flex;align-items:center;gap:7px;border:none;background:transparent;color:inherit;font-size:inherit;text-align:left;cursor:pointer;padding:0}.file-item:hover{background:var(--bg-elev2);color:var(--text)}.file-icon{flex-shrink:0;color:var(--text-faint)}.file-item.dir .file-icon{color:#fbbf24}.file-name{flex:1;min-width:0;display:flex;align-items:center;gap:7px;border:none;background:transparent;color:inherit;font-size:inherit;font-family:inherit;text-align:left;padding:0;overflow:hidden;cursor:pointer}.file-name:hover{color:var(--accent)}.file-name.no-preview{cursor:default}.file-name.no-preview:hover{color:inherit}.file-name-text{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.file-attach{position:relative;display:none;align-items:center;justify-content:center;width:22px;height:22px;border:none;border-radius:5px;cursor:pointer;flex-shrink:0}.file-item:hover .file-attach{display:inline-flex}.file-attach:after{content:attr(data-tip);position:absolute;bottom:calc(100% + 8px);right:0;background:#232733;color:var(--text);border:1px solid var(--border);border-radius:6px;padding:4px 9px;font-size:11px;line-height:1.4;white-space:nowrap;pointer-events:none;opacity:0;transition:opacity .09s ease;z-index:40;box-shadow:0 4px 14px #00000073}.file-attach:before{content:"";position:absolute;bottom:calc(100% + 3px);right:6px;border:5px solid transparent;border-top-color:#232733;pointer-events:none;opacity:0;transition:opacity .09s ease;z-index:40}.file-attach:hover:after,.file-attach:hover:before{opacity:1}.chip[data-tip]{position:relative}.chip[data-tip]:after{content:attr(data-tip);position:absolute;top:calc(100% + 8px);right:0;background:#232733;color:var(--text);border:1px solid var(--border);border-radius:6px;padding:4px 9px;font-size:11px;line-height:1.4;white-space:nowrap;pointer-events:none;opacity:0;transition:opacity .09s ease;z-index:40;box-shadow:0 4px 14px #00000073}.chip[data-tip]:hover:after{opacity:1}.file-attach.inline{background:var(--accent-soft);color:var(--accent)}.file-attach.inline:hover{background:var(--accent);color:#fff}.file-attach.download{background:#34d39924;color:var(--green);text-decoration:none}.file-attach.download:hover{background:var(--green);color:#fff}.file-attach.ref{background:#fbbf241f;color:var(--amber)}.file-attach.ref:hover{background:var(--amber);color:#1a1a1a}.attach-row{max-width:860px;margin:0 auto 6px;display:flex;flex-wrap:wrap;gap:6px;align-items:center}.attach-chip{display:inline-flex;align-items:center;gap:5px;padding:3px 10px;border:1px solid rgba(139,92,246,.4);border-radius:999px;background:var(--accent-soft);color:var(--text);font-size:12px;max-width:320px}.attach-chip.reference{border-color:#fbbf2466;background:#fbbf241a}.attach-chip.lines{border-color:#34d39966;background:#34d3991a}.attach-chip.image{border-color:#ec489973;background:#ec48991f}.attach-chip.file{border-color:#60a5fa73;background:#60a5fa1f}.inputbar.drop-active{outline:2px dashed var(--accent);outline-offset:-2px}.drop-overlay{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;align-items:center;justify-content:center;background:color-mix(in srgb,var(--bg-elev) 88%,transparent);z-index:5;pointer-events:none}.drop-overlay span{padding:10px 18px;border:1px dashed var(--accent);border-radius:10px;background:var(--bg-elev2);color:var(--text);font-size:14px}.inputbox-actions .btn.attach-img{padding:7px 10px}.inputbox-actions .btn.attach-img svg{width:16px;height:16px}.attach-range{font-family:var(--mono);font-size:10.5px;padding:0 6px;border-radius:999px;background:#34d3992e;color:var(--green)}.attach-remove{border:none;background:transparent;color:var(--text-dim);cursor:pointer;font-size:14px;line-height:1;padding:0 2px}.attach-remove:hover{color:var(--red)}.attach-hint{font-size:11.5px;color:var(--text-faint)}.attachcard{margin:6px 0;border:1px dashed rgba(139,92,246,.45);border-radius:8px;background:var(--accent-soft);overflow:hidden}.attachcard-head{display:flex;align-items:center;gap:8px;width:100%;padding:7px 12px;border:none;background:transparent;color:var(--text);font-size:12.5px;cursor:pointer;text-align:left}.attachcard-head:hover{background:#8b5cf614}.attachcard-icon{flex-shrink:0}.attachcard-name{font-weight:600;font-family:var(--mono);font-size:12px}.attachcard-path{color:var(--text-faint);font-size:11px;font-family:var(--mono);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:200px}.attachcard-meta{margin-left:auto;color:var(--text-faint);font-size:11px;flex-shrink:0}.attachcard-mode{margin-left:auto;font-size:11px;flex-shrink:0;padding:1px 8px;border-radius:999px;border:1px solid var(--border);color:var(--text-faint)}.attachcard-mode.inline{color:var(--green);border-color:#34d39959}.attachcard-mode.ref{color:var(--amber);border-color:#fbbf2466}.attachcard-mode.lines{color:var(--accent);border-color:#8b5cf666}.attachcard-refnote{padding:8px 12px;border-top:1px dashed rgba(251,191,36,.3);font-size:11.5px;color:var(--text-faint)}.attachcard-content{margin:0;padding:10px 12px;border-top:1px dashed rgba(139,92,246,.3);font-family:var(--mono);font-size:12px;line-height:1.55;color:var(--text-dim);white-space:pre-wrap;word-break:break-word;max-height:320px;overflow-y:auto;background:#0003}.attachcard-image img{max-width:100%;max-height:320px;border-radius:0 0 8px 8px;display:block}.view-pane{flex:1;display:flex;min-width:0;min-height:0}.view-pane.hidden{display:none}.view-switch{display:inline-flex;align-items:center;border:1px solid var(--border);border-radius:999px;overflow:hidden;background:var(--bg-elev2);flex-shrink:0}.view-switch button{display:inline-flex;align-items:center;gap:5px;border:none;background:transparent;color:var(--text-dim);font-size:12.5px;padding:4px 12px;cursor:pointer;transition:background .15s,color .15s}.view-switch button:hover{color:var(--text)}.view-switch button.active{background:var(--accent-soft);color:var(--accent)}.view-switch button+button{border-left:1px solid var(--border)}.terminal-view{flex:1;display:flex;min-height:0;min-width:0}.term-side{display:flex;flex-direction:column;width:250px;flex-shrink:0;min-height:0;background:var(--bg-elev)}.term-commands{border-right:1px solid var(--border)}.term-tabs{width:200px;border-left:1px solid var(--border)}.term-main{flex:1;display:flex;flex-direction:column;min-width:0;min-height:0;background:#0b0d12}.term-xterm{flex:1;min-height:0;padding:10px 8px 8px}.term-xterm.hidden{display:none}.term-xterm .xterm{height:100%}.term-empty{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;color:var(--text-faint)}.term-empty-icon{font-size:34px;opacity:.5}.term-empty-title{font-size:15px;color:var(--text-dim)}.term-empty-sub{font-size:12px}.panel-header-actions{display:flex;align-items:center;gap:6px}.cmd-item{display:flex;align-items:center;gap:4px;padding:6px;border-radius:8px;margin-bottom:2px}.cmd-item:hover{background:var(--bg-elev2)}.cmd-run{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:24px;height:24px;border:none;border-radius:6px;background:var(--accent-soft);color:var(--accent);cursor:pointer}.cmd-run:hover{background:var(--accent);color:#fff}.cmd-main{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px;text-align:left;background:transparent;border:none;color:var(--text-dim);cursor:pointer;padding:1px 4px}.cmd-name{font-size:12.5px;font-weight:600;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.cmd-command{font-family:var(--mono);font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.cmd-cwd{font-size:10px;color:var(--text-faint);font-family:var(--mono);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.cmd-act{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:22px;height:22px;border:none;border-radius:5px;background:transparent;color:var(--text-faint);cursor:pointer;opacity:0;transition:opacity .1s;font-size:11px}.cmd-item:hover .cmd-act{opacity:1}.cmd-act:hover{color:var(--text);background:var(--bg-elev)}.cmd-act.del:hover{color:var(--red)}.cmd-act.del.confirm{opacity:1;color:var(--red);background:var(--red-soft);border:1px solid rgba(248,113,113,.4);width:auto;padding:0 7px}.cmd-form{display:flex;flex-direction:column;gap:4px;padding:6px 4px}.cmd-form label{font-size:11px;color:var(--text-faint);margin-top:4px}.cmd-input{width:100%;border:1px solid var(--border);border-radius:6px;background:var(--bg-elev2);color:var(--text);font-family:var(--mono);font-size:12px;padding:5px 8px;outline:none}.cmd-input:focus{border-color:var(--accent)}.cmd-hint{color:var(--text-faint);font-size:10.5px}.cmd-form-actions{display:flex;gap:6px;justify-content:flex-end;margin-top:8px}.cmd-file{display:flex;align-items:center;gap:5px;font-size:10.5px;color:var(--text-faint);font-family:var(--mono);min-width:0}.cmd-file-icon{flex-shrink:0}.cmd-file-path{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.cmd-file-hint{margin-top:3px;font-size:10.5px;color:var(--text-faint)}.term-tab{display:flex;align-items:center;gap:2px;padding:4px 6px;border-radius:8px;margin-bottom:2px}.term-tab:hover{background:var(--bg-elev2)}.term-tab.active{background:var(--accent-soft);border:1px solid rgba(139,92,246,.3);padding:3px 5px}.term-tab-main{flex:1;min-width:0;display:flex;align-items:center;gap:6px;background:transparent;border:none;color:var(--text-dim);cursor:pointer;text-align:left;padding:2px}.term-tab-main:hover,.term-tab.active .term-tab-main{color:var(--text)}.term-tab-dot{flex-shrink:0;width:7px;height:7px;border-radius:50%}.term-tab-dot.run{background:var(--green);box-shadow:0 0 5px var(--green)}.term-tab-dot.exit{background:var(--text-faint)}.term-tab-title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12.5px}.term-tab-exit{color:var(--text-faint);font-size:11px}.term-tab-close{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:20px;height:20px;border:none;border-radius:5px;background:transparent;color:var(--text-faint);cursor:pointer;opacity:0;transition:opacity .1s}.term-tab:hover .term-tab-close,.term-tab.active .term-tab-close{opacity:1}.term-tab-close:hover{color:var(--red);background:var(--red-soft)}.statusbar{flex-shrink:0;display:flex;align-items:center;gap:8px;padding:5px 16px;border-top:1px solid var(--border);background:var(--bg-elev);font-size:11px;color:var(--text-faint);white-space:nowrap;overflow:hidden}.status-dot{width:7px;height:7px;border-radius:50%;flex-shrink:0}.status-dot.ok{background:var(--green);box-shadow:0 0 5px var(--green)}.status-dot.busy{background:var(--amber)}.status-item{display:inline-flex;align-items:center;gap:6px;color:var(--text-dim)}.status-sep{color:var(--border)}.ctx-bar{display:inline-block;width:46px;height:5px;border-radius:3px;background:var(--bg-elev2);border:1px solid var(--border-soft);overflow:hidden;vertical-align:middle}.ctx-bar-fill{display:block;height:100%;border-radius:2px;transition:width .3s ease}.ctx-bar.ok .ctx-bar-fill{background:var(--green)}.ctx-bar.mid .ctx-bar-fill{background:var(--amber)}.ctx-bar.warn .ctx-bar-fill{background:var(--red)}.status-item.working{color:var(--accent)}.working-spin{width:9px;height:9px;border-radius:50%;border:2px solid rgba(139,92,246,.25);border-top-color:var(--accent);animation:spin .8s linear infinite;flex-shrink:0}.status-cwd{margin-left:auto;max-width:42%;overflow:hidden;text-overflow:ellipsis;color:var(--text-faint);border:none;background:transparent;font:inherit;font-size:11px;cursor:pointer;padding:2px 4px;border-radius:4px}.status-cwd:hover{color:var(--text-dim);background:var(--bg-elev2)}.status-cwd-wrap{position:relative;margin-left:auto;max-width:46%;min-width:0;display:flex;align-items:center}.status-cwd-input{width:100%;border:1px solid var(--accent);border-radius:6px;background:var(--bg-elev2);color:var(--text);font-family:var(--mono);font-size:11px;padding:3px 7px;outline:none}.status-completions{position:fixed;right:16px;bottom:40px;width:min(440px,calc(100vw - 32px));max-height:260px;overflow-y:auto;overflow-x:hidden;margin:0;padding:4px;list-style:none;background:var(--bg-elev2);border:1px solid var(--border);border-radius:8px;box-shadow:0 -6px 24px #00000073;z-index:200}.status-queue{color:var(--amber)}@keyframes spin{to{transform:rotate(360deg)}}.topbar{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 16px;border-bottom:1px solid var(--border);background:var(--bg-elev);flex-shrink:0}.brand{display:flex;align-items:center;gap:8px;min-width:0}.brand-logo{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border-radius:7px;background:linear-gradient(135deg,#8b5cf6,#6366f1);color:#fff;font-weight:700;font-size:16px}.brand-name{font-weight:600;letter-spacing:.2px}.conn-dot{width:8px;height:8px;border-radius:50%;margin-left:6px}.conn-dot.ok{background:var(--green);box-shadow:0 0 6px var(--green)}.conn-dot.busy{background:var(--amber)}.conn-dot.err{background:var(--red)}.conn-label{color:var(--text-faint);font-size:12px}.topbar-actions{display:flex;align-items:center;gap:8px;min-width:0}.chip{display:inline-flex;align-items:center;gap:6px;padding:5px 10px;border:1px solid var(--border);border-radius:999px;background:var(--bg-elev2);color:var(--text);font-size:12.5px;cursor:pointer;transition:border-color .15s,background .15s;white-space:nowrap}.chip:hover{border-color:var(--accent);background:var(--accent-soft)}.chip-model{max-width:220px;overflow:hidden;text-overflow:ellipsis}.chip-vision{color:var(--accent);font-size:11px;line-height:1}.chip-sub{color:var(--text-faint);font-size:11px}.chip.newchat{color:var(--accent);border-color:#8b5cf666}.chip.newchat:hover{background:var(--accent-soft)}.dropdown{position:relative;display:inline-flex}.dd-caret{color:var(--text-faint);transition:transform .15s}.dd-caret.up{transform:rotate(180deg)}.dd-menu{position:absolute;top:calc(100% + 6px);right:0;min-width:240px;max-width:340px;max-height:420px;overflow-y:auto;background:var(--bg-elev2);border:1px solid var(--border);border-radius:10px;box-shadow:0 12px 40px #00000080;padding:6px;z-index:60}.dropdown.left .dd-menu{right:auto;left:0}.dropdown.fit .dd-menu{max-height:none;overflow:visible}.dd-header{padding:6px 10px 4px;font-size:11px;font-weight:700;letter-spacing:.6px;text-transform:uppercase;color:var(--text-faint)}.dd-loading{padding:10px;font-size:12px;color:var(--text-faint);text-align:center}.dd-item{display:flex;align-items:center;justify-content:space-between;gap:10px;width:100%;padding:7px 10px;border:none;border-radius:7px;background:transparent;color:var(--text-dim);font-size:13px;text-align:left;cursor:pointer}.dd-item:hover{background:var(--bg-elev);color:var(--text)}.dd-item:disabled{opacity:.45;cursor:not-allowed}.dd-item:disabled:hover{background:transparent;color:var(--text-dim)}.dd-item.active{background:var(--accent-soft);color:var(--text)}.dd-item.active:after{content:"✓";color:var(--accent);font-size:12px}.dd-model-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dd-model-sub{flex-shrink:0;font-size:11px;color:var(--text-faint);font-family:var(--mono)}.dd-refresh{width:100%;margin-top:4px;padding:6px;border:none;border-top:1px solid var(--border-soft);background:transparent;color:var(--text-faint);font-size:12px;cursor:pointer}.dd-refresh:hover{color:var(--accent)}.update-dot{width:7px;height:7px;border-radius:50%;background:var(--amber);flex-shrink:0;box-shadow:0 0 0 2px #f59e0b40}.dd-update{display:flex;flex-direction:column;gap:6px;padding:8px 10px 10px}.dd-row{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:12.5px;color:var(--text-dim)}.dd-row b{color:var(--text);font-family:var(--mono);font-weight:600}.dd-note{font-size:12px;line-height:1.5;padding:6px 10px;border-radius:6px;background:var(--bg-elev1);border:1px solid var(--border-soft);color:var(--text-dim)}.dd-note.ok{color:var(--green);border-color:#34d3994d}.dd-note.warn{color:var(--amber);border-color:#f59e0b59}.dd-note.err{color:var(--red);border-color:#f8717159}.dd-actions{display:flex;gap:6px;padding:0 2px 2px}.dd-actions .dd-refresh{margin-top:0;border:1px solid var(--border);border-radius:6px;background:var(--bg-elev1)}.dd-refresh.accent{color:var(--accent);border-color:#8b5cf666}.dd-refresh.accent.armed{color:var(--amber);border-color:#f59e0b80}.notices{position:fixed;top:52px;left:50%;transform:translate(-50%);z-index:50;display:flex;flex-direction:column;gap:6px;align-items:center;pointer-events:none}.notice{pointer-events:auto;max-width:min(720px,90vw);padding:7px 14px;border-radius:8px;border:1px solid var(--border);background:var(--bg-elev2);color:var(--text);font-size:12.5px;box-shadow:0 6px 24px #00000073;cursor:pointer;text-align:center}.notice-error{border-color:#f8717180;color:#fca5a5}.notice-warning{border-color:#fbbf2480;color:#fcd34d}.messages-wrap{flex:1;position:relative;min-height:0;display:flex;flex-direction:column}.messages{flex:1;overflow-y:auto;padding:20px 0 12px}.msg{max-width:860px;margin:0 auto;padding:6px 20px 14px}.msg-meta{display:flex;align-items:baseline;gap:10px;margin-bottom:4px}.msg-role{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.8px;color:var(--text-faint)}.msg-model{font-size:11px;color:var(--accent);font-family:var(--mono)}.msg-customtype{font-size:11px;color:var(--amber)}.msg-time{font-size:11px;color:var(--text-faint);margin-left:auto}.msg-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:2px}.msg-action{display:inline-flex;align-items:center;gap:4px;border:none;background:transparent;color:var(--text-faint);font-size:11.5px;cursor:pointer;padding:3px 8px;border-radius:6px;opacity:0;transition:opacity .15s ease,color .15s ease,background .15s ease}.msg:hover .msg-action,.msg-action:focus-visible{opacity:1}.msg-action:hover{color:var(--accent);background:var(--bg-elev2)}.msg-editor{display:flex;flex-direction:column;gap:8px}.msg-editor-input{width:100%;min-height:48px;resize:vertical;padding:10px 12px;border:1px solid var(--border);border-radius:10px;background:var(--bg-elev1);color:var(--text);font-family:inherit;font-size:13.5px;line-height:1.6;outline:none}.msg-editor-input:focus{border-color:var(--accent);box-shadow:0 0 0 2px #8b5cf626}.msg-editor-actions{display:flex;align-items:center;justify-content:flex-end;gap:8px}.msg-editor-hint{margin-right:auto;font-size:11px;color:var(--text-faint)}.chip.primary{border-color:var(--accent);background:var(--accent-soft);color:var(--accent)}.chip.primary:disabled{opacity:.45;cursor:not-allowed}.msg-collapsed{display:flex;align-items:center;gap:10px;width:100%;max-width:860px;margin:0 auto 2px;padding:7px 12px;border:1px solid var(--border-soft);border-radius:8px;background:var(--bg-elev1);color:var(--text-dim);font-size:12px;text-align:left;cursor:pointer;transition:border-color .15s ease,background .15s ease,color .15s ease}.msg-collapsed:hover{border-color:var(--border);background:var(--bg-elev2);color:var(--text)}.msg-collapsed-role{flex-shrink:0;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.7px;color:var(--text-faint);min-width:52px}.msg-collapsed-role.role-user{color:var(--accent)}.msg-collapsed-role.role-assistant{color:var(--green)}.msg-collapsed-role.role-bashExecution,.msg-collapsed-role.role-toolResult{color:var(--amber)}.msg-collapsed-body{flex:1;min-width:0;display:flex;align-items:center;gap:8px;overflow:hidden}.msg-collapsed-preview{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--text-dim)}.msg-collapsed-chips{display:inline-flex;gap:4px;flex-shrink:0}.msg-collapsed-chip{font-size:10px;color:var(--text-faint);padding:1px 6px;border-radius:999px;border:1px solid var(--border);white-space:nowrap}.msg-collapsed-time{flex-shrink:0;font-size:10.5px;color:var(--text-faint)}.msg-collapsed-action{flex-shrink:0;display:inline-flex;align-items:center;gap:3px;font-size:11px;color:var(--text-faint)}.msg-collapsed:hover .msg-collapsed-action{color:var(--accent)}.msg-collapse-btn{display:inline-flex;align-items:center;gap:3px;margin-left:auto;border:none;background:transparent;color:var(--text-faint);font-size:11px;cursor:pointer;padding:1px 6px;border-radius:5px}.msg-collapse-btn:hover{color:var(--accent);background:var(--bg-elev2)}.msg-text{color:var(--text)}.msg-text p{margin:6px 0}.msg-text p:first-child{margin-top:0}.msg-text p:last-child{margin-bottom:0}.msg-text ul,.msg-text ol{padding-left:22px;margin:6px 0}.msg-text li{margin:2px 0}.msg-text blockquote{margin:8px 0;padding:2px 14px;border-left:3px solid var(--accent);color:var(--text-dim)}.msg-text table{border-collapse:collapse;margin:8px 0}.msg-text th,.msg-text td{border:1px solid var(--border);padding:4px 10px}.msg-text a{color:#a78bfa}.msg-text code{font-family:var(--mono);font-size:12.5px;background:var(--bg-elev2);border:1px solid var(--border-soft);border-radius:4px;padding:1px 5px}.codeblock{position:relative;margin:10px 0}.codeblock pre{background:#0b0d12!important;border:1px solid var(--border);border-radius:8px;padding:12px 14px;overflow-x:auto;margin:0}.codeblock code{background:transparent;border:none;padding:0;font-family:var(--mono);font-size:12.5px;line-height:1.55}.copy-btn{position:absolute;top:8px;right:8px;z-index:2;display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border-radius:6px;border:1px solid var(--border);background:var(--bg-elev2);color:var(--text-dim);cursor:pointer;opacity:0;transition:opacity .15s}.codeblock:hover .copy-btn{opacity:1}.copy-btn:hover{color:var(--text);border-color:var(--accent)}.trunc-note{margin-top:6px;font-size:11.5px;color:var(--text-faint);font-style:italic}.msg-image img{max-width:100%;max-height:420px;border-radius:8px;border:1px solid var(--border)}.thinking{margin:8px 0;border:1px solid var(--border-soft);border-radius:8px;background:#ffffff04}.thinking-toggle{display:flex;align-items:center;gap:7px;width:100%;padding:7px 12px;border:none;background:transparent;color:var(--text-dim);font-size:12.5px;cursor:pointer;text-align:left}.thinking-toggle:hover{color:var(--text)}.thinking-icon{color:var(--accent)}.thinking-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.thinking-body{padding:4px 14px 12px;color:var(--text-dim);font-style:italic;font-size:13px;white-space:pre-wrap;border-top:1px solid var(--border-soft)}.thinking-live .thinking-toggle{color:var(--text)}.thinking-live-label{display:inline-flex;align-items:center}.thinking-live-label .dots:after{content:"";display:inline-block;width:1.2em;text-align:left;animation:thinking-dots 1.2s steps(4,end) infinite}@keyframes thinking-dots{0%{content:""}25%{content:"."}50%{content:".."}75%{content:"..."}}.thinking-wait{display:inline-flex;align-items:center;gap:5px;color:var(--text-dim);font-size:13px;padding:2px 0}.thinking-wait .dot{display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--accent);animation:stream-blink 1s steps(2,start) infinite}.stream-cursor{display:inline-block;width:8px;height:1.05em;margin-left:3px;vertical-align:text-bottom;border-radius:1px;background:var(--accent);animation:stream-blink 1s steps(2,start) infinite}@keyframes stream-blink{0%,49%{opacity:1}50%,to{opacity:0}}.toolcall{margin:8px 0;border:1px solid var(--border);border-radius:8px;background:var(--bg-elev);overflow:hidden}.toolcall-head{display:flex;align-items:center;gap:8px;padding:7px 12px;font-size:12.5px}.toolcall-icon{font-size:13px}.toolcall-name{font-family:var(--mono);font-weight:600;color:var(--text)}.toolcall-status{font-size:11px;padding:1px 8px;border-radius:999px;border:1px solid var(--border);color:var(--text-faint)}.toolcall.run .toolcall-status{color:var(--amber);border-color:#fbbf2466}.toolcall.ok .toolcall-status{color:var(--green);border-color:#34d39959}.toolcall.err .toolcall-status{color:var(--red);border-color:#f8717166}.toolcall.err{border-color:#f8717159}.toolcall-spacer{flex:1}.toolcall-copy,.toolcall-toggle{display:inline-flex;align-items:center;justify-content:center;border:none;background:transparent;color:var(--text-faint);cursor:pointer;padding:3px;border-radius:5px}.toolcall-copy:hover,.toolcall-toggle:hover{color:var(--text);background:var(--bg-elev2)}.toolcall-body{border-top:1px solid var(--border-soft);padding:8px 12px 10px}.toolcall-args pre{margin:0;font-family:var(--mono);font-size:12px;color:var(--text-dim);white-space:pre;overflow:auto;max-height:200px;background:var(--bg-elev2);border:1px solid var(--border-soft);border-radius:6px;padding:8px 10px}.termline{display:flex;align-items:center;gap:8px;background:#0b0d12;border:1px solid var(--border-soft);border-radius:6px;padding:8px 10px;font-family:var(--mono);font-size:12.5px}.termline-icon{color:var(--green);flex-shrink:0}.termline code{word-break:break-all}.termline-timeout{margin-left:auto;color:var(--text-faint);font-size:11px;flex-shrink:0}.toolcall-output{margin-top:8px}.toolcall-output-label{font-size:11px;text-transform:uppercase;letter-spacing:.6px;color:var(--text-faint);margin-bottom:4px}.toolcall-output pre{margin:0;max-height:320px;overflow:auto;background:#0b0d12;border:1px solid var(--border-soft);border-radius:6px;padding:8px 10px;font-family:var(--mono);font-size:12px;line-height:1.5;color:#c9d1d9;white-space:pre;word-break:normal}.toolcall.err .toolcall-output pre{color:#f0a5a5}.toolcall-waiting{margin-top:8px;font-size:12px;color:var(--text-faint)}.cursor{display:inline-block;width:7px;height:13px;background:var(--accent);vertical-align:-2px;margin-left:4px;animation:blink 1s steps(2,start) infinite}@keyframes blink{50%{opacity:0}}.bashblock{margin:8px 0;border:1px solid var(--border);border-radius:8px;background:#0b0d12;overflow:hidden}.bashblock-command{display:flex;align-items:center;gap:8px;padding:7px 12px;font-family:var(--mono);font-size:12.5px;border-bottom:1px solid var(--border-soft)}.bashblock-prompt{color:var(--green);font-weight:700}.bashblock-exit{margin-left:auto;font-size:11px;padding:1px 8px;border-radius:999px;border:1px solid var(--border)}.bashblock-exit.ok{color:var(--green)}.bashblock-exit.err{color:var(--red)}.bashblock-output{margin:0;padding:10px 12px;font-family:var(--mono);font-size:12px;line-height:1.5;color:#c9d1d9;white-space:pre;overflow:auto;max-height:320px}.msg-user .msg-body{background:var(--accent-soft);border:1px solid rgba(139,92,246,.25);border-radius:12px;padding:10px 14px}.msg-error{color:var(--red);background:var(--red-soft);border:1px solid rgba(248,113,113,.35);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:13px}.empty-state{display:flex;flex-direction:column;align-items:center;text-align:center;padding:70px 20px 30px;color:var(--text-dim)}.empty-logo-wrap{position:relative;margin-bottom:20px}.empty-logo{width:68px;height:68px;display:flex;align-items:center;justify-content:center;font-size:40px;font-weight:700;color:#fff;background:linear-gradient(135deg,#8b5cf6,#6366f1 60%,#4f46e5);border-radius:20px;box-shadow:0 16px 44px #8b5cf661,inset 0 1px #ffffff38}.empty-logo-wrap:before{content:"";position:absolute;top:-18px;right:-18px;bottom:-18px;left:-18px;border-radius:32px;background:radial-gradient(ellipse at center,rgba(139,92,246,.28),transparent 70%);z-index:-1}.empty-title{margin:0 0 6px;color:var(--text);font-size:22px;font-weight:650;letter-spacing:.3px}.empty-sub{margin:0 0 16px;font-size:13px;color:var(--text-faint)}.empty-cwd{display:inline-flex;align-items:center;gap:7px;max-width:min(560px,80vw);padding:4px 12px;border:1px solid var(--border-soft);border-radius:999px;background:var(--bg-elev);font-size:11.5px}.empty-cwd-label{color:var(--text-faint);flex-shrink:0}.empty-cwd-path{color:var(--text-dim);font-family:var(--mono);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.empty-examples{margin-top:34px;display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:10px;width:min(560px,90vw)}.empty-example{display:flex;align-items:center;gap:10px;padding:11px 14px;border:1px solid var(--border);border-radius:12px;background:var(--bg-elev);color:var(--text-dim);font-size:13px;text-align:left;cursor:pointer;transition:border-color .15s,background .15s,transform .12s,box-shadow .15s}.empty-example:hover{border-color:#8b5cf680;background:var(--accent-soft);color:var(--text);transform:translateY(-1px);box-shadow:0 8px 22px #8b5cf629}.empty-example-icon{font-size:17px;flex-shrink:0}.empty-example-text{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.streaming-wait,.boot-wait{text-align:center;color:var(--text-faint);padding:60px 20px;font-size:13px}.scroll-bottom{position:absolute;bottom:16px;left:50%;transform:translate(-50%);display:inline-flex;align-items:center;gap:6px;padding:6px 14px;border-radius:999px;border:1px solid var(--border);background:var(--bg-elev2);color:var(--text-dim);font-size:12px;cursor:pointer;box-shadow:0 6px 20px #0006}.scroll-bottom:hover{color:var(--text);border-color:var(--accent)}.inputbar{flex-shrink:0;padding:10px 20px 12px;border-top:1px solid var(--border);background:var(--bg-elev);position:relative}.queue-hint{max-width:860px;margin:0 auto 6px;display:flex;gap:14px;font-size:12px;color:var(--amber)}.inputbox{max-width:860px;margin:0 auto;display:flex;align-items:flex-end;gap:8px;background:var(--bg-elev2);border:1px solid var(--border);border-radius:12px;padding:8px 8px 8px 14px;transition:border-color .15s}.inputbox:focus-within{border-color:var(--accent)}.inputbox textarea{flex:1;resize:none;border:none;outline:none;background:transparent;color:var(--text);font-family:var(--sans);font-size:14px;line-height:1.55;max-height:220px;padding:4px 0}.inputbox textarea::placeholder{color:var(--text-faint)}.inputbox textarea:disabled{opacity:.6}.inputbox-actions{display:flex;align-items:center;gap:6px;flex-shrink:0}.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;border:1px solid var(--border);border-radius:8px;padding:7px 12px;font-size:13px;cursor:pointer;transition:all .15s}.btn:disabled{opacity:.4;cursor:default}.btn.send{background:var(--accent);border-color:var(--accent);color:#fff}.btn.send:hover:not(:disabled){filter:brightness(1.12)}.btn.supplement{background:transparent;border-color:#8b5cf680;color:var(--accent)}.btn.supplement:hover:not(:disabled){background:var(--accent);border-color:var(--accent);color:#fff}.btn.stop{background:var(--red-soft);border-color:#f8717166;color:#fca5a5}.btn.stop:hover{background:#f8717138}::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-thumb{background:#2a2f3d;border-radius:6px;border:2px solid var(--bg)}::-webkit-scrollbar-thumb:hover{background:#3a4152}::-webkit-scrollbar-track{background:transparent}@media(max-width:640px){.chip-model{max-width:120px}.brand-name,.conn-label{display:none}}.dialog-overlay{position:fixed;top:0;right:0;bottom:0;left:0;z-index:200;display:flex;align-items:center;justify-content:center;background:#0405089e;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);padding:24px}.dialog{display:flex;flex-direction:column;gap:14px;width:100%;max-width:500px;max-height:82vh;overflow-y:auto;background:var(--bg-elev2);border:1px solid var(--border);border-radius:12px;box-shadow:0 24px 64px #0000008c;padding:18px 20px}.dialog-title{font-size:14px;font-weight:700;color:var(--text);white-space:pre-wrap;word-break:break-word}.dialog-options{display:flex;flex-direction:column;gap:8px}.dialog-option{display:block;width:100%;text-align:left;border:1px solid var(--border);border-radius:8px;background:var(--bg-elev);color:var(--text);padding:9px 12px;font-size:13px;line-height:1.5;cursor:pointer;white-space:pre-wrap;word-break:break-word;transition:border-color .12s,background .12s}.dialog-option:hover,.dialog-option.sel{border-color:var(--accent);background:var(--accent-soft)}.dialog-hint{color:var(--text-faint);font-size:13px}.dialog-body{font-size:13px;color:var(--text-dim);white-space:pre-wrap;word-break:break-word}.dialog-input{width:100%;border:1px solid var(--border);border-radius:8px;background:var(--bg);color:var(--text);font-family:var(--sans);font-size:14px;padding:9px 12px;outline:none}.dialog-input:focus{border-color:var(--accent)}.dialog-actions{display:flex;justify-content:flex-end;gap:8px}.btn.primary{background:var(--accent);border-color:var(--accent);color:#fff}.btn.primary:hover:not(:disabled){filter:brightness(1.12)}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:300;display:flex;align-items:center;justify-content:center;background:#0405089e;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);padding:24px}.modal{position:relative;display:flex;flex-direction:column;gap:14px;width:100%;max-width:460px;max-height:84vh;overflow-y:auto;background:var(--bg-elev2);border:1px solid var(--border);border-radius:12px;box-shadow:0 24px 64px #0000008c;padding:20px 22px}.model-modal{max-width:640px}.modal-close{position:absolute;top:12px;right:12px;display:flex;align-items:center;justify-content:center;width:28px;height:28px;border:none;border-radius:6px;background:transparent;color:var(--text-dim);cursor:pointer}.modal-close:hover{color:var(--text);background:var(--bg-elev)}.modal-head{display:flex;align-items:center;gap:10px}.modal-head-icon{color:var(--accent);font-size:18px}.modal-head h2{font-size:16px;margin:0}.modal-desc{margin:0;color:var(--text-dim);font-size:13px;line-height:1.6}.modal-desc code{background:var(--bg-elev);border:1px solid var(--border-soft);border-radius:4px;padding:1px 5px;font-size:12px}.modal-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:4px}.setup-actions{display:flex;gap:10px;margin-top:6px}.setup-key-form{display:flex;flex-direction:column;gap:12px}.setup-done{color:var(--text);font-size:13px;line-height:1.6}.setup-failed{display:flex;flex-direction:column;gap:10px}.setup-detail{margin:0;max-height:140px;overflow-y:auto;white-space:pre-wrap;word-break:break-all;background:var(--bg-elev);border:1px solid var(--border-soft);border-radius:8px;padding:10px 12px;font-size:12px;color:#fca5a5}.field{display:flex;flex-direction:column;gap:5px}.field-label{font-size:12px;color:var(--text-dim)}.field-label em{font-style:normal;opacity:.7}.field input,.field select{background:var(--bg-elev);border:1px solid var(--border);border-radius:8px;color:var(--text);padding:8px 10px;font-size:13px;outline:none}.field input:focus,.field select:focus{border-color:var(--accent)}.field.check{flex-direction:row;align-items:center;gap:8px;font-size:13px;color:var(--text)}.field-hint{font-size:12px;color:var(--text-dim)}.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}.form-section-title{font-size:12px;font-weight:700;color:var(--text-dim);text-transform:uppercase;letter-spacing:.06em;margin-top:4px}.section-hint{font-weight:400;text-transform:none;letter-spacing:0;opacity:.75}.auth-badge{display:inline-block;margin-left:6px;padding:1px 7px;border-radius:99px;font-size:11px;color:#6ee7a0;background:#34d3991f;border:1px solid rgba(52,211,153,.25);vertical-align:1px}.auth-badge.dim{color:var(--text-dim);background:var(--bg-elev);border-color:var(--border)}.key-input{background:var(--bg-elev);border:1px solid var(--border);border-radius:6px;color:var(--text);padding:6px 9px;font-size:12.5px;outline:none;width:190px}.key-input:focus{border-color:var(--accent)}.btn.sm{padding:6px 10px;font-size:12px}.provider-list{display:flex;flex-direction:column;gap:8px}.provider-row{display:flex;align-items:center;justify-content:space-between;gap:10px;background:var(--bg-elev);border:1px solid var(--border-soft);border-radius:8px;padding:10px 12px}.provider-info{display:flex;flex-direction:column;gap:2px;min-width:0}.provider-name{font-size:13.5px;font-weight:600;color:var(--text)}.provider-sub{font-size:12px;color:var(--text-dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.provider-actions{display:flex;gap:6px;flex-shrink:0}.iconbtn{display:flex;align-items:center;justify-content:center;width:28px;height:28px;border:1px solid var(--border);border-radius:6px;background:transparent;color:var(--text-dim);cursor:pointer}.iconbtn:hover{color:var(--text);background:var(--bg-elev)}.iconbtn.danger{color:#fca5a5}.iconbtn.danger:hover{background:var(--red-soft)}.model-row{display:grid;grid-template-columns:1.4fr 1fr 110px auto 88px 88px 28px;gap:8px;align-items:center;margin-bottom:8px}.model-row input,.model-row select{background:var(--bg-elev);border:1px solid var(--border);border-radius:6px;color:var(--text);padding:6px 8px;font-size:12.5px;outline:none;min-width:0}.model-row input:focus,.model-row select:focus{border-color:var(--accent)}.model-row .check{display:flex;align-items:center;gap:4px;font-size:12px;color:var(--text-dim);white-space:nowrap}@media(max-width:640px){.form-grid,.model-row{grid-template-columns:1fr}}.sound-menu{display:flex;flex-direction:column;gap:2px;width:300px}.sound-row{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:8px 10px;border-radius:8px;cursor:pointer;transition:background .12s}.sound-row:hover{background:var(--bg-elev)}.sound-row.disabled{opacity:.45;pointer-events:none}.sound-row input[type=checkbox]{width:15px;height:15px;accent-color:var(--accent);cursor:pointer;flex-shrink:0}.sound-label{display:flex;align-items:center;gap:8px;min-width:0}.sound-master .sound-name{font-weight:700}.sound-icon{color:var(--accent);flex-shrink:0}.sound-name{font-size:13px;color:var(--text)}.sound-desc{font-size:11px;color:var(--text-faint);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sound-right{display:flex;align-items:center;gap:8px;flex-shrink:0}.sound-preview{border:1px solid var(--border);border-radius:6px;background:var(--bg-elev);color:var(--text-dim);font-size:11px;padding:2px 8px;cursor:pointer;transition:border-color .12s,color .12s}.sound-preview:hover:not(:disabled){border-color:var(--accent);color:var(--text)}.sound-preview:disabled{opacity:.4;cursor:default}.sound-volume{display:flex;align-items:center;gap:10px;padding:8px 10px}.sound-volume.disabled{opacity:.45;pointer-events:none}.sound-volume input[type=range]{flex:1;accent-color:var(--accent);cursor:pointer}.sound-vol-num{font-size:11px;color:var(--text-faint);min-width:34px;text-align:right}.fp-overlay{position:fixed;top:0;right:0;bottom:0;left:0;z-index:250;display:flex;align-items:center;justify-content:center;background:#0405089e;-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);padding:28px}.fp{display:flex;flex-direction:column;width:100%;max-width:860px;height:min(78vh,720px);background:var(--bg-elev2);border:1px solid var(--border);border-radius:12px;box-shadow:0 24px 64px #0000008c;overflow:hidden}.fp-head{display:flex;align-items:center;gap:10px;padding:10px 14px;border-bottom:1px solid var(--border-soft);flex-shrink:0;min-width:0}.fp-name{font-family:var(--mono);font-size:13px;font-weight:700;color:var(--text);white-space:nowrap}.fp-path{font-family:var(--mono);font-size:11px;color:var(--text-faint);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}.fp-meta{font-size:11px;color:var(--text-faint);flex-shrink:0;white-space:nowrap}.fp-head-actions{display:flex;align-items:center;gap:6px;flex-shrink:0}.fp-attach{position:relative;display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border:1px solid var(--border);border-radius:6px;background:var(--bg-elev);color:var(--text-dim);cursor:pointer}.fp-attach.inline:hover{background:var(--accent);border-color:var(--accent);color:#fff}.fp-attach.ref:hover{background:var(--amber);border-color:var(--amber);color:#1a1a1a}.fp-attach:after{content:attr(data-tip);position:absolute;top:calc(100% + 8px);right:0;background:#232733;color:var(--text);border:1px solid var(--border);border-radius:6px;padding:4px 9px;font-size:11px;line-height:1.4;white-space:nowrap;pointer-events:none;opacity:0;transition:opacity .09s ease;z-index:40;box-shadow:0 4px 14px #00000073}.fp-attach:hover:after{opacity:1}.fp-close{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border:1px solid var(--border);border-radius:6px;background:transparent;color:var(--text-dim);cursor:pointer;margin-left:2px}.fp-close:hover{color:var(--red);border-color:var(--red)}.fp-notice{padding:7px 14px;font-size:11.5px;color:var(--amber);background:#fbbf2414;border-bottom:1px solid var(--border-soft);flex-shrink:0}.fp-empty{flex:1;display:flex;align-items:center;justify-content:center;color:var(--text-faint);font-size:13px;padding:40px}.fp-hex-wrap{flex:1;display:flex;flex-direction:column;min-height:0}.fp-hex{flex:1;overflow:auto;margin:0;padding:12px 14px;background:#00000038;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;line-height:1.55;color:var(--text-dim);white-space:pre;-webkit-user-select:text;user-select:text}.fp-code{flex:1;overflow:auto;background:#00000038;font-family:var(--mono);font-size:12px;line-height:1.6;padding:6px 0;user-select:none;-webkit-user-select:none}.fp-media-wrap{flex:1;min-height:0;overflow:auto;background:#00000038;display:flex;align-items:center;justify-content:center;padding:12px}.fp-media{max-width:100%;max-height:100%;border-radius:4px}.fp-line{display:flex;gap:0;padding-right:16px;cursor:pointer}.fp-line:hover{background:#8b5cf614}.fp-line.sel{background:#8b5cf638}.fp-code.dragging .fp-line.sel{background:#8b5cf64d}.fp-num{flex-shrink:0;width:52px;padding-right:12px;text-align:right;color:var(--text-faint);background:transparent;border-right:1px solid var(--border-soft);margin-right:14px;user-select:none;-webkit-user-select:none}.fp-code-text{flex:1;min-width:0;white-space:pre;color:var(--text-dim)}.fp-line.sel .fp-code-text{color:var(--text)}.fp-lines-note{padding:8px 16px 6px;font-size:11px;color:var(--text-faint)}.fp-foot{display:flex;align-items:center;gap:12px;padding:10px 14px;border-top:1px solid var(--border-soft);flex-shrink:0;background:var(--bg-elev)}.fp-hint{flex:1;min-width:0;font-size:12px;color:var(--text-faint);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.fp-actions{display:flex;align-items:center;gap:8px;flex-shrink:0}.fp-actions .btn{display:inline-flex;align-items:center;gap:5px}pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
33
+ Theme: GitHub Dark
34
+ Description: Dark theme as seen on github.com
35
+ Author: github.com
36
+ Maintainer: @Hirse
37
+ Updated: 2021-05-15
38
+
39
+ Outdated base version: https://github.com/primer/github-syntax-dark
40
+ Current colors taken from GitHub's CSS
41
+ */.hljs{color:#c9d1d9;background:#0d1117}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#ff7b72}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#d2a8ff}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-variable,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id{color:#79c0ff}.hljs-regexp,.hljs-string,.hljs-meta .hljs-string{color:#a5d6ff}.hljs-built_in,.hljs-symbol{color:#ffa657}.hljs-comment,.hljs-code,.hljs-formula{color:#8b949e}.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo{color:#7ee787}.hljs-subst{color:#c9d1d9}.hljs-section{color:#1f6feb;font-weight:700}.hljs-bullet{color:#f2cc60}.hljs-emphasis{color:#c9d1d9;font-style:italic}.hljs-strong{color:#c9d1d9;font-weight:700}.hljs-addition{color:#aff5b4;background-color:#033a16}.hljs-deletion{color:#ffdcd7;background-color:#67060c}