@juspay/svelte-ui-components 2.130.0 → 2.131.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/TaskList/TaskList.svelte +268 -0
- package/dist/TaskList/TaskList.svelte.d.ts +4 -0
- package/dist/TaskList/properties.d.ts +24 -0
- package/dist/TaskList/properties.js +1 -0
- package/dist/ThinkingIndicator/ThinkingIndicator.svelte +621 -35
- package/dist/ThinkingIndicator/ThinkingIndicator.svelte.d.ts +1 -1
- package/dist/ThinkingIndicator/properties.d.ts +63 -3
- package/dist/ToolCallLog/ToolCallLog.svelte +418 -0
- package/dist/ToolCallLog/ToolCallLog.svelte.d.ts +4 -0
- package/dist/ToolCallLog/properties.d.ts +27 -0
- package/dist/ToolCallLog/properties.js +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +3 -0
- package/dist/soundKit/properties.d.ts +29 -0
- package/dist/soundKit/properties.js +1 -0
- package/dist/soundKit/soundKit.d.ts +2 -0
- package/dist/soundKit/soundKit.js +284 -0
- package/package.json +1 -1
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import type { Action } from 'svelte/action';
|
|
2
3
|
import Accordion from '../Accordion/Accordion.svelte';
|
|
3
4
|
import Button from '../Button/Button.svelte';
|
|
4
5
|
import Loader from '../Loader/Loader.svelte';
|
|
6
|
+
import Pill from '../Pill/Pill.svelte';
|
|
5
7
|
import type { ThinkingIndicatorProperties } from './properties';
|
|
6
8
|
|
|
7
9
|
let {
|
|
@@ -9,6 +11,7 @@
|
|
|
9
11
|
detail,
|
|
10
12
|
expanded = $bindable(false),
|
|
11
13
|
variant = 'default',
|
|
14
|
+
showElapsed = false,
|
|
12
15
|
onToggle,
|
|
13
16
|
avatar,
|
|
14
17
|
toggleIcon,
|
|
@@ -16,25 +19,197 @@
|
|
|
16
19
|
toggleTestId,
|
|
17
20
|
detailTestId,
|
|
18
21
|
labelTestId,
|
|
19
|
-
classes
|
|
22
|
+
classes,
|
|
23
|
+
rows,
|
|
24
|
+
kind = 'steps',
|
|
25
|
+
busy,
|
|
26
|
+
query,
|
|
27
|
+
moreLabel,
|
|
28
|
+
selectable = false,
|
|
29
|
+
selected = $bindable(null),
|
|
30
|
+
onrowselect,
|
|
31
|
+
onsettled,
|
|
32
|
+
collapseDelayMs = 2600
|
|
20
33
|
}: ThinkingIndicatorProperties = $props();
|
|
21
34
|
|
|
22
|
-
//
|
|
23
|
-
//
|
|
35
|
+
// `rows` being present at all (even `[]`) puts the indicator into trace mode: the
|
|
36
|
+
// Accordion body renders the kind-aware trace instead of the `detail` paragraph,
|
|
37
|
+
// and that alone is enough to make the indicator expandable.
|
|
38
|
+
const hasTraceMode = $derived(Array.isArray(rows));
|
|
39
|
+
const traceRows = $derived(rows ?? []);
|
|
40
|
+
|
|
41
|
+
// A detail string or a trace is what makes the indicator expandable. Without either
|
|
42
|
+
// there is nothing to reveal, so it renders as a plain live status line instead of a
|
|
24
43
|
// disclosure control. `bare` overrides that entirely.
|
|
25
44
|
const isExpandable = $derived(
|
|
26
|
-
variant !== 'bare' && typeof detail === 'string' && detail.length > 0
|
|
45
|
+
variant !== 'bare' && (hasTraceMode || (typeof detail === 'string' && detail.length > 0))
|
|
27
46
|
);
|
|
28
47
|
|
|
48
|
+
// Backward-compatible shimmer rule: without an explicit `busy`, the expandable
|
|
49
|
+
// summary holds still (settled) and every other shape shimmers (live) — exactly
|
|
50
|
+
// today's released behaviour. Passing `busy` takes direct control of the shimmer
|
|
51
|
+
// in every shape, including `bare`.
|
|
52
|
+
const labelIsBusy = $derived(busy ?? !isExpandable);
|
|
53
|
+
|
|
54
|
+
// Set once a person clicks the disclosure open/closed — from then on the automatic
|
|
55
|
+
// busy-driven machine below leaves `expanded` alone for the rest of this mount.
|
|
56
|
+
let manuallyToggled = false;
|
|
57
|
+
|
|
29
58
|
const handleToggle = (): void => {
|
|
59
|
+
manuallyToggled = true;
|
|
30
60
|
expanded = !expanded;
|
|
31
61
|
onToggle?.();
|
|
32
62
|
};
|
|
63
|
+
|
|
64
|
+
// Elapsed counter: ticks while the label is live (see `labelIsBusy`); freezes at its
|
|
65
|
+
// last value once it settles. Resets to 0 each time a fresh busy phase starts.
|
|
66
|
+
let elapsedSeconds = $state(0);
|
|
67
|
+
let hasTicked = $state(false);
|
|
68
|
+
let tickInterval: ReturnType<typeof setInterval> | null = null;
|
|
69
|
+
|
|
70
|
+
const startTicking = (): void => {
|
|
71
|
+
elapsedSeconds = 0;
|
|
72
|
+
hasTicked = true;
|
|
73
|
+
if (tickInterval) {
|
|
74
|
+
clearInterval(tickInterval);
|
|
75
|
+
}
|
|
76
|
+
tickInterval = setInterval(() => {
|
|
77
|
+
elapsedSeconds += 1;
|
|
78
|
+
}, 1000);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const stopTicking = (): void => {
|
|
82
|
+
if (tickInterval) {
|
|
83
|
+
clearInterval(tickInterval);
|
|
84
|
+
tickInterval = null;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const elapsedWatcher: Action<HTMLElement, boolean> = (_node, initialShouldTick) => {
|
|
89
|
+
// Reset-and-start happens inside `startTicking` itself, and only when this
|
|
90
|
+
// mount is actually asked to tick. That matters because this same action is
|
|
91
|
+
// mounted on BOTH the status-host and expandable roots, which are separate
|
|
92
|
+
// elements swapped by the `{#if}` above: when a `detail`/`rows`-less status
|
|
93
|
+
// line becomes expandable mid-life, Svelte destroys the status-host node and
|
|
94
|
+
// mounts a fresh expandable one, re-running this action with an initial value
|
|
95
|
+
// that is now `false` (busy has already settled) — skipping the reset here
|
|
96
|
+
// lets `hasTicked`/`elapsedSeconds` carry the frozen final value across that
|
|
97
|
+
// swap instead of wiping it back to 0/false.
|
|
98
|
+
let previous = initialShouldTick;
|
|
99
|
+
if (initialShouldTick) {
|
|
100
|
+
startTicking();
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
update(nowShouldTick: boolean): void {
|
|
104
|
+
if (nowShouldTick !== previous) {
|
|
105
|
+
previous = nowShouldTick;
|
|
106
|
+
if (nowShouldTick) {
|
|
107
|
+
startTicking();
|
|
108
|
+
} else {
|
|
109
|
+
// Freeze, don't hide: stop the interval but leave `hasTicked` set so
|
|
110
|
+
// the expandable branch keeps showing the final elapsed value. The
|
|
111
|
+
// `showElapsed` prop itself (not `hasTicked`) is what lets a host
|
|
112
|
+
// hide the counter outright.
|
|
113
|
+
stopTicking();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
destroy(): void {
|
|
118
|
+
stopTicking();
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Disclosure machine (the ThinkingTrace settleWatcher pattern, adapted to a plain
|
|
124
|
+
// bindable boolean instead of a tri-state): auto-open while busy, auto-collapse a
|
|
125
|
+
// beat after settling. A manual toggle (handleToggle, above) permanently takes over
|
|
126
|
+
// for the rest of this mount. Only engages once a host passes `busy` at all —
|
|
127
|
+
// omitting it keeps today's released behaviour (manual toggling only, no timers).
|
|
128
|
+
let settledOnce = false;
|
|
129
|
+
let collapseTimer: ReturnType<typeof setTimeout> | null = null;
|
|
130
|
+
|
|
131
|
+
const applyBusyChange = (nowBusy: boolean): void => {
|
|
132
|
+
if (nowBusy) {
|
|
133
|
+
settledOnce = false;
|
|
134
|
+
if (collapseTimer) {
|
|
135
|
+
clearTimeout(collapseTimer);
|
|
136
|
+
collapseTimer = null;
|
|
137
|
+
}
|
|
138
|
+
if (!manuallyToggled) {
|
|
139
|
+
expanded = true;
|
|
140
|
+
}
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (!settledOnce) {
|
|
144
|
+
settledOnce = true;
|
|
145
|
+
onsettled?.();
|
|
146
|
+
if (collapseDelayMs !== null && !manuallyToggled) {
|
|
147
|
+
collapseTimer = setTimeout(() => {
|
|
148
|
+
if (!manuallyToggled) {
|
|
149
|
+
expanded = false;
|
|
150
|
+
}
|
|
151
|
+
}, collapseDelayMs);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const busyWatcher: Action<HTMLElement, boolean | null> = (_node, initialBusy) => {
|
|
157
|
+
let previous = initialBusy;
|
|
158
|
+
if (initialBusy === true) {
|
|
159
|
+
expanded = true;
|
|
160
|
+
} else if (initialBusy === false) {
|
|
161
|
+
settledOnce = true;
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
update(nowBusy: boolean | null): void {
|
|
165
|
+
if (nowBusy !== previous) {
|
|
166
|
+
previous = nowBusy;
|
|
167
|
+
if (typeof nowBusy === 'boolean') {
|
|
168
|
+
applyBusyChange(nowBusy);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
destroy(): void {
|
|
173
|
+
if (collapseTimer) {
|
|
174
|
+
clearTimeout(collapseTimer);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// Trace rows appended in one update stagger relative to the batch, not the list start.
|
|
181
|
+
let staggerBase = $state(0);
|
|
182
|
+
|
|
183
|
+
const growthWatcher: Action<HTMLElement, number> = (_node, initialCount) => {
|
|
184
|
+
let previousCount = initialCount;
|
|
185
|
+
return {
|
|
186
|
+
update(count: number): void {
|
|
187
|
+
if (count > previousCount) {
|
|
188
|
+
staggerBase = previousCount;
|
|
189
|
+
}
|
|
190
|
+
previousCount = count;
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const rowDelay = (index: number): string => {
|
|
196
|
+
return `${Math.max(0, index - staggerBase) * 120}ms`;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const handleRowSelect = (index: number): void => {
|
|
200
|
+
selected = selected === index ? null : index;
|
|
201
|
+
onrowselect?.(selected);
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
let rowsHeight = $state(0);
|
|
33
205
|
</script>
|
|
34
206
|
|
|
35
207
|
{#if isExpandable}
|
|
36
208
|
<div
|
|
37
209
|
class="thinking-indicator expandable {classes ?? ''}"
|
|
210
|
+
class:busy={busy === true}
|
|
211
|
+
use:busyWatcher={busy ?? null}
|
|
212
|
+
use:elapsedWatcher={showElapsed && labelIsBusy}
|
|
38
213
|
data-pw={typeof testId === 'string' ? testId : null}
|
|
39
214
|
testID={typeof testId === 'string' ? testId : null}
|
|
40
215
|
>
|
|
@@ -45,37 +220,186 @@
|
|
|
45
220
|
testId={toggleTestId ?? (testId && `${testId}-toggle`)}
|
|
46
221
|
>
|
|
47
222
|
<span class="status-row">
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
</span>
|
|
53
|
-
<span class="arrow" class:expanded aria-hidden="true">
|
|
54
|
-
{#if toggleIcon}
|
|
55
|
-
{@render toggleIcon()}
|
|
56
|
-
{:else}
|
|
57
|
-
<svg viewBox="0 0 16 16" fill="none">
|
|
58
|
-
<path
|
|
59
|
-
d="M4 6l4 4 4-4"
|
|
60
|
-
stroke="currentColor"
|
|
61
|
-
stroke-width="1.5"
|
|
62
|
-
stroke-linecap="round"
|
|
63
|
-
stroke-linejoin="round"
|
|
64
|
-
/>
|
|
65
|
-
</svg>
|
|
223
|
+
{#if avatar || labelIsBusy}
|
|
224
|
+
<span class="avatar">
|
|
225
|
+
{#if avatar}{@render avatar()}{:else}<Loader />{/if}
|
|
226
|
+
</span>
|
|
66
227
|
{/if}
|
|
228
|
+
<span class="label-cluster">
|
|
229
|
+
<span
|
|
230
|
+
class="status-label"
|
|
231
|
+
class:static-label={!labelIsBusy}
|
|
232
|
+
data-pw={labelTestId ?? null}>{label}</span
|
|
233
|
+
>
|
|
234
|
+
<span class="arrow" class:expanded aria-hidden="true">
|
|
235
|
+
{#if toggleIcon}
|
|
236
|
+
{@render toggleIcon()}
|
|
237
|
+
{:else}
|
|
238
|
+
<svg viewBox="0 0 16 16" fill="none">
|
|
239
|
+
<path
|
|
240
|
+
d="M4 6l4 4 4-4"
|
|
241
|
+
stroke="currentColor"
|
|
242
|
+
stroke-width="1.5"
|
|
243
|
+
stroke-linecap="round"
|
|
244
|
+
stroke-linejoin="round"
|
|
245
|
+
/>
|
|
246
|
+
</svg>
|
|
247
|
+
{/if}
|
|
248
|
+
</span>
|
|
249
|
+
</span>
|
|
67
250
|
</span>
|
|
251
|
+
{#if showElapsed && hasTicked}
|
|
252
|
+
<span
|
|
253
|
+
class="elapsed"
|
|
254
|
+
data-pw={typeof testId === 'string' ? `${testId}-elapsed` : null}
|
|
255
|
+
testID={typeof testId === 'string' ? `${testId}-elapsed` : null}>{elapsedSeconds}s</span
|
|
256
|
+
>
|
|
257
|
+
{/if}
|
|
68
258
|
</Button>
|
|
69
259
|
</div>
|
|
70
260
|
<Accordion expand={expanded}>
|
|
71
|
-
<
|
|
72
|
-
{
|
|
73
|
-
|
|
261
|
+
<div class="thinking-indicator-body" inert={!expanded}>
|
|
262
|
+
{#if hasTraceMode}
|
|
263
|
+
{#if kind === 'search' && typeof query === 'string' && query.length > 0}
|
|
264
|
+
<span class="trace-query-wrap">
|
|
265
|
+
<Pill text={query}>
|
|
266
|
+
{#snippet leadingIcon()}
|
|
267
|
+
<svg
|
|
268
|
+
viewBox="0 0 24 24"
|
|
269
|
+
fill="none"
|
|
270
|
+
stroke="currentColor"
|
|
271
|
+
stroke-width="2"
|
|
272
|
+
aria-hidden="true"
|
|
273
|
+
>
|
|
274
|
+
<circle cx="11" cy="11" r="7" />
|
|
275
|
+
<path d="m21 21-4.3-4.3" stroke-linecap="round" />
|
|
276
|
+
</svg>
|
|
277
|
+
{/snippet}
|
|
278
|
+
</Pill>
|
|
279
|
+
</span>
|
|
280
|
+
{/if}
|
|
281
|
+
<div class="trace-body">
|
|
282
|
+
<span class="trace-connector" style:height="{rowsHeight}px" aria-hidden="true"></span>
|
|
283
|
+
<div
|
|
284
|
+
class="trace-rows"
|
|
285
|
+
use:growthWatcher={traceRows.length}
|
|
286
|
+
bind:clientHeight={rowsHeight}
|
|
287
|
+
>
|
|
288
|
+
{#each traceRows as row, index (index)}
|
|
289
|
+
{#if kind === 'coding' && selectable}
|
|
290
|
+
<button
|
|
291
|
+
class="trace-row selectable"
|
|
292
|
+
class:selected={selected === index}
|
|
293
|
+
type="button"
|
|
294
|
+
aria-pressed={selected === index}
|
|
295
|
+
style:animation-delay={rowDelay(index)}
|
|
296
|
+
onclick={() => handleRowSelect(index)}
|
|
297
|
+
>
|
|
298
|
+
<b class="row-primary">{row.primary}</b>
|
|
299
|
+
{#if row.secondary}<span class="row-secondary" class:mono={row.mono}
|
|
300
|
+
>{row.secondary}</span
|
|
301
|
+
>{/if}
|
|
302
|
+
{#if typeof row.added === 'number' || typeof row.removed === 'number'}
|
|
303
|
+
<span class="row-diffstat">
|
|
304
|
+
{#if typeof row.added === 'number'}<span class="added">+{row.added}</span
|
|
305
|
+
>{/if}
|
|
306
|
+
{#if typeof row.removed === 'number'}<span class="removed"
|
|
307
|
+
>−{row.removed}</span
|
|
308
|
+
>{/if}
|
|
309
|
+
</span>
|
|
310
|
+
{/if}
|
|
311
|
+
</button>
|
|
312
|
+
{:else if kind === 'search' && typeof row.href === 'string'}
|
|
313
|
+
<a
|
|
314
|
+
class="trace-row linked"
|
|
315
|
+
href={row.href}
|
|
316
|
+
target="_blank"
|
|
317
|
+
rel="noopener noreferrer"
|
|
318
|
+
style:animation-delay={rowDelay(index)}
|
|
319
|
+
>
|
|
320
|
+
<span class="row-badge" data-tone={(index % 3) + 1} aria-hidden="true">
|
|
321
|
+
<svg
|
|
322
|
+
viewBox="0 0 24 24"
|
|
323
|
+
fill="none"
|
|
324
|
+
stroke="currentColor"
|
|
325
|
+
stroke-width="3"
|
|
326
|
+
stroke-linecap="round"
|
|
327
|
+
stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></svg
|
|
328
|
+
>
|
|
329
|
+
</span>
|
|
330
|
+
<b class="row-primary">{row.primary}</b>
|
|
331
|
+
{#if row.secondary}<span class="row-secondary">{row.secondary}</span>{/if}
|
|
332
|
+
</a>
|
|
333
|
+
{:else}
|
|
334
|
+
<div
|
|
335
|
+
class="trace-row"
|
|
336
|
+
class:prose={kind === 'reasoning'}
|
|
337
|
+
style:animation-delay={rowDelay(index)}
|
|
338
|
+
>
|
|
339
|
+
{#if kind === 'steps'}
|
|
340
|
+
<span class="row-icon" aria-hidden="true">
|
|
341
|
+
{#if busy && index === traceRows.length - 1}
|
|
342
|
+
<Loader />
|
|
343
|
+
{:else}
|
|
344
|
+
<svg
|
|
345
|
+
viewBox="0 0 24 24"
|
|
346
|
+
fill="none"
|
|
347
|
+
stroke="currentColor"
|
|
348
|
+
stroke-width="2.4"
|
|
349
|
+
stroke-linecap="round"
|
|
350
|
+
stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></svg
|
|
351
|
+
>
|
|
352
|
+
{/if}
|
|
353
|
+
</span>
|
|
354
|
+
{:else if kind === 'search'}
|
|
355
|
+
<span class="row-badge" data-tone={(index % 3) + 1} aria-hidden="true">
|
|
356
|
+
<svg
|
|
357
|
+
viewBox="0 0 24 24"
|
|
358
|
+
fill="none"
|
|
359
|
+
stroke="currentColor"
|
|
360
|
+
stroke-width="3"
|
|
361
|
+
stroke-linecap="round"
|
|
362
|
+
stroke-linejoin="round"><path d="M20 6 9 17l-5-5" /></svg
|
|
363
|
+
>
|
|
364
|
+
</span>
|
|
365
|
+
{/if}
|
|
366
|
+
{#if kind === 'reasoning'}
|
|
367
|
+
{row.primary}
|
|
368
|
+
{:else}
|
|
369
|
+
<b class="row-primary">{row.primary}</b>
|
|
370
|
+
{/if}
|
|
371
|
+
{#if row.secondary}<span class="row-secondary" class:mono={row.mono}
|
|
372
|
+
>{row.secondary}</span
|
|
373
|
+
>{/if}
|
|
374
|
+
{#if typeof row.added === 'number' || typeof row.removed === 'number'}
|
|
375
|
+
<span class="row-diffstat">
|
|
376
|
+
{#if typeof row.added === 'number'}<span class="added">+{row.added}</span
|
|
377
|
+
>{/if}
|
|
378
|
+
{#if typeof row.removed === 'number'}<span class="removed"
|
|
379
|
+
>−{row.removed}</span
|
|
380
|
+
>{/if}
|
|
381
|
+
</span>
|
|
382
|
+
{/if}
|
|
383
|
+
</div>
|
|
384
|
+
{/if}
|
|
385
|
+
{/each}
|
|
386
|
+
</div>
|
|
387
|
+
</div>
|
|
388
|
+
{#if !busy && typeof moreLabel === 'string' && moreLabel.length > 0}
|
|
389
|
+
<div class="trace-more">{moreLabel}</div>
|
|
390
|
+
{/if}
|
|
391
|
+
{:else}
|
|
392
|
+
<p class="detail" data-pw={detailTestId ?? (testId && `${testId}-detail`) ?? null}>
|
|
393
|
+
{detail}
|
|
394
|
+
</p>
|
|
395
|
+
{/if}
|
|
396
|
+
</div>
|
|
74
397
|
</Accordion>
|
|
75
398
|
</div>
|
|
76
399
|
{:else if variant === 'bare'}
|
|
77
400
|
<span
|
|
78
401
|
class="status-label {classes ?? ''}"
|
|
402
|
+
class:static-label={!labelIsBusy}
|
|
79
403
|
data-pw={typeof testId === 'string' ? testId : null}
|
|
80
404
|
testID={typeof testId === 'string' ? testId : null}
|
|
81
405
|
>{#if typeof labelTestId === 'string'}<span data-pw={labelTestId} testID={labelTestId}
|
|
@@ -85,6 +409,7 @@
|
|
|
85
409
|
{:else}
|
|
86
410
|
<div
|
|
87
411
|
class="thinking-indicator status-host {classes ?? ''}"
|
|
412
|
+
use:elapsedWatcher={showElapsed && labelIsBusy}
|
|
88
413
|
data-pw={typeof testId === 'string' ? testId : null}
|
|
89
414
|
testID={typeof testId === 'string' ? testId : null}
|
|
90
415
|
>
|
|
@@ -92,12 +417,51 @@
|
|
|
92
417
|
<span class="avatar">
|
|
93
418
|
{#if avatar}{@render avatar()}{:else}<Loader />{/if}
|
|
94
419
|
</span>
|
|
95
|
-
<span class="status-label" data-pw={labelTestId ?? null}
|
|
420
|
+
<span class="status-label" class:static-label={!labelIsBusy} data-pw={labelTestId ?? null}
|
|
421
|
+
>{label}</span
|
|
422
|
+
>
|
|
96
423
|
</span>
|
|
424
|
+
{#if showElapsed}
|
|
425
|
+
<span
|
|
426
|
+
class="elapsed"
|
|
427
|
+
data-pw={typeof testId === 'string' ? `${testId}-elapsed` : null}
|
|
428
|
+
testID={typeof testId === 'string' ? `${testId}-elapsed` : null}>{elapsedSeconds}s</span
|
|
429
|
+
>
|
|
430
|
+
{/if}
|
|
97
431
|
</div>
|
|
98
432
|
{/if}
|
|
99
433
|
|
|
100
434
|
<style>
|
|
435
|
+
@keyframes thinking-indicator-shimmer {
|
|
436
|
+
0% {
|
|
437
|
+
background-position: 200% 0;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
100% {
|
|
441
|
+
background-position: -200% 0;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
@keyframes thinking-indicator-fade-up {
|
|
446
|
+
from {
|
|
447
|
+
opacity: 0;
|
|
448
|
+
transform: translateY(9px);
|
|
449
|
+
}
|
|
450
|
+
to {
|
|
451
|
+
opacity: 1;
|
|
452
|
+
transform: none;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
@keyframes thinking-indicator-fade-in {
|
|
457
|
+
from {
|
|
458
|
+
opacity: 0;
|
|
459
|
+
}
|
|
460
|
+
to {
|
|
461
|
+
opacity: 1;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
101
465
|
.thinking-indicator {
|
|
102
466
|
box-sizing: border-box;
|
|
103
467
|
width: 100%;
|
|
@@ -107,6 +471,13 @@
|
|
|
107
471
|
border-bottom: var(--thinking-indicator-border-bottom, 1px solid #e4e4e7);
|
|
108
472
|
padding-block: var(--thinking-indicator-padding-block, 0.5rem);
|
|
109
473
|
margin-bottom: var(--thinking-indicator-margin-bottom, 1rem);
|
|
474
|
+
min-height: 0;
|
|
475
|
+
transition: min-height 400ms
|
|
476
|
+
var(--thinking-indicator-trace-ease, cubic-bezier(0.23, 1, 0.32, 1));
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
.expandable.busy {
|
|
480
|
+
min-height: var(--thinking-indicator-trace-busy-min-height, 0px);
|
|
110
481
|
}
|
|
111
482
|
|
|
112
483
|
.toggle {
|
|
@@ -120,13 +491,17 @@
|
|
|
120
491
|
--button-padding: 0;
|
|
121
492
|
--button-justify-content: flex-start;
|
|
122
493
|
--button-text-color: inherit;
|
|
494
|
+
--button-content-gap: var(--thinking-indicator-header-gap, 0.375rem);
|
|
123
495
|
}
|
|
124
496
|
|
|
125
497
|
/* A flex ROW host makes the inner status-row hug its content, so the shimmer
|
|
126
|
-
gradient maps to the text's own width rather than the full container.
|
|
498
|
+
gradient maps to the text's own width rather than the full container. Neither
|
|
499
|
+
form stretches to fill the width — a trailing gap after a short label is real
|
|
500
|
+
empty space, not a reserved box, so nothing pushes the elapsed counter away. */
|
|
127
501
|
.status-host {
|
|
128
502
|
display: flex;
|
|
129
503
|
align-items: center;
|
|
504
|
+
gap: var(--thinking-indicator-header-gap, 0.375rem);
|
|
130
505
|
}
|
|
131
506
|
|
|
132
507
|
.status-row {
|
|
@@ -147,8 +522,17 @@
|
|
|
147
522
|
--loader-height: var(--thinking-indicator-avatar-loader-size, 1rem);
|
|
148
523
|
}
|
|
149
524
|
|
|
525
|
+
/* The chevron lives in the same flex cluster as the label so it hugs the text
|
|
526
|
+
immediately, instead of drifting to the far edge of the toggle button. */
|
|
527
|
+
.label-cluster {
|
|
528
|
+
display: flex;
|
|
529
|
+
align-items: center;
|
|
530
|
+
gap: var(--thinking-indicator-arrow-gap, 0.125rem);
|
|
531
|
+
min-width: 0;
|
|
532
|
+
}
|
|
533
|
+
|
|
150
534
|
.status-label {
|
|
151
|
-
flex: 1;
|
|
535
|
+
flex: 0 1 auto;
|
|
152
536
|
min-width: 0;
|
|
153
537
|
overflow: hidden;
|
|
154
538
|
white-space: nowrap;
|
|
@@ -168,7 +552,7 @@
|
|
|
168
552
|
infinite;
|
|
169
553
|
}
|
|
170
554
|
|
|
171
|
-
/*
|
|
555
|
+
/* A settled label (no live busy phase) holds still instead of shimmering. */
|
|
172
556
|
.static-label {
|
|
173
557
|
animation: none;
|
|
174
558
|
-webkit-text-fill-color: var(--thinking-indicator-label-color, #858585);
|
|
@@ -181,7 +565,6 @@
|
|
|
181
565
|
flex-shrink: 0;
|
|
182
566
|
width: var(--thinking-indicator-arrow-size, 1rem);
|
|
183
567
|
height: var(--thinking-indicator-arrow-size, 1rem);
|
|
184
|
-
margin-left: var(--thinking-indicator-arrow-margin-left, 0.25rem);
|
|
185
568
|
color: var(--thinking-indicator-arrow-color, #7a7a7a);
|
|
186
569
|
transform: rotate(-90deg);
|
|
187
570
|
transition: var(--thinking-indicator-arrow-transition, transform 0.2s ease-in-out);
|
|
@@ -205,13 +588,216 @@
|
|
|
205
588
|
text-align: left;
|
|
206
589
|
}
|
|
207
590
|
|
|
208
|
-
|
|
209
|
-
0
|
|
210
|
-
|
|
591
|
+
.elapsed {
|
|
592
|
+
flex-shrink: 0;
|
|
593
|
+
font-variant-numeric: tabular-nums;
|
|
594
|
+
color: var(--thinking-indicator-elapsed-color, #9a9a9a);
|
|
595
|
+
font-size: var(--thinking-indicator-elapsed-font-size, 0.75rem);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/* ---- Trace body (rows?: ThinkingIndicatorTraceRow[]) ---- */
|
|
599
|
+
|
|
600
|
+
/* Wrapper exists only to carry `inert` while the Accordion has it collapsed —
|
|
601
|
+
no box of its own, so it can't disturb the detail/trace layout it wraps. */
|
|
602
|
+
.thinking-indicator-body {
|
|
603
|
+
display: contents;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
.trace-query-wrap {
|
|
607
|
+
display: inline-flex;
|
|
608
|
+
margin: var(--thinking-indicator-trace-query-margin, 10px 0 0);
|
|
609
|
+
--pill-width: auto;
|
|
610
|
+
--pill-justify-content: flex-start;
|
|
611
|
+
--pill-cursor: default;
|
|
612
|
+
--pill-gap: 6px;
|
|
613
|
+
--pill-background: var(--thinking-indicator-trace-query-background, #f1f1f1);
|
|
614
|
+
--pill-color: var(--thinking-indicator-trace-query-color, #6b6b6b);
|
|
615
|
+
--pill-border-radius: var(--thinking-indicator-trace-query-radius, 999px);
|
|
616
|
+
--pill-padding: var(--thinking-indicator-trace-query-padding, 3px 11px);
|
|
617
|
+
--pill-font-size: var(--thinking-indicator-trace-query-font-size, 0.8125rem);
|
|
618
|
+
--pill-font-weight: 400;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
.trace-query-wrap :global(svg) {
|
|
622
|
+
width: 11px;
|
|
623
|
+
height: 11px;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
.trace-body {
|
|
627
|
+
display: flex;
|
|
628
|
+
gap: var(--thinking-indicator-trace-body-gap, 12px);
|
|
629
|
+
padding-top: var(--thinking-indicator-trace-body-padding-top, 10px);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
.trace-connector {
|
|
633
|
+
width: 2px;
|
|
634
|
+
border-radius: 999px;
|
|
635
|
+
background: var(--thinking-indicator-trace-connector-color, #dcdcdc);
|
|
636
|
+
margin-left: 6px;
|
|
637
|
+
flex-shrink: 0;
|
|
638
|
+
transition: height 500ms var(--thinking-indicator-trace-ease, cubic-bezier(0.23, 1, 0.32, 1));
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
.trace-rows {
|
|
642
|
+
flex: 1;
|
|
643
|
+
min-width: 0;
|
|
644
|
+
display: flex;
|
|
645
|
+
flex-direction: column;
|
|
646
|
+
gap: var(--thinking-indicator-trace-row-gap, 7px);
|
|
647
|
+
align-self: flex-start;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
.trace-row {
|
|
651
|
+
display: flex;
|
|
652
|
+
align-items: baseline;
|
|
653
|
+
gap: 8px;
|
|
654
|
+
font-size: var(--thinking-indicator-trace-row-font-size, 0.8125rem);
|
|
655
|
+
color: var(--thinking-indicator-trace-row-color, #2b2b2b);
|
|
656
|
+
animation: thinking-indicator-fade-up 320ms
|
|
657
|
+
var(--thinking-indicator-trace-ease, cubic-bezier(0.23, 1, 0.32, 1)) both;
|
|
658
|
+
text-decoration: none;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
.trace-row.prose {
|
|
662
|
+
color: var(--thinking-indicator-trace-prose-color, #6b6b6b);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
.trace-row.selectable {
|
|
666
|
+
border: none;
|
|
667
|
+
background: none;
|
|
668
|
+
font: inherit;
|
|
669
|
+
text-align: left;
|
|
670
|
+
padding: 3px 6px;
|
|
671
|
+
margin: -3px -6px;
|
|
672
|
+
border-radius: var(--thinking-indicator-trace-row-radius, 6px);
|
|
673
|
+
cursor: pointer;
|
|
674
|
+
transition: background 150ms ease;
|
|
675
|
+
width: calc(100% + 12px);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
.trace-row.selectable:hover {
|
|
679
|
+
background: var(--thinking-indicator-trace-row-hover-background, #f4f4f4);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
.trace-row.selectable.selected {
|
|
683
|
+
background: var(--thinking-indicator-trace-row-selected-background, #ececec);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
.trace-row.linked:hover .row-primary {
|
|
687
|
+
text-decoration: underline;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
.row-primary {
|
|
691
|
+
font-weight: var(--thinking-indicator-trace-row-weight, 500);
|
|
692
|
+
white-space: nowrap;
|
|
693
|
+
overflow: hidden;
|
|
694
|
+
text-overflow: ellipsis;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
.row-secondary {
|
|
698
|
+
color: var(--thinking-indicator-trace-secondary-color, #9a9a9a);
|
|
699
|
+
font-size: var(--thinking-indicator-trace-secondary-font-size, 0.75rem);
|
|
700
|
+
white-space: nowrap;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
.row-secondary.mono {
|
|
704
|
+
font-family: var(--thinking-indicator-trace-mono-font, ui-monospace, Menlo, monospace);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/* Steps kind: a static check icon, or — on the frontier row while busy — the
|
|
708
|
+
library Loader, recoloured/resized to match the row's small icon footprint. */
|
|
709
|
+
.row-icon {
|
|
710
|
+
display: inline-flex;
|
|
711
|
+
align-self: center;
|
|
712
|
+
width: 14px;
|
|
713
|
+
flex-shrink: 0;
|
|
714
|
+
color: var(--thinking-indicator-trace-icon-color, #9a9a9a);
|
|
715
|
+
--loader-width: var(--thinking-indicator-trace-spinner-size, 11px);
|
|
716
|
+
--loader-height: var(--thinking-indicator-trace-spinner-size, 11px);
|
|
717
|
+
--loader-before-width: 5px;
|
|
718
|
+
--loader-before-height: 5px;
|
|
719
|
+
--loader-after-width: 8px;
|
|
720
|
+
--loader-after-height: 8px;
|
|
721
|
+
--loader-foreground: var(--thinking-indicator-trace-spinner-color, #6b6b6b);
|
|
722
|
+
--loader-foreground-end: var(--thinking-indicator-trace-spinner-color-end, transparent);
|
|
723
|
+
--loader-background: var(--thinking-indicator-trace-spinner-track-color, #dcdcdc);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
.row-icon svg {
|
|
727
|
+
width: 12px;
|
|
728
|
+
height: 12px;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
.row-badge {
|
|
732
|
+
width: 15px;
|
|
733
|
+
height: 15px;
|
|
734
|
+
border-radius: 999px;
|
|
735
|
+
display: inline-flex;
|
|
736
|
+
align-items: center;
|
|
737
|
+
justify-content: center;
|
|
738
|
+
color: var(--thinking-indicator-trace-badge-check-color, #fff);
|
|
739
|
+
flex-shrink: 0;
|
|
740
|
+
align-self: center;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
.row-badge[data-tone='1'] {
|
|
744
|
+
background: var(--thinking-indicator-trace-tone-1, #2f6fec);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
.row-badge[data-tone='2'] {
|
|
748
|
+
background: var(--thinking-indicator-trace-tone-2, #e56d24);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
.row-badge[data-tone='3'] {
|
|
752
|
+
background: var(--thinking-indicator-trace-tone-3, #1f7a5f);
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
.row-badge svg {
|
|
756
|
+
width: 9px;
|
|
757
|
+
height: 9px;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
.row-diffstat {
|
|
761
|
+
margin-left: auto;
|
|
762
|
+
font-family: var(--thinking-indicator-trace-mono-font, ui-monospace, Menlo, monospace);
|
|
763
|
+
font-size: 0.75rem;
|
|
764
|
+
font-variant-numeric: tabular-nums;
|
|
765
|
+
white-space: nowrap;
|
|
766
|
+
display: inline-flex;
|
|
767
|
+
gap: 5px;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
.row-diffstat .added {
|
|
771
|
+
color: var(--thinking-indicator-trace-added-color, #1f7a5f);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
.row-diffstat .removed {
|
|
775
|
+
color: var(--thinking-indicator-trace-removed-color, #c93f38);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
.trace-more {
|
|
779
|
+
font-size: var(--thinking-indicator-trace-more-font-size, 0.75rem);
|
|
780
|
+
color: var(--thinking-indicator-trace-more-color, #9a9a9a);
|
|
781
|
+
padding: 6px 0 0 22px;
|
|
782
|
+
animation: thinking-indicator-fade-in 300ms ease both;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
@media (prefers-reduced-motion: reduce) {
|
|
786
|
+
.status-label,
|
|
787
|
+
.trace-row,
|
|
788
|
+
.trace-more {
|
|
789
|
+
animation-duration: 0.001s;
|
|
211
790
|
}
|
|
212
791
|
|
|
213
|
-
|
|
214
|
-
|
|
792
|
+
.expandable,
|
|
793
|
+
.trace-connector,
|
|
794
|
+
.arrow {
|
|
795
|
+
transition-duration: 0.001s;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
.status-label {
|
|
799
|
+
animation: none;
|
|
800
|
+
-webkit-text-fill-color: var(--thinking-indicator-label-color, #858585);
|
|
215
801
|
}
|
|
216
802
|
}
|
|
217
803
|
</style>
|