@2lab.ai/ui 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -2
- package/dist/agent-inbox.js +1061 -0
- package/dist/agent-inbox.js.map +1 -0
- package/dist/agent-queue.js +1610 -0
- package/dist/agent-queue.js.map +1 -0
- package/dist/agent-run.js +859 -0
- package/dist/agent-run.js.map +1 -0
- package/dist/ai-answer.js +687 -0
- package/dist/ai-answer.js.map +1 -0
- package/dist/components/agent-inbox/agent-inbox.d.ts +158 -0
- package/dist/components/agent-inbox/index.d.ts +2 -0
- package/dist/components/agent-inbox/styles.d.ts +6 -0
- package/dist/components/agent-queue/agent-queue.d.ts +41 -0
- package/dist/components/agent-queue/index.d.ts +6 -0
- package/dist/components/agent-queue/logic.d.ts +261 -0
- package/dist/components/agent-queue/strings.d.ts +473 -0
- package/dist/components/agent-queue/styles.d.ts +8 -0
- package/dist/components/agent-run/agent-run.d.ts +273 -0
- package/dist/components/agent-run/index.d.ts +2 -0
- package/dist/components/agent-run/styles.d.ts +6 -0
- package/dist/components/ai-answer/ai-answer.d.ts +92 -0
- package/dist/components/ai-answer/index.d.ts +2 -0
- package/dist/components/ai-answer/styles.d.ts +2 -0
- package/dist/components/context-window/context-window.d.ts +156 -0
- package/dist/components/context-window/index.d.ts +2 -0
- package/dist/components/context-window/styles.d.ts +2 -0
- package/dist/components/memory-panel/index.d.ts +2 -0
- package/dist/components/memory-panel/memory-panel.d.ts +101 -0
- package/dist/components/memory-panel/styles.d.ts +2 -0
- package/dist/components/model-picker/index.d.ts +2 -0
- package/dist/components/model-picker/model-picker.d.ts +136 -0
- package/dist/components/model-picker/morph.d.ts +9 -0
- package/dist/components/model-picker/styles.d.ts +6 -0
- package/dist/components/tool-call-card/index.d.ts +7 -0
- package/dist/components/tool-call-card/logic.d.ts +87 -0
- package/dist/components/tool-call-card/strings.d.ts +94 -0
- package/dist/components/tool-call-card/styles.d.ts +5 -0
- package/dist/components/tool-call-card/tool-call-card.d.ts +49 -0
- package/dist/components/tool-call-card/tool-call-group.d.ts +32 -0
- package/dist/context-window.js +384 -0
- package/dist/context-window.js.map +1 -0
- package/dist/core/element.d.ts +41 -0
- package/dist/core/format.d.ts +14 -0
- package/dist/core/html.d.ts +10 -0
- package/dist/core/tokens.d.ts +14 -0
- package/dist/format-BBGWXPzk.js +51 -0
- package/dist/format-BBGWXPzk.js.map +1 -0
- package/dist/html-elD6Bb_u.js +175 -0
- package/dist/html-elD6Bb_u.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +129 -0
- package/dist/index.js.map +1 -0
- package/dist/memory-panel.js +1255 -0
- package/dist/memory-panel.js.map +1 -0
- package/dist/model-picker.js +858 -0
- package/dist/model-picker.js.map +1 -0
- package/dist/tokens.css +4 -0
- package/dist/tool-call-card.js +1046 -0
- package/dist/tool-call-card.js.map +1 -0
- package/package.json +79 -4
|
@@ -0,0 +1,1046 @@
|
|
|
1
|
+
import { e as esc, d as define, U as UiElement, f as escAttr } from "./html-elD6Bb_u.js";
|
|
2
|
+
function formatToolMs(ms, live = false) {
|
|
3
|
+
const v = Math.max(0, Number(ms) || 0);
|
|
4
|
+
if (!live && v < 1e3) return `${Math.max(1, Math.round(v))}ms`;
|
|
5
|
+
if (v < 6e4) return `${(Math.floor(v / 100) / 10).toFixed(1)}s`;
|
|
6
|
+
const s = Math.floor(v / 1e3);
|
|
7
|
+
const m = Math.floor(s / 60);
|
|
8
|
+
if (m < 60) return `${m}m ${String(s % 60).padStart(2, "0")}s`;
|
|
9
|
+
return `${Math.floor(m / 60)}h ${String(m % 60).padStart(2, "0")}m`;
|
|
10
|
+
}
|
|
11
|
+
function formatToolClock(at, lang = "en") {
|
|
12
|
+
const v = Number(at);
|
|
13
|
+
if (at === void 0 || at === null || !Number.isFinite(v) || Number.isNaN(new Date(v).getTime())) return "";
|
|
14
|
+
try {
|
|
15
|
+
return new Date(v).toLocaleTimeString(lang === "ko" ? "ko-KR" : "en-GB", {
|
|
16
|
+
hour: "2-digit",
|
|
17
|
+
minute: "2-digit",
|
|
18
|
+
second: "2-digit",
|
|
19
|
+
hourCycle: "h23"
|
|
20
|
+
});
|
|
21
|
+
} catch {
|
|
22
|
+
return "";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function toCodeText(value) {
|
|
26
|
+
if (typeof value === "string") {
|
|
27
|
+
const head = value.trimStart().charAt(0);
|
|
28
|
+
if (head === "{" || head === "[") {
|
|
29
|
+
try {
|
|
30
|
+
return { text: JSON.stringify(JSON.parse(value), null, 2), json: true };
|
|
31
|
+
} catch {
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return { text: value, json: false };
|
|
35
|
+
}
|
|
36
|
+
if (value === void 0) return { text: "", json: false };
|
|
37
|
+
try {
|
|
38
|
+
const s = JSON.stringify(value, null, 2);
|
|
39
|
+
return s === void 0 ? { text: String(value), json: false } : { text: s, json: true };
|
|
40
|
+
} catch {
|
|
41
|
+
return { text: String(value), json: false };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const JSON_TOKEN = /("(?:\\.|[^"\\])*")(\s*:)?|\b(?:true|false|null)\b|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/g;
|
|
45
|
+
function highlightJson(text) {
|
|
46
|
+
return esc(text).replace(JSON_TOKEN, (m, str, colon) => {
|
|
47
|
+
if (str) return colon ? `<span class="tc-k">${str}</span>${colon}` : `<span class="tc-s">${str}</span>`;
|
|
48
|
+
return `<span class="tc-${m.charCodeAt(0) >= 97 ? "l" : "n"}">${m}</span>`;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
const ICON_BY_NAME = {
|
|
52
|
+
Read: "file",
|
|
53
|
+
Write: "file",
|
|
54
|
+
Edit: "edit",
|
|
55
|
+
MultiEdit: "edit",
|
|
56
|
+
NotebookEdit: "edit",
|
|
57
|
+
Bash: "terminal",
|
|
58
|
+
BashOutput: "terminal",
|
|
59
|
+
Grep: "search",
|
|
60
|
+
Glob: "search",
|
|
61
|
+
WebSearch: "search",
|
|
62
|
+
WebFetch: "globe",
|
|
63
|
+
Agent: "agent",
|
|
64
|
+
Task: "agent",
|
|
65
|
+
TodoWrite: "list"
|
|
66
|
+
};
|
|
67
|
+
function toolIcon(name) {
|
|
68
|
+
const n = String(name ?? "");
|
|
69
|
+
if (Object.hasOwn(ICON_BY_NAME, n)) return ICON_BY_NAME[n];
|
|
70
|
+
return n.startsWith("mcp__") ? "plug" : "wrench";
|
|
71
|
+
}
|
|
72
|
+
function lineCount(text) {
|
|
73
|
+
let n = 1;
|
|
74
|
+
for (let i = text.indexOf("\n"); i !== -1; i = text.indexOf("\n", i + 1)) n++;
|
|
75
|
+
return n;
|
|
76
|
+
}
|
|
77
|
+
function groupStats(calls) {
|
|
78
|
+
let running = false;
|
|
79
|
+
let failed = 0;
|
|
80
|
+
let sum = 0;
|
|
81
|
+
let all = calls.length > 0;
|
|
82
|
+
for (const c of calls) {
|
|
83
|
+
if (c.status === "running") running = true;
|
|
84
|
+
else if (c.status === "error") failed++;
|
|
85
|
+
const d = Number(c.duration);
|
|
86
|
+
if (c.duration === void 0 || c.duration === null || !Number.isFinite(d)) all = false;
|
|
87
|
+
else sum += Math.max(0, d);
|
|
88
|
+
}
|
|
89
|
+
return { running, failed, sum: all ? sum : void 0 };
|
|
90
|
+
}
|
|
91
|
+
const G = (d) => `<svg viewBox="0 0 16 16" aria-hidden="true">${d}</svg>`;
|
|
92
|
+
const TOOL_ICONS = {
|
|
93
|
+
wrench: G(
|
|
94
|
+
'<path d="M13.4 4.6 11.6 6.4 9.9 6.1 9.6 4.4l1.8-1.8a3.2 3.2 0 0 0-4.3 4L2.9 10.8a1.6 1.6 0 0 0 2.3 2.3L9.4 8.9a3.2 3.2 0 0 0 4-4.3Z"/>'
|
|
95
|
+
),
|
|
96
|
+
search: G('<circle cx="7" cy="7" r="4.25"/><path d="m10.2 10.2 3.3 3.3"/>'),
|
|
97
|
+
file: G(
|
|
98
|
+
'<path d="M9 1.9H4.6a1 1 0 0 0-1 1v10.2a1 1 0 0 0 1 1h6.8a1 1 0 0 0 1-1V5.3Z"/><path d="M9 1.9v3.4h3.4M5.9 8.6h4.2M5.9 11h2.8"/>'
|
|
99
|
+
),
|
|
100
|
+
edit: G(
|
|
101
|
+
'<path d="M10.3 2.9a1.5 1.5 0 0 1 2.1 0l.7.7a1.5 1.5 0 0 1 0 2.1L6.4 12.4l-3.3.8.8-3.3Z"/><path d="m9.2 4 2.8 2.8"/>'
|
|
102
|
+
),
|
|
103
|
+
terminal: G('<rect x="1.9" y="2.9" width="12.2" height="10.2" rx="2"/><path d="m4.6 6.3 2 1.8-2 1.8M8.4 10.1h3"/>'),
|
|
104
|
+
globe: G(
|
|
105
|
+
'<circle cx="8" cy="8" r="6.1"/><path d="M1.9 8h12.2M8 1.9c-1.7 1.7-2.5 3.7-2.5 6.1s.8 4.4 2.5 6.1c1.7-1.7 2.5-3.7 2.5-6.1S9.7 3.6 8 1.9Z"/>'
|
|
106
|
+
),
|
|
107
|
+
agent: G(
|
|
108
|
+
'<rect x="2.9" y="5.1" width="10.2" height="8" rx="2.4"/><path d="M8 2.4v2.7M6.1 8.6v1M9.9 8.6v1M1.4 9.1v1.4M14.6 9.1v1.4"/>'
|
|
109
|
+
),
|
|
110
|
+
list: G('<path d="m2.4 4.1.9.9 1.6-1.9M2.4 8.6l.9.9 1.6-1.9M2.6 12.6h1.9M7.2 4.2h6.4M7.2 8.2h6.4M7.2 12.6h6.4"/>'),
|
|
111
|
+
plug: G('<path d="M5.9 1.9v2.9M10.1 1.9v2.9M4.1 4.8h7.8v2.3a3.9 3.9 0 0 1-7.8 0ZM8 11v3.1"/>')
|
|
112
|
+
};
|
|
113
|
+
const en = {
|
|
114
|
+
running: "Running",
|
|
115
|
+
done: "Finished",
|
|
116
|
+
failed: "Failed",
|
|
117
|
+
noResult: "No result",
|
|
118
|
+
args: "Arguments",
|
|
119
|
+
result: "Result",
|
|
120
|
+
waiting: "Waiting for result…",
|
|
121
|
+
nothing: "No arguments or result.",
|
|
122
|
+
failedDefault: "The tool call failed.",
|
|
123
|
+
interrupted: "The run ended before this call returned.",
|
|
124
|
+
showAll: "Show all",
|
|
125
|
+
showLess: "Show less",
|
|
126
|
+
retry: "Retry",
|
|
127
|
+
reload: "Try again",
|
|
128
|
+
loading: "Loading…",
|
|
129
|
+
copy: (part) => `Copy ${part.toLowerCase()}`,
|
|
130
|
+
copied: "Copied",
|
|
131
|
+
selected: "Selected — press ⌘C / Ctrl+C to copy",
|
|
132
|
+
startedAt: (clock) => `Started at ${clock}`,
|
|
133
|
+
sayRunning: (name) => `${name} running`,
|
|
134
|
+
sayDone: (name) => `${name} finished`,
|
|
135
|
+
sayFailed: (name, error) => `${name} failed${error ? `: ${error}` : ""}`,
|
|
136
|
+
sayInterrupted: (name) => `${name} ended without a result`,
|
|
137
|
+
used: (n) => `Used ${n} ${n === 1 ? "tool" : "tools"}`,
|
|
138
|
+
using: "Using tools…",
|
|
139
|
+
nFailed: (n) => `${n} failed`,
|
|
140
|
+
groupSay: (label, failed) => `${label}${failed ? `, ${failed} failed` : ""}`
|
|
141
|
+
};
|
|
142
|
+
const ko = {
|
|
143
|
+
running: "실행 중",
|
|
144
|
+
done: "완료",
|
|
145
|
+
failed: "실패",
|
|
146
|
+
noResult: "결과 없음",
|
|
147
|
+
args: "입력",
|
|
148
|
+
result: "결과",
|
|
149
|
+
waiting: "결과 기다리는 중…",
|
|
150
|
+
nothing: "입력과 결과 없음",
|
|
151
|
+
failedDefault: "툴 호출이 실패했어요",
|
|
152
|
+
interrupted: "이 호출이 끝나기 전에 실행이 멈췄어요",
|
|
153
|
+
showAll: "모두 보기",
|
|
154
|
+
showLess: "접기",
|
|
155
|
+
retry: "다시 시도",
|
|
156
|
+
reload: "다시 시도",
|
|
157
|
+
loading: "불러오는 중…",
|
|
158
|
+
copy: (part) => `${part} 복사`,
|
|
159
|
+
copied: "복사됨",
|
|
160
|
+
selected: "선택했습니다 — ⌘C / Ctrl+C로 복사",
|
|
161
|
+
startedAt: (clock) => `${clock} 시작`,
|
|
162
|
+
sayRunning: (name) => `${name} 실행 중`,
|
|
163
|
+
sayDone: (name) => `${name} 완료`,
|
|
164
|
+
sayFailed: (name, error) => `${name} 실패${error ? `: ${error}` : ""}`,
|
|
165
|
+
sayInterrupted: (name) => `${name} 결과 없이 끝남`,
|
|
166
|
+
used: (n) => `툴 ${n}개 사용`,
|
|
167
|
+
using: "툴 사용 중…",
|
|
168
|
+
nFailed: (n) => `${n}개 실패`,
|
|
169
|
+
groupSay: (label, failed) => `${label}${failed ? `, ${failed}개 실패` : ""}`
|
|
170
|
+
};
|
|
171
|
+
const STRINGS = { en, ko };
|
|
172
|
+
function toolCallStrings(lang) {
|
|
173
|
+
return lang === "ko" ? STRINGS.ko : STRINGS.en;
|
|
174
|
+
}
|
|
175
|
+
const styles = `
|
|
176
|
+
:host { --tc-ink: var(--text); --tc-text: var(--text-secondary); --tc-muted: var(--text-tertiary);
|
|
177
|
+
--tc-bg: var(--surface); --tc-line: var(--border); --tc-tile: var(--surface-raised);
|
|
178
|
+
--tc-hover: color-mix(in srgb, var(--text) 3.5%, var(--surface));
|
|
179
|
+
--tc-code: color-mix(in srgb, var(--surface-raised) 60%, var(--surface));
|
|
180
|
+
--tc-ring: var(--surface);
|
|
181
|
+
--tc-red: var(--st-fail-text); --tc-red-bg: color-mix(in srgb, var(--st-fail) 7%, var(--surface));
|
|
182
|
+
--tc-red-line: color-mix(in srgb, var(--st-fail) 24%, transparent);
|
|
183
|
+
--tc-key: var(--text); --tc-str: color-mix(in srgb, var(--green) 62%, var(--accent)); --tc-num: var(--orange);
|
|
184
|
+
--tc-lit: var(--purple); --tc-shine: var(--text);
|
|
185
|
+
--tc-lift: 0 1px 2px color-mix(in srgb, var(--text) 6%, transparent);
|
|
186
|
+
--tc-ease: var(--ease-out);
|
|
187
|
+
min-width: 0; font-size: 13.5px; line-height: 1.4; letter-spacing: -0.006em; }
|
|
188
|
+
[hidden] { display: none !important; }
|
|
189
|
+
button { appearance: none; margin: 0; border: 0; background: none; text-align: left; letter-spacing: inherit; }
|
|
190
|
+
|
|
191
|
+
/* Card */
|
|
192
|
+
.tc { position: relative; border: 1px solid var(--tc-line); border-radius: 12px; background: var(--tc-bg); box-shadow: var(--tc-lift); }
|
|
193
|
+
.tc-head { display: flex; align-items: center; gap: 10px; width: 100%; min-height: 44px; padding: 8px 10px 8px 9px;
|
|
194
|
+
border-radius: 11px; color: var(--tc-text); transition: background-color 160ms ease; }
|
|
195
|
+
.tc[data-open] > .tc-head { border-radius: 11px 11px 0 0; }
|
|
196
|
+
.tc-icon { display: grid; place-items: center; flex: none; width: 26px; height: 26px; border-radius: 7px;
|
|
197
|
+
background: var(--tc-tile); box-shadow: inset 0 0 0 1px var(--tc-line); color: var(--tc-ink); }
|
|
198
|
+
.tc-icon > svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; }
|
|
199
|
+
.tc-main { display: flex; align-items: baseline; gap: 8px; flex: 1; min-width: 0; }
|
|
200
|
+
.tc-name { flex: 0 1 auto; max-width: 55%; overflow: hidden; color: var(--tc-ink); font-family: var(--mono); font-size: 12.5px;
|
|
201
|
+
font-weight: 500; letter-spacing: 0; text-overflow: ellipsis; white-space: nowrap; }
|
|
202
|
+
.tc-summary { flex: 1 1 0; min-width: 0; overflow: hidden; color: var(--tc-text); text-overflow: ellipsis; white-space: nowrap; }
|
|
203
|
+
.tc-status { display: inline-flex; align-items: center; gap: 6px; flex: none; color: var(--tc-muted); font-size: 12.5px;
|
|
204
|
+
font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
205
|
+
.tc-status.is-in { animation: tc-in 360ms var(--tc-ease) both; }
|
|
206
|
+
.tc[data-status="error"] .tc-status { color: var(--tc-red); }
|
|
207
|
+
.tc-status > svg { width: 14px; height: 14px; flex: none; fill: none; stroke-linecap: round; stroke-linejoin: round; }
|
|
208
|
+
.tc-spin { animation: tc-spin 800ms linear infinite; }
|
|
209
|
+
.tc-spin circle { stroke: currentColor; stroke-width: 1.6; opacity: 0.25; }
|
|
210
|
+
.tc-spin path { stroke: var(--st-run); stroke-width: 1.6; }
|
|
211
|
+
.tc-ok { stroke: var(--st-done); stroke-width: 1.8; }
|
|
212
|
+
.is-in .tc-ok path { stroke-dasharray: 1; stroke-dashoffset: 1; animation: tc-draw 360ms var(--tc-ease) 80ms forwards; }
|
|
213
|
+
.tc-err { stroke: var(--st-fail); stroke-width: 1.5; }
|
|
214
|
+
.tc-stop { stroke: currentColor; stroke-width: 1.5; }
|
|
215
|
+
.tc-chev { flex: none; width: 14px; height: 14px; fill: none; stroke: var(--tc-muted); stroke-width: 1.5; stroke-linecap: round;
|
|
216
|
+
stroke-linejoin: round; transition: transform 360ms var(--tc-ease); }
|
|
217
|
+
.tc[data-open] > .tc-head .tc-chev, .tcg[data-open] > .tcg-head .tc-chev { transform: rotate(180deg); }
|
|
218
|
+
@media (hover: hover) {
|
|
219
|
+
.tc-head:hover { background: var(--tc-hover); }
|
|
220
|
+
.tc-head:hover .tc-chev { stroke: var(--tc-ink); }
|
|
221
|
+
.tcg-head:hover { background: var(--tc-hover); color: var(--tc-ink); }
|
|
222
|
+
.tc-copy:hover { background: var(--tc-hover); color: var(--tc-ink); }
|
|
223
|
+
.tc-more:hover { color: var(--tc-ink); }
|
|
224
|
+
.tc-btn:hover { background: var(--tc-hover); }
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/* Body: 0fr → 1fr, so the height animates without measuring */
|
|
228
|
+
.tc-body { display: grid; grid-template-rows: 0fr; opacity: 0;
|
|
229
|
+
transition: grid-template-rows 380ms var(--tc-ease), opacity 240ms ease; }
|
|
230
|
+
.tc-body[data-open] { grid-template-rows: 1fr; opacity: 1; }
|
|
231
|
+
.tc-clip { min-height: 0; overflow: hidden; }
|
|
232
|
+
.tc-sections { display: grid; gap: 12px; padding: 12px; border-top: 1px solid var(--tc-line); }
|
|
233
|
+
.tc-top { display: flex; justify-content: flex-end; margin: -2px 0 -6px; }
|
|
234
|
+
.tc-line { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
|
|
235
|
+
.tc-line > p { min-width: 0; }
|
|
236
|
+
.tc-head-end { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
|
|
237
|
+
.tc-at { flex: none; color: var(--tc-muted); font-size: 11.5px; line-height: 16px; font-variant-numeric: tabular-nums; }
|
|
238
|
+
.tc-section { display: grid; gap: 6px; min-width: 0; }
|
|
239
|
+
.tc-section-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 24px; }
|
|
240
|
+
.tc-label { color: var(--tc-muted); font-size: 12px; font-weight: 500; }
|
|
241
|
+
.tc-pending, .tc-note { margin: 0; color: var(--tc-muted); font-size: 12.5px; }
|
|
242
|
+
.tc-note { font-size: 12px; }
|
|
243
|
+
|
|
244
|
+
.tc-copy { position: relative; display: grid; place-items: center; flex: none; width: 24px; height: 24px; padding: 0;
|
|
245
|
+
border-radius: 6px; color: var(--tc-muted); transition: background-color 160ms ease, color 160ms ease; }
|
|
246
|
+
.tc-copy svg { position: absolute; width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.4;
|
|
247
|
+
stroke-linecap: round; stroke-linejoin: round;
|
|
248
|
+
transition: opacity 200ms ease, transform 320ms var(--tc-ease), filter 200ms ease; }
|
|
249
|
+
.tc-copy-done { opacity: 0; transform: scale(0.6); filter: blur(2px); stroke-width: 1.7; }
|
|
250
|
+
.tc-copy[data-copied] { color: var(--st-done); }
|
|
251
|
+
.tc-copy[data-copied] .tc-copy-icon { opacity: 0; transform: scale(0.6); filter: blur(2px); }
|
|
252
|
+
.tc-copy[data-copied] .tc-copy-done { opacity: 1; transform: none; filter: none; }
|
|
253
|
+
|
|
254
|
+
/* Code wraps instead of scrolling sideways; a capped block fades out at the bottom */
|
|
255
|
+
.tc-code { position: relative; margin: 0; padding: 10px 12px; overflow: hidden; border-radius: 8px; background: var(--tc-code);
|
|
256
|
+
box-shadow: inset 0 0 0 1px var(--tc-line); color: var(--tc-text); font-family: var(--mono); font-size: 12px;
|
|
257
|
+
line-height: 1.6; letter-spacing: 0; white-space: pre-wrap; overflow-wrap: anywhere; tab-size: 2;
|
|
258
|
+
transition: max-height 420ms var(--tc-ease); }
|
|
259
|
+
.tc-code[data-json] { color: var(--tc-muted); }
|
|
260
|
+
.tc-code code { display: block; font: inherit; }
|
|
261
|
+
.tc-code::after { content: ""; position: absolute; left: 1px; right: 1px; bottom: 1px; height: 56px; border-radius: 0 0 7px 7px;
|
|
262
|
+
background: linear-gradient(to bottom, color-mix(in srgb, var(--tc-code) 0%, transparent), var(--tc-code));
|
|
263
|
+
opacity: 0; transition: opacity 240ms ease; pointer-events: none; }
|
|
264
|
+
.tc-code[data-capped]::after { opacity: 1; }
|
|
265
|
+
.tc-k { color: var(--tc-key); }
|
|
266
|
+
.tc-s { color: var(--tc-str); }
|
|
267
|
+
.tc-n { color: var(--tc-num); }
|
|
268
|
+
.tc-l { color: var(--tc-lit); }
|
|
269
|
+
.tc-more { position: relative; justify-self: start; padding: 2px 4px; margin-left: -4px; border-radius: 5px; color: var(--tc-text);
|
|
270
|
+
font-size: 12.5px; font-weight: 500; transition: color 160ms ease; }
|
|
271
|
+
|
|
272
|
+
/* Error: the message and an optional Retry; a failed host fetch: the message and Try again */
|
|
273
|
+
.tc-error { display: flex; align-items: flex-start; gap: 8px; padding: 9px 10px 9px 11px; border-radius: 8px;
|
|
274
|
+
background: var(--tc-red-bg); box-shadow: inset 0 0 0 1px var(--tc-red-line); }
|
|
275
|
+
.tc-error > svg { flex: none; width: 14px; height: 14px; margin-top: 2px; fill: none; stroke-linecap: round; }
|
|
276
|
+
.tc-error-text { flex: 1; min-width: 0; margin: 0; color: var(--tc-red); font-size: 13px; line-height: 1.45;
|
|
277
|
+
white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
278
|
+
.tc-reload-row { display: flex; align-items: center; gap: 8px; }
|
|
279
|
+
.tc-reload-row > .tc-pending { flex: 1; min-width: 0; overflow-wrap: anywhere; }
|
|
280
|
+
.tc-btn { position: relative; display: inline-flex; align-items: center; gap: 5px; flex: none; height: 26px; margin: -3px 0;
|
|
281
|
+
padding: 0 10px 0 8px; border-radius: 7px; background: var(--tc-bg); box-shadow: inset 0 0 0 1px var(--tc-line), var(--tc-lift);
|
|
282
|
+
color: var(--tc-ink); font-size: 12.5px; font-weight: 500; white-space: nowrap; transition: background-color 160ms ease; }
|
|
283
|
+
.tc-btn svg { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round;
|
|
284
|
+
stroke-linejoin: round; transition: transform 200ms var(--tc-ease); }
|
|
285
|
+
.tc-btn:active svg { transform: rotate(-60deg); }
|
|
286
|
+
|
|
287
|
+
/* Group: one quiet line with the tools' icons stacked, the cards below */
|
|
288
|
+
.tcg { display: grid; }
|
|
289
|
+
.tcg-head { display: flex; align-items: center; gap: 9px; justify-self: start; max-width: 100%; min-height: 36px;
|
|
290
|
+
padding: 4px 10px 4px 5px; border-radius: 10px; color: var(--tc-text);
|
|
291
|
+
transition: background-color 160ms ease, color 160ms ease; }
|
|
292
|
+
.tcg-stack { display: flex; flex: none; }
|
|
293
|
+
.tcg-stack .tc-icon { width: 24px; height: 24px; box-shadow: inset 0 0 0 1px var(--tc-line), 0 0 0 2px var(--tc-ring); }
|
|
294
|
+
.tcg-stack .tc-icon.is-in { animation: tc-pop 420ms var(--tc-ease) both; }
|
|
295
|
+
.tcg-stack .tc-icon + .tc-icon { margin-left: -5px; }
|
|
296
|
+
.tcg-stack .tc-icon > svg { width: 13px; height: 13px; }
|
|
297
|
+
.tcg-title { display: flex; align-items: baseline; gap: 5px; min-width: 0; white-space: nowrap; }
|
|
298
|
+
.tcg-title.is-in { animation: tc-in 360ms var(--tc-ease) both; }
|
|
299
|
+
.tcg-label { position: relative; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: inherit; font-weight: 500; }
|
|
300
|
+
.tcg-failed { flex: none; color: var(--tc-red); }
|
|
301
|
+
.tcg-time { flex: none; color: var(--tc-muted); font-variant-numeric: tabular-nums; }
|
|
302
|
+
.tcg-list { display: grid; gap: 8px; padding-top: 8px; }
|
|
303
|
+
|
|
304
|
+
/* A band of the text colour sweeping over “Using tools…” */
|
|
305
|
+
.tc-shine::after { content: attr(data-text); content: attr(data-text) / ""; position: absolute; inset: 0; white-space: nowrap;
|
|
306
|
+
color: transparent; background: linear-gradient(90deg, transparent 38%, var(--tc-shine) 50%, transparent 62%) 100% 0 / 300% 100% no-repeat;
|
|
307
|
+
-webkit-background-clip: text; background-clip: text; animation: tc-sweep 1.8s linear infinite; pointer-events: none; }
|
|
308
|
+
|
|
309
|
+
@keyframes tc-in { from { opacity: 0; transform: translateY(3px); filter: blur(2px); } to { opacity: 1; transform: none; filter: none; } }
|
|
310
|
+
@keyframes tc-pop { from { opacity: 0; transform: scale(0.6); } to { opacity: 1; transform: none; } }
|
|
311
|
+
@keyframes tc-spin { to { transform: rotate(360deg); } }
|
|
312
|
+
@keyframes tc-draw { to { stroke-dashoffset: 0; } }
|
|
313
|
+
@keyframes tc-sweep { from { background-position: 100% 0; } to { background-position: 0% 0; } }
|
|
314
|
+
|
|
315
|
+
/* Touch: every control gets a 44px target without changing the layout */
|
|
316
|
+
@media (pointer: coarse) {
|
|
317
|
+
.tcg-head { min-height: 44px; }
|
|
318
|
+
.tc-copy::before, .tc-more::before, .tc-btn::before { content: ""; position: absolute; inset: -10px; }
|
|
319
|
+
.tc-more::before { inset: -12px -8px; }
|
|
320
|
+
.tc-btn::before { inset: -9px -4px; }
|
|
321
|
+
}
|
|
322
|
+
@media (prefers-reduced-motion: reduce) {
|
|
323
|
+
.tc-shine::after { display: none; }
|
|
324
|
+
.tc-spin { animation: none; }
|
|
325
|
+
.is-in .tc-ok path { animation: none; stroke-dashoffset: 0; }
|
|
326
|
+
}
|
|
327
|
+
`;
|
|
328
|
+
const COPIED_MS = 1400;
|
|
329
|
+
const TICK_MS$1 = 100;
|
|
330
|
+
const DEFAULT_CAP = 220;
|
|
331
|
+
const LINE_PX = 19.2;
|
|
332
|
+
const CODE_PAD = 20;
|
|
333
|
+
const SPIN_SVG = '<svg class="tc-spin" viewBox="0 0 16 16" aria-hidden="true"><circle cx="8" cy="8" r="5.8"/><path d="M8 2.2a5.8 5.8 0 0 1 5.8 5.8"/></svg>';
|
|
334
|
+
const OK_SVG = '<svg class="tc-ok" viewBox="0 0 16 16" aria-hidden="true"><path d="m3.6 8.5 3 2.9 5.8-6.4" pathLength="1"/></svg>';
|
|
335
|
+
const ERR_SVG = '<svg class="tc-err" viewBox="0 0 16 16" aria-hidden="true"><circle cx="8" cy="8" r="6.1"/><path d="M8 4.9v3.6M8 11v.1"/></svg>';
|
|
336
|
+
const STOP_SVG = '<svg class="tc-stop" viewBox="0 0 16 16" aria-hidden="true"><circle cx="8" cy="8" r="6.1"/><path d="M5.4 8h5.2"/></svg>';
|
|
337
|
+
const CHEV_SVG = '<svg class="tc-chev" viewBox="0 0 16 16" aria-hidden="true"><path d="m4.4 6.2 3.6 3.6 3.6-3.6"/></svg>';
|
|
338
|
+
const COPY_SVG = '<svg class="tc-copy-icon" viewBox="0 0 16 16" aria-hidden="true"><rect x="5.4" y="5.4" width="8.1" height="8.1" rx="1.9"/><path d="M10.6 5.4V4.3a1.6 1.6 0 0 0-1.6-1.6H4.3a1.6 1.6 0 0 0-1.6 1.6V9a1.6 1.6 0 0 0 1.6 1.6h1.1"/></svg>';
|
|
339
|
+
const DONE_SVG = '<svg class="tc-copy-done" viewBox="0 0 16 16" aria-hidden="true"><path d="m3.6 8.5 3 2.9 5.8-6.4"/></svg>';
|
|
340
|
+
const RETRY_SVG = '<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M2.8 8a5.2 5.2 0 1 0 1.5-3.7"/><path d="M2.6 2.4v2.9h2.9"/></svg>';
|
|
341
|
+
const STATUSES = /* @__PURE__ */ new Set(["running", "success", "error", "interrupted"]);
|
|
342
|
+
function normStatus(s) {
|
|
343
|
+
return STATUSES.has(s) ? s : "interrupted";
|
|
344
|
+
}
|
|
345
|
+
function callIconSvg(d) {
|
|
346
|
+
const key = d.icon && Object.hasOwn(TOOL_ICONS, d.icon) ? d.icon : toolIcon(d.name);
|
|
347
|
+
return TOOL_ICONS[key];
|
|
348
|
+
}
|
|
349
|
+
const finite$1 = (v) => {
|
|
350
|
+
if (v === void 0 || v === null || v === "") return void 0;
|
|
351
|
+
const n = Number(v);
|
|
352
|
+
return Number.isFinite(n) ? n : void 0;
|
|
353
|
+
};
|
|
354
|
+
const HEAD_CHARS_PER_LINE = 240;
|
|
355
|
+
function cutAfterLines(text, n) {
|
|
356
|
+
let i = -1;
|
|
357
|
+
for (let k = 0; k < n; k++) {
|
|
358
|
+
i = text.indexOf("\n", i + 1);
|
|
359
|
+
if (i === -1) return text.length;
|
|
360
|
+
}
|
|
361
|
+
return i;
|
|
362
|
+
}
|
|
363
|
+
class UiToolCallCard extends UiElement {
|
|
364
|
+
static styles = [styles];
|
|
365
|
+
static get observedAttributes() {
|
|
366
|
+
return ["lang"];
|
|
367
|
+
}
|
|
368
|
+
#open = false;
|
|
369
|
+
#auto = false;
|
|
370
|
+
#shownOpen = false;
|
|
371
|
+
#rendered = false;
|
|
372
|
+
#status;
|
|
373
|
+
#statusIn = false;
|
|
374
|
+
#runStart = 0;
|
|
375
|
+
#measured;
|
|
376
|
+
#tick = 0;
|
|
377
|
+
#full = /* @__PURE__ */ new Set();
|
|
378
|
+
#copied = /* @__PURE__ */ new Set();
|
|
379
|
+
#copyTimers = /* @__PURE__ */ new Map();
|
|
380
|
+
#code = /* @__PURE__ */ new Map();
|
|
381
|
+
#ro;
|
|
382
|
+
#saidStatus;
|
|
383
|
+
#live;
|
|
384
|
+
constructor() {
|
|
385
|
+
super();
|
|
386
|
+
this.#live = document.createElement("span");
|
|
387
|
+
this.#live.className = "sr-only";
|
|
388
|
+
this.#live.setAttribute("role", "status");
|
|
389
|
+
this.#live.setAttribute("aria-live", "polite");
|
|
390
|
+
this.root.append(this.#live);
|
|
391
|
+
this.root.addEventListener("click", (e) => this.#click(e));
|
|
392
|
+
}
|
|
393
|
+
attributeChangedCallback() {
|
|
394
|
+
this.requestRender();
|
|
395
|
+
}
|
|
396
|
+
disconnectedCallback() {
|
|
397
|
+
this.#stopTick();
|
|
398
|
+
this.#ro?.disconnect();
|
|
399
|
+
}
|
|
400
|
+
/** Focus the header button (the card's one tab stop). */
|
|
401
|
+
/** Retry, Try again, a Show all that went away, an auto-closed body: focus stays on the card's header. */
|
|
402
|
+
focusFallback() {
|
|
403
|
+
return this.$(".tc-head");
|
|
404
|
+
}
|
|
405
|
+
focus(options) {
|
|
406
|
+
if (!this.$(".tc-head") && this.data && this.isConnected) this.renderNow();
|
|
407
|
+
const head = this.$(".tc-head");
|
|
408
|
+
if (head) head.focus(options);
|
|
409
|
+
else super.focus(options);
|
|
410
|
+
}
|
|
411
|
+
/** Whether the body is open now (controlled `data.open`, else the element's own state). */
|
|
412
|
+
get isOpen() {
|
|
413
|
+
return this.data ? this.#isOpen(this.data) : false;
|
|
414
|
+
}
|
|
415
|
+
get #lang() {
|
|
416
|
+
return this.getAttribute("lang") === "ko" ? "ko" : "en";
|
|
417
|
+
}
|
|
418
|
+
get #t() {
|
|
419
|
+
return toolCallStrings(this.#lang);
|
|
420
|
+
}
|
|
421
|
+
#isOpen(d) {
|
|
422
|
+
return d.open ?? this.#open;
|
|
423
|
+
}
|
|
424
|
+
#startOf(d) {
|
|
425
|
+
return finite$1(d.startedAt) ?? this.#runStart;
|
|
426
|
+
}
|
|
427
|
+
#liveMs(d) {
|
|
428
|
+
return Math.max(0, Date.now() - this.#startOf(d));
|
|
429
|
+
}
|
|
430
|
+
/** Status transitions: first-render defaults, measured time, open-on-error. */
|
|
431
|
+
#sync(d, st) {
|
|
432
|
+
const prev = this.#status;
|
|
433
|
+
this.#statusIn = false;
|
|
434
|
+
if (prev === void 0) {
|
|
435
|
+
this.#open = !!d.defaultOpen;
|
|
436
|
+
if (st === "running") this.#runStart = Date.now();
|
|
437
|
+
} else if (st !== prev) {
|
|
438
|
+
this.#statusIn = true;
|
|
439
|
+
if (prev === "running") {
|
|
440
|
+
this.#measured = this.#liveMs(d);
|
|
441
|
+
this.#stopTick();
|
|
442
|
+
}
|
|
443
|
+
if (st === "running") {
|
|
444
|
+
this.#runStart = Date.now();
|
|
445
|
+
this.#measured = void 0;
|
|
446
|
+
}
|
|
447
|
+
if (d.open === void 0 && d.openOnError !== false) {
|
|
448
|
+
if (st === "error" && !this.#open) {
|
|
449
|
+
this.#open = true;
|
|
450
|
+
this.#auto = true;
|
|
451
|
+
} else if (prev === "error" && this.#auto) {
|
|
452
|
+
this.#open = false;
|
|
453
|
+
this.#auto = false;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
this.#status = st;
|
|
458
|
+
}
|
|
459
|
+
render() {
|
|
460
|
+
const d = this.data;
|
|
461
|
+
if (!d) return "";
|
|
462
|
+
const st = normStatus(d.status);
|
|
463
|
+
this.#sync(d, st);
|
|
464
|
+
const t = this.#t;
|
|
465
|
+
const open = this.#isOpen(d);
|
|
466
|
+
if (!this.#rendered) {
|
|
467
|
+
this.#rendered = true;
|
|
468
|
+
this.#shownOpen = open;
|
|
469
|
+
}
|
|
470
|
+
const shown = this.#shownOpen;
|
|
471
|
+
const summary = d.summary === void 0 || d.summary === null ? "" : String(d.summary);
|
|
472
|
+
const head = `<button type="button" class="tc-head" data-act="toggle" data-key="head" aria-expanded="${shown}" aria-controls="tc-body"${d.focusable === false ? ' tabindex="-1"' : ""}><span class="tc-icon" aria-hidden="true">${callIconSvg(d)}</span><span class="tc-main"><code class="tc-name">${esc(
|
|
473
|
+
d.name
|
|
474
|
+
)}</code>${summary ? `<span class="tc-summary" title="${escAttr(summary)}">${esc(summary)}</span>` : ""}</span>${this.#statusHtml(
|
|
475
|
+
d,
|
|
476
|
+
st,
|
|
477
|
+
t
|
|
478
|
+
)}${CHEV_SVG}</button>`;
|
|
479
|
+
const sections = open || shown ? `<div class="tc-sections">${this.#bodyHtml(d, st, t)}</div>` : "";
|
|
480
|
+
return `<div class="tc" data-status="${st}"${shown ? " data-open" : ""}>${head}<div class="tc-body" id="tc-body"${shown ? " data-open" : " inert"}><div class="tc-clip">${sections}</div></div></div>`;
|
|
481
|
+
}
|
|
482
|
+
#statusHtml(d, st, t) {
|
|
483
|
+
const cls = `tc-status${this.#statusIn ? " is-in" : ""}`;
|
|
484
|
+
if (st === "running")
|
|
485
|
+
return `<span class="${cls}">${SPIN_SVG}<span class="sr-only">${esc(t.running)}, </span><span class="tc-time">${esc(
|
|
486
|
+
formatToolMs(this.#liveMs(d), true)
|
|
487
|
+
)}</span></span>`;
|
|
488
|
+
if (st === "success") {
|
|
489
|
+
const ms = finite$1(d.duration) ?? this.#measured;
|
|
490
|
+
return `<span class="${cls}">${OK_SVG}<span class="sr-only">${esc(t.done)}${ms !== void 0 ? ", " : ""}</span>${ms !== void 0 ? `<span class="tc-time">${esc(formatToolMs(ms))}</span>` : ""}</span>`;
|
|
491
|
+
}
|
|
492
|
+
if (st === "error") return `<span class="${cls}">${ERR_SVG}<span class="tc-time">${esc(t.failed)}</span></span>`;
|
|
493
|
+
const dur = finite$1(d.duration);
|
|
494
|
+
return `<span class="${cls}">${STOP_SVG}<span class="tc-time">${esc(t.noResult)}${dur !== void 0 ? ` · ${esc(formatToolMs(dur))}` : ""}</span></span>`;
|
|
495
|
+
}
|
|
496
|
+
#bodyHtml(d, st, t) {
|
|
497
|
+
const items = [];
|
|
498
|
+
const at = formatToolClock(d.at, this.#lang);
|
|
499
|
+
let clock = at ? `<time class="tc-at" datetime="${escAttr(new Date(Number(d.at)).toISOString())}" title="${escAttr(
|
|
500
|
+
t.startedAt(at)
|
|
501
|
+
)}">${esc(at)}</time>` : "";
|
|
502
|
+
const take = () => {
|
|
503
|
+
if (items.length) return "";
|
|
504
|
+
const c = clock;
|
|
505
|
+
clock = "";
|
|
506
|
+
return c;
|
|
507
|
+
};
|
|
508
|
+
const line = (cls, text) => {
|
|
509
|
+
const c = take();
|
|
510
|
+
const p = `<p class="${cls}">${esc(text)}</p>`;
|
|
511
|
+
return c ? `<div class="tc-line">${p}${c}</div>` : p;
|
|
512
|
+
};
|
|
513
|
+
if (st === "interrupted") items.push(line("tc-pending", t.interrupted));
|
|
514
|
+
if (st === "error") {
|
|
515
|
+
const msg = d.error === void 0 || d.error === null || d.error === "" ? t.failedDefault : String(d.error);
|
|
516
|
+
items.push(
|
|
517
|
+
`<div class="tc-error">${ERR_SVG}<p class="tc-error-text">${esc(msg)}</p>${d.canRetry ? `<button type="button" class="tc-btn" data-act="retry" data-key="retry">${RETRY_SVG}${esc(t.retry)}</button>` : ""}</div>`
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
const hasArgs = d.args !== void 0;
|
|
521
|
+
const hasResult = d.result !== void 0;
|
|
522
|
+
const cap = this.#cap(d);
|
|
523
|
+
if (hasArgs) items.push(this.#section("args", t.args, d.args, cap, t, take()));
|
|
524
|
+
else this.#code.delete("args");
|
|
525
|
+
if (hasResult) items.push(this.#section("result", t.result, d.result, cap, t, take()));
|
|
526
|
+
else {
|
|
527
|
+
this.#code.delete("result");
|
|
528
|
+
if (st === "running" && !d.loading && !d.detailError)
|
|
529
|
+
items.push(
|
|
530
|
+
`<div class="tc-section"><div class="tc-section-head"><span class="tc-label">${esc(
|
|
531
|
+
t.result
|
|
532
|
+
)}</span>${take()}</div><p class="tc-pending">${esc(t.waiting)}</p></div>`
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
if (d.loading) items.push(line("tc-pending tc-loading", t.loading));
|
|
536
|
+
else if (d.detailError)
|
|
537
|
+
items.push(
|
|
538
|
+
`<div class="tc-reload-row"><p class="tc-pending">${esc(
|
|
539
|
+
d.detailError
|
|
540
|
+
)}</p><button type="button" class="tc-btn" data-act="reload" data-key="reload">${RETRY_SVG}${esc(t.reload)}</button></div>`
|
|
541
|
+
);
|
|
542
|
+
else if (st === "success" && !hasArgs && !hasResult) items.push(line("tc-pending", t.nothing));
|
|
543
|
+
if (d.note !== void 0 && d.note !== null && d.note !== "") items.push(line("tc-note", String(d.note)));
|
|
544
|
+
if (clock) items.unshift(`<div class="tc-top">${clock}</div>`);
|
|
545
|
+
return items.join("");
|
|
546
|
+
}
|
|
547
|
+
#section(part, label, value, cap, t, clock) {
|
|
548
|
+
const c = this.#codeOf(part, value);
|
|
549
|
+
const full = this.#full.has(part);
|
|
550
|
+
const code = this.#codeHtml(c, full, cap);
|
|
551
|
+
const copied = this.#copied.has(part);
|
|
552
|
+
const copy = copied ? t.copied : t.copy(label);
|
|
553
|
+
return `<div class="tc-section" data-part="${part}"><div class="tc-section-head"><span class="tc-label">${esc(
|
|
554
|
+
label
|
|
555
|
+
)}</span><span class="tc-head-end">${clock}<button type="button" class="tc-copy" data-act="copy" data-part="${part}" data-key="copy-${part}" aria-label="${escAttr(
|
|
556
|
+
copy
|
|
557
|
+
)}" title="${escAttr(copy)}"${copied ? " data-copied" : ""}>${COPY_SVG}${DONE_SVG}</button></span></div><pre class="tc-code" data-part="${part}"${c.json ? " data-json" : ""}><code${code.partial ? " data-partial" : ""}>${code.html}</code></pre><button type="button" class="tc-more" data-act="more" data-part="${part}" data-key="more-${part}" aria-expanded="${full}" hidden>${esc(
|
|
558
|
+
full ? t.showLess : t.showAll
|
|
559
|
+
)}</button></div>`;
|
|
560
|
+
}
|
|
561
|
+
/** Text + highlighted HTML per block, kept across renders while the value is the same. */
|
|
562
|
+
#codeOf(part, value) {
|
|
563
|
+
const hit = this.#code.get(part);
|
|
564
|
+
if (hit && hit.value === value) return hit;
|
|
565
|
+
const { text, json } = toCodeText(value);
|
|
566
|
+
if (hit && hit.text === text) {
|
|
567
|
+
hit.value = value;
|
|
568
|
+
return hit;
|
|
569
|
+
}
|
|
570
|
+
if (hit) this.#full.delete(part);
|
|
571
|
+
const c = { value, text, json, lines: lineCount(text) };
|
|
572
|
+
this.#code.set(part, c);
|
|
573
|
+
return c;
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* A block far taller than the cap renders only its first lines (enough to fill the cap, so it is capped all the
|
|
577
|
+
* same); Show all, a fallback selection, or a block that fits renders the whole. A 256 KiB result costs its first
|
|
578
|
+
* screen until someone asks for the rest.
|
|
579
|
+
*/
|
|
580
|
+
#codeHtml(c, full, cap) {
|
|
581
|
+
const keep = Math.ceil((cap + 24) / LINE_PX) + 20;
|
|
582
|
+
const maxChars = keep * HEAD_CHARS_PER_LINE;
|
|
583
|
+
if (!full && (c.lines > keep || c.text.length > maxChars)) {
|
|
584
|
+
if (c.head?.keep !== keep) {
|
|
585
|
+
const head = c.text.slice(0, Math.min(cutAfterLines(c.text, keep), maxChars));
|
|
586
|
+
c.head = { keep, html: c.json ? highlightJson(head) : esc(head) };
|
|
587
|
+
}
|
|
588
|
+
return { html: c.head.html, partial: true };
|
|
589
|
+
}
|
|
590
|
+
c.html ??= c.json ? highlightJson(c.text) : esc(c.text);
|
|
591
|
+
return { html: c.html, partial: false };
|
|
592
|
+
}
|
|
593
|
+
/** Puts the whole block into a partially rendered code element (in place). */
|
|
594
|
+
#fillCode(part) {
|
|
595
|
+
const code = this.$(`pre.tc-code[data-part="${part}"] > code[data-partial]`);
|
|
596
|
+
const c = this.#code.get(part);
|
|
597
|
+
if (!code || !c) return;
|
|
598
|
+
code.innerHTML = this.#codeHtml(c, true, 0).html;
|
|
599
|
+
code.removeAttribute("data-partial");
|
|
600
|
+
}
|
|
601
|
+
updated() {
|
|
602
|
+
const d = this.data;
|
|
603
|
+
if (d) {
|
|
604
|
+
const open = this.#isOpen(d);
|
|
605
|
+
if (open !== this.#shownOpen) {
|
|
606
|
+
this.$(".tc-body")?.getBoundingClientRect();
|
|
607
|
+
this.#shownOpen = open;
|
|
608
|
+
this.#applyOpen(open);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
this.#measure();
|
|
612
|
+
this.#observe();
|
|
613
|
+
this.#syncTick();
|
|
614
|
+
this.#announce();
|
|
615
|
+
}
|
|
616
|
+
#applyOpen(open) {
|
|
617
|
+
this.$(".tc")?.toggleAttribute("data-open", open);
|
|
618
|
+
this.$(".tc-head")?.setAttribute("aria-expanded", String(open));
|
|
619
|
+
const body = this.$(".tc-body");
|
|
620
|
+
body?.toggleAttribute("data-open", open);
|
|
621
|
+
body?.toggleAttribute("inert", !open);
|
|
622
|
+
}
|
|
623
|
+
#click(e) {
|
|
624
|
+
const btn = e.target?.closest?.("[data-act]");
|
|
625
|
+
const d = this.data;
|
|
626
|
+
if (!btn || !d) return;
|
|
627
|
+
const act = btn.getAttribute("data-act");
|
|
628
|
+
const part = btn.getAttribute("data-part") === "result" ? "result" : "args";
|
|
629
|
+
if (act === "toggle") this.#toggle(d);
|
|
630
|
+
else if (act === "copy") this.#copy(part);
|
|
631
|
+
else if (act === "more") this.#more(part);
|
|
632
|
+
else if (act === "retry") this.emit("ui-retry", this.#withId({}));
|
|
633
|
+
else if (act === "reload") this.emit("ui-reload", this.#withId({}));
|
|
634
|
+
}
|
|
635
|
+
#withId(detail) {
|
|
636
|
+
const id = this.data?.id;
|
|
637
|
+
return id === void 0 || id === null ? detail : { ...detail, id: String(id) };
|
|
638
|
+
}
|
|
639
|
+
#toggle(d) {
|
|
640
|
+
const next = !this.#isOpen(d);
|
|
641
|
+
if (!this.emit("ui-toggle", this.#withId({ open: next }))) return;
|
|
642
|
+
this.#auto = false;
|
|
643
|
+
if (d.open !== void 0) return;
|
|
644
|
+
this.#open = next;
|
|
645
|
+
this.renderNow();
|
|
646
|
+
}
|
|
647
|
+
/** Show all / Show less changes the block in place, so its height animates and a re-render keeps it. */
|
|
648
|
+
#more(part) {
|
|
649
|
+
if (this.#full.has(part)) this.#full.delete(part);
|
|
650
|
+
else {
|
|
651
|
+
this.#full.add(part);
|
|
652
|
+
this.#fillCode(part);
|
|
653
|
+
}
|
|
654
|
+
this.#measure();
|
|
655
|
+
}
|
|
656
|
+
#cap(d) {
|
|
657
|
+
return Math.max(40, finite$1(d.maxCodeHeight) ?? DEFAULT_CAP);
|
|
658
|
+
}
|
|
659
|
+
/** Caps blocks taller than maxCodeHeight (+24px slack) with a fade; Show all opens to the natural height. */
|
|
660
|
+
#measure() {
|
|
661
|
+
const d = this.data;
|
|
662
|
+
if (!d) return;
|
|
663
|
+
const t = this.#t;
|
|
664
|
+
const cap = this.#cap(d);
|
|
665
|
+
for (const pre of this.$$("pre.tc-code")) {
|
|
666
|
+
const part = pre.dataset.part === "result" ? "result" : "args";
|
|
667
|
+
const c = this.#code.get(part);
|
|
668
|
+
let h = pre.scrollHeight;
|
|
669
|
+
if (!h && c) h = c.lines * LINE_PX + CODE_PAD;
|
|
670
|
+
const capped = h > cap + 24;
|
|
671
|
+
const full = capped && this.#full.has(part);
|
|
672
|
+
pre.toggleAttribute("data-capped", capped && !full);
|
|
673
|
+
pre.style.maxHeight = capped ? `${full ? Math.ceil(h) : cap}px` : "";
|
|
674
|
+
const more = pre.parentElement?.querySelector(".tc-more");
|
|
675
|
+
if (!more) continue;
|
|
676
|
+
more.hidden = !capped;
|
|
677
|
+
more.setAttribute("aria-expanded", String(full));
|
|
678
|
+
const label = full ? t.showLess : t.showAll;
|
|
679
|
+
if (more.textContent !== label) more.textContent = label;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
/** Re-measure when a block's wrapped height changes (width change, panel shown after display:none). */
|
|
683
|
+
#observe() {
|
|
684
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
685
|
+
this.#ro ??= new ResizeObserver(() => this.#measure());
|
|
686
|
+
this.#ro.disconnect();
|
|
687
|
+
for (const code of this.$$("pre.tc-code > code")) this.#ro.observe(code);
|
|
688
|
+
}
|
|
689
|
+
#copy(part) {
|
|
690
|
+
const c = this.#code.get(part);
|
|
691
|
+
if (!c) return;
|
|
692
|
+
if (!this.emit("ui-copy", this.#withId({ part, text: c.text }))) return;
|
|
693
|
+
const done = () => this.#flashCopied(part);
|
|
694
|
+
const fallback = () => this.#select(part);
|
|
695
|
+
try {
|
|
696
|
+
const clip = typeof navigator !== "undefined" ? navigator.clipboard : void 0;
|
|
697
|
+
if (clip && typeof clip.writeText === "function") clip.writeText(c.text).then(done, fallback);
|
|
698
|
+
else fallback();
|
|
699
|
+
} catch {
|
|
700
|
+
fallback();
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
/** The copy icon morphs into a check for 1.4 s — in place, and a re-render in between keeps it. */
|
|
704
|
+
#flashCopied(part) {
|
|
705
|
+
this.#copied.add(part);
|
|
706
|
+
this.#paintCopy(part);
|
|
707
|
+
this.#speak(this.#t.copied);
|
|
708
|
+
clearTimeout(this.#copyTimers.get(part));
|
|
709
|
+
this.#copyTimers.set(
|
|
710
|
+
part,
|
|
711
|
+
window.setTimeout(() => {
|
|
712
|
+
this.#copied.delete(part);
|
|
713
|
+
this.#copyTimers.delete(part);
|
|
714
|
+
this.#paintCopy(part);
|
|
715
|
+
}, COPIED_MS)
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
#paintCopy(part) {
|
|
719
|
+
const b = this.$(`.tc-copy[data-part="${part}"]`);
|
|
720
|
+
if (!b) return;
|
|
721
|
+
const t = this.#t;
|
|
722
|
+
const on = this.#copied.has(part);
|
|
723
|
+
const label = on ? t.copied : t.copy(part === "args" ? t.args : t.result);
|
|
724
|
+
b.toggleAttribute("data-copied", on);
|
|
725
|
+
b.setAttribute("aria-label", label);
|
|
726
|
+
b.setAttribute("title", label);
|
|
727
|
+
}
|
|
728
|
+
/** Without clipboard access (plain http, a denied permission) the block's text is selected for ⌘C / Ctrl+C. */
|
|
729
|
+
#select(part) {
|
|
730
|
+
this.#fillCode(part);
|
|
731
|
+
const code = this.$(`pre.tc-code[data-part="${part}"] > code`);
|
|
732
|
+
try {
|
|
733
|
+
const sel = typeof getSelection === "function" ? getSelection() : null;
|
|
734
|
+
if (code && sel) {
|
|
735
|
+
const range = document.createRange();
|
|
736
|
+
range.selectNodeContents(code);
|
|
737
|
+
sel.removeAllRanges();
|
|
738
|
+
sel.addRange(range);
|
|
739
|
+
}
|
|
740
|
+
} catch {
|
|
741
|
+
}
|
|
742
|
+
this.#speak(this.#t.selected);
|
|
743
|
+
}
|
|
744
|
+
#speak(text) {
|
|
745
|
+
if (this.#live.textContent !== text) this.#live.textContent = text;
|
|
746
|
+
}
|
|
747
|
+
/** Status changes are announced once; a card that first appears finished or failed stays quiet. */
|
|
748
|
+
#announce() {
|
|
749
|
+
const d = this.data;
|
|
750
|
+
if (!d) return;
|
|
751
|
+
const st = normStatus(d.status);
|
|
752
|
+
if (st === this.#saidStatus) return;
|
|
753
|
+
const first = this.#saidStatus === void 0;
|
|
754
|
+
this.#saidStatus = st;
|
|
755
|
+
if (first && st !== "running") return;
|
|
756
|
+
const t = this.#t;
|
|
757
|
+
const name = String(d.name ?? "");
|
|
758
|
+
this.#speak(
|
|
759
|
+
st === "running" ? t.sayRunning(name) : st === "success" ? t.sayDone(name) : st === "error" ? t.sayFailed(name, d.error ? String(d.error) : "") : t.sayInterrupted(name)
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
/** The live timer ticks a text node in place every 100 ms — only while running and connected. */
|
|
763
|
+
#syncTick() {
|
|
764
|
+
const run = !!this.data && normStatus(this.data.status) === "running" && this.isConnected;
|
|
765
|
+
if (run && !this.#tick) this.#tick = window.setInterval(() => this.#paintTime(), TICK_MS$1);
|
|
766
|
+
else if (!run) this.#stopTick();
|
|
767
|
+
}
|
|
768
|
+
#stopTick() {
|
|
769
|
+
if (!this.#tick) return;
|
|
770
|
+
clearInterval(this.#tick);
|
|
771
|
+
this.#tick = 0;
|
|
772
|
+
}
|
|
773
|
+
#paintTime() {
|
|
774
|
+
const d = this.data;
|
|
775
|
+
if (!d || normStatus(d.status) !== "running" || !this.isConnected) {
|
|
776
|
+
this.#stopTick();
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
const node = this.$(".tc-status .tc-time")?.firstChild;
|
|
780
|
+
const text = formatToolMs(this.#liveMs(d), true);
|
|
781
|
+
if (node && node.nodeType === Node.TEXT_NODE && node.data !== text) node.data = text;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
define("ui-tool-call-card", UiToolCallCard);
|
|
785
|
+
const TICK_MS = 100;
|
|
786
|
+
const STACK = 3;
|
|
787
|
+
function callKeys(calls) {
|
|
788
|
+
const seen = /* @__PURE__ */ new Set();
|
|
789
|
+
return calls.map((c, i) => {
|
|
790
|
+
const id = c && c.id !== void 0 && c.id !== null && c.id !== "" ? `id:${c.id}` : `i:${i}`;
|
|
791
|
+
const key = seen.has(id) ? `${id}#${i}` : id;
|
|
792
|
+
seen.add(key);
|
|
793
|
+
return key;
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
function sameCall(a, b) {
|
|
797
|
+
if (a === b) return true;
|
|
798
|
+
const ka = Object.keys(a);
|
|
799
|
+
if (ka.length !== Object.keys(b).length) return false;
|
|
800
|
+
for (const k of ka) if (!Object.is(a[k], b[k])) return false;
|
|
801
|
+
return true;
|
|
802
|
+
}
|
|
803
|
+
const finite = (v) => {
|
|
804
|
+
if (v === void 0 || v === null || v === "") return void 0;
|
|
805
|
+
const n = Number(v);
|
|
806
|
+
return Number.isFinite(n) ? n : void 0;
|
|
807
|
+
};
|
|
808
|
+
class UiToolCallGroup extends UiElement {
|
|
809
|
+
static styles = [styles];
|
|
810
|
+
static get observedAttributes() {
|
|
811
|
+
return ["lang", "theme"];
|
|
812
|
+
}
|
|
813
|
+
#open = false;
|
|
814
|
+
#shownOpen = false;
|
|
815
|
+
#rendered = false;
|
|
816
|
+
#running;
|
|
817
|
+
#titleIn = false;
|
|
818
|
+
#acc = 0;
|
|
819
|
+
#since = null;
|
|
820
|
+
#lastEnd = 0;
|
|
821
|
+
#tick = 0;
|
|
822
|
+
#keys = [];
|
|
823
|
+
#stack = /* @__PURE__ */ new Set();
|
|
824
|
+
#cards = /* @__PURE__ */ new Map();
|
|
825
|
+
#saidRunning;
|
|
826
|
+
#body;
|
|
827
|
+
#list;
|
|
828
|
+
#live;
|
|
829
|
+
constructor() {
|
|
830
|
+
super();
|
|
831
|
+
this.#body = document.createElement("div");
|
|
832
|
+
this.#body.className = "tc-body tcg-body";
|
|
833
|
+
this.#body.id = "tcg-body";
|
|
834
|
+
this.#body.hidden = true;
|
|
835
|
+
this.#body.setAttribute("inert", "");
|
|
836
|
+
const clip = document.createElement("div");
|
|
837
|
+
clip.className = "tc-clip";
|
|
838
|
+
this.#list = document.createElement("div");
|
|
839
|
+
this.#list.className = "tcg-list";
|
|
840
|
+
this.#list.setAttribute("role", "list");
|
|
841
|
+
clip.append(this.#list);
|
|
842
|
+
this.#body.append(clip);
|
|
843
|
+
this.#live = document.createElement("span");
|
|
844
|
+
this.#live.className = "sr-only";
|
|
845
|
+
this.#live.setAttribute("role", "status");
|
|
846
|
+
this.#live.setAttribute("aria-live", "polite");
|
|
847
|
+
this.root.append(this.#body, this.#live);
|
|
848
|
+
this.root.addEventListener("click", (e) => {
|
|
849
|
+
if (e.target?.closest?.(".tcg-head")) this.#toggle();
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
attributeChangedCallback() {
|
|
853
|
+
this.requestRender();
|
|
854
|
+
}
|
|
855
|
+
disconnectedCallback() {
|
|
856
|
+
this.#stopTick();
|
|
857
|
+
}
|
|
858
|
+
/** Focus the group's header button. */
|
|
859
|
+
focus(options) {
|
|
860
|
+
if (!this.$(".tcg-head") && this.data && this.isConnected) this.renderNow();
|
|
861
|
+
const head = this.$(".tcg-head");
|
|
862
|
+
if (head) head.focus(options);
|
|
863
|
+
else super.focus(options);
|
|
864
|
+
}
|
|
865
|
+
/** Whether the list is open now (controlled `data.open`, else the element's own state). */
|
|
866
|
+
get isOpen() {
|
|
867
|
+
return this.data ? this.#isOpen(this.data) : false;
|
|
868
|
+
}
|
|
869
|
+
/** The card elements, in call order. */
|
|
870
|
+
get cards() {
|
|
871
|
+
return Array.from(this.#cards.values(), (e) => e.el);
|
|
872
|
+
}
|
|
873
|
+
get #t() {
|
|
874
|
+
return toolCallStrings(this.getAttribute("lang"));
|
|
875
|
+
}
|
|
876
|
+
#isOpen(d) {
|
|
877
|
+
return d.open ?? this.#open;
|
|
878
|
+
}
|
|
879
|
+
/** Time with tools running, added up over running stretches (e.g. a pause before a retry). */
|
|
880
|
+
#syncRun(running, calls) {
|
|
881
|
+
const prev = this.#running;
|
|
882
|
+
this.#titleIn = prev !== void 0 && prev !== running;
|
|
883
|
+
const now = Date.now();
|
|
884
|
+
if (running && this.#since === null) {
|
|
885
|
+
const starts = calls.filter((c) => c.status === "running").map((c) => finite(c.startedAt)).filter((v) => v !== void 0);
|
|
886
|
+
const first = starts.length ? Math.min(...starts) : now;
|
|
887
|
+
this.#since = Math.min(now, Math.max(this.#lastEnd, first));
|
|
888
|
+
} else if (!running && this.#since !== null) {
|
|
889
|
+
this.#lastEnd = now;
|
|
890
|
+
this.#acc += now - this.#since;
|
|
891
|
+
this.#since = null;
|
|
892
|
+
}
|
|
893
|
+
if (prev !== running && !running) this.#stopTick();
|
|
894
|
+
this.#running = running;
|
|
895
|
+
}
|
|
896
|
+
#liveMs() {
|
|
897
|
+
return this.#acc + (this.#since === null ? 0 : Math.max(0, Date.now() - this.#since));
|
|
898
|
+
}
|
|
899
|
+
render() {
|
|
900
|
+
const d = this.data;
|
|
901
|
+
if (!d) return "";
|
|
902
|
+
const calls = Array.isArray(d.calls) ? d.calls : [];
|
|
903
|
+
const t = this.#t;
|
|
904
|
+
const { running, failed, sum } = groupStats(calls);
|
|
905
|
+
this.#syncRun(running, calls);
|
|
906
|
+
if (!this.#rendered) this.#open = !!d.defaultOpen;
|
|
907
|
+
const open = this.#isOpen(d);
|
|
908
|
+
const first = !this.#rendered;
|
|
909
|
+
if (first) {
|
|
910
|
+
this.#rendered = true;
|
|
911
|
+
this.#shownOpen = open;
|
|
912
|
+
}
|
|
913
|
+
const shown = this.#shownOpen;
|
|
914
|
+
const keys = callKeys(calls);
|
|
915
|
+
this.#keys = keys;
|
|
916
|
+
const from = Math.max(0, calls.length - STACK);
|
|
917
|
+
const stackKeys = /* @__PURE__ */ new Set();
|
|
918
|
+
let stack = "";
|
|
919
|
+
for (let i = from; i < calls.length; i++) {
|
|
920
|
+
stackKeys.add(keys[i]);
|
|
921
|
+
const enter = !first && !this.#stack.has(keys[i]);
|
|
922
|
+
stack += `<span class="tc-icon${enter ? " is-in" : ""}">${callIconSvg(calls[i] ?? { name: "" })}</span>`;
|
|
923
|
+
}
|
|
924
|
+
this.#stack = stackKeys;
|
|
925
|
+
const title = running ? d.runningLabel || t.using : d.label || t.used(calls.length);
|
|
926
|
+
const ms = running ? this.#liveMs() : finite(d.duration) ?? sum ?? (this.#acc > 0 ? this.#acc : void 0);
|
|
927
|
+
return `<div class="tcg"${running ? " data-running" : ""}${shown ? " data-open" : ""}><button type="button" class="tcg-head" data-key="head" aria-expanded="${shown}" aria-controls="tcg-body"${d.focusable === false ? ' tabindex="-1"' : ""}><span class="tcg-stack" aria-hidden="true">${stack}</span><span class="tcg-title${this.#titleIn ? " is-in" : ""}"><span class="tcg-label${running ? " tc-shine" : ""}" data-text="${escAttr(title)}">${esc(title)}</span>${!running && failed ? `<span class="tcg-failed">· ${esc(t.nFailed(failed))}</span>` : ""}${ms !== void 0 ? `<span class="tcg-time">· ${esc(formatToolMs(ms, running))}</span>` : ""}</span>${CHEV_SVG}</button></div>`;
|
|
928
|
+
}
|
|
929
|
+
updated() {
|
|
930
|
+
const d = this.data;
|
|
931
|
+
this.#body.hidden = !d;
|
|
932
|
+
if (d) {
|
|
933
|
+
this.#reconcile(d);
|
|
934
|
+
const open = this.#isOpen(d);
|
|
935
|
+
if (open !== this.#shownOpen) {
|
|
936
|
+
this.$(".tcg")?.getBoundingClientRect();
|
|
937
|
+
this.#shownOpen = open;
|
|
938
|
+
}
|
|
939
|
+
this.#applyOpen(this.#shownOpen);
|
|
940
|
+
}
|
|
941
|
+
this.#syncTick();
|
|
942
|
+
this.#announce();
|
|
943
|
+
}
|
|
944
|
+
/** One card per call, keyed by id (else index): create / remove only what changed, keep the rest in place. */
|
|
945
|
+
#reconcile(d) {
|
|
946
|
+
const calls = Array.isArray(d.calls) ? d.calls : [];
|
|
947
|
+
const keys = this.#keys.length === calls.length ? this.#keys : callKeys(calls);
|
|
948
|
+
const lang = this.getAttribute("lang");
|
|
949
|
+
const theme = this.getAttribute("theme");
|
|
950
|
+
const next = /* @__PURE__ */ new Map();
|
|
951
|
+
calls.forEach((call, i) => {
|
|
952
|
+
if (!call) return;
|
|
953
|
+
let entry = this.#cards.get(keys[i]);
|
|
954
|
+
if (!entry) {
|
|
955
|
+
const el = document.createElement("ui-tool-call-card");
|
|
956
|
+
el.setAttribute("role", "listitem");
|
|
957
|
+
entry = { el, data: void 0 };
|
|
958
|
+
}
|
|
959
|
+
syncAttr(entry.el, "lang", lang);
|
|
960
|
+
syncAttr(entry.el, "theme", theme);
|
|
961
|
+
const data = d.focusable === false && call.focusable === void 0 ? { ...call, focusable: false } : call;
|
|
962
|
+
if (!entry.data || !sameCall(entry.data, data)) {
|
|
963
|
+
entry.data = data;
|
|
964
|
+
entry.el.data = data;
|
|
965
|
+
}
|
|
966
|
+
next.set(keys[i], entry);
|
|
967
|
+
});
|
|
968
|
+
for (const [key, entry] of this.#cards) if (!next.has(key)) entry.el.remove();
|
|
969
|
+
let at = this.#list.firstElementChild;
|
|
970
|
+
for (const { el } of next.values()) {
|
|
971
|
+
if (el === at) at = at.nextElementSibling;
|
|
972
|
+
else this.#list.insertBefore(el, at);
|
|
973
|
+
}
|
|
974
|
+
this.#cards = next;
|
|
975
|
+
}
|
|
976
|
+
#applyOpen(open) {
|
|
977
|
+
this.$(".tcg")?.toggleAttribute("data-open", open);
|
|
978
|
+
this.$(".tcg-head")?.setAttribute("aria-expanded", String(open));
|
|
979
|
+
this.#body.toggleAttribute("data-open", open);
|
|
980
|
+
this.#body.toggleAttribute("inert", !open);
|
|
981
|
+
}
|
|
982
|
+
#toggle() {
|
|
983
|
+
const d = this.data;
|
|
984
|
+
if (!d) return;
|
|
985
|
+
const next = !this.#isOpen(d);
|
|
986
|
+
if (!this.emit("ui-toggle", { open: next })) return;
|
|
987
|
+
if (d.open !== void 0) return;
|
|
988
|
+
this.#open = next;
|
|
989
|
+
this.#shownOpen = next;
|
|
990
|
+
this.#applyOpen(next);
|
|
991
|
+
}
|
|
992
|
+
/** Running ↔ done is announced once each way; a group that first appears done stays quiet. */
|
|
993
|
+
#announce() {
|
|
994
|
+
const d = this.data;
|
|
995
|
+
if (!d) return;
|
|
996
|
+
const running = !!this.#running;
|
|
997
|
+
if (running === this.#saidRunning) return;
|
|
998
|
+
const first = this.#saidRunning === void 0;
|
|
999
|
+
this.#saidRunning = running;
|
|
1000
|
+
if (first && !running) return;
|
|
1001
|
+
const t = this.#t;
|
|
1002
|
+
const calls = Array.isArray(d.calls) ? d.calls : [];
|
|
1003
|
+
const text = running ? d.runningLabel || t.using : t.groupSay(d.label || t.used(calls.length), groupStats(calls).failed);
|
|
1004
|
+
if (this.#live.textContent !== text) this.#live.textContent = text;
|
|
1005
|
+
}
|
|
1006
|
+
#syncTick() {
|
|
1007
|
+
const run = !!this.data && !!this.#running && this.isConnected;
|
|
1008
|
+
if (run && !this.#tick) this.#tick = window.setInterval(() => this.#paintTime(), TICK_MS);
|
|
1009
|
+
else if (!run) this.#stopTick();
|
|
1010
|
+
}
|
|
1011
|
+
#stopTick() {
|
|
1012
|
+
if (!this.#tick) return;
|
|
1013
|
+
clearInterval(this.#tick);
|
|
1014
|
+
this.#tick = 0;
|
|
1015
|
+
}
|
|
1016
|
+
#paintTime() {
|
|
1017
|
+
if (!this.#running || !this.isConnected) {
|
|
1018
|
+
this.#stopTick();
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
const node = this.$(".tcg-time")?.firstChild;
|
|
1022
|
+
const text = `· ${formatToolMs(this.#liveMs(), true)}`;
|
|
1023
|
+
if (node && node.nodeType === Node.TEXT_NODE && node.data !== text) node.data = text;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
function syncAttr(el, name, value) {
|
|
1027
|
+
if (el.getAttribute(name) === value) return;
|
|
1028
|
+
if (value === null) el.removeAttribute(name);
|
|
1029
|
+
else el.setAttribute(name, value);
|
|
1030
|
+
}
|
|
1031
|
+
define("ui-tool-call-group", UiToolCallGroup);
|
|
1032
|
+
export {
|
|
1033
|
+
TOOL_ICONS,
|
|
1034
|
+
UiToolCallCard,
|
|
1035
|
+
UiToolCallGroup,
|
|
1036
|
+
callKeys,
|
|
1037
|
+
formatToolClock,
|
|
1038
|
+
formatToolMs,
|
|
1039
|
+
groupStats,
|
|
1040
|
+
highlightJson,
|
|
1041
|
+
normStatus,
|
|
1042
|
+
toCodeText,
|
|
1043
|
+
toolCallStrings,
|
|
1044
|
+
toolIcon
|
|
1045
|
+
};
|
|
1046
|
+
//# sourceMappingURL=tool-call-card.js.map
|