@poodle64/librarian 2026.10.3 → 2026.10.5
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 +12 -4
- package/dist/chat.svelte.d.ts +40 -0
- package/dist/chat.svelte.js +57 -3
- package/dist/client.d.ts +3 -0
- package/dist/client.js +4 -1
- package/dist/components/activity-group/activity-group.svelte +72 -77
- package/dist/components/activity-group/activity-group.svelte.d.ts +6 -1
- package/dist/components/agent-transcript/agent-transcript.svelte +225 -9
- package/dist/components/agent-transcript/agent-transcript.svelte.d.ts +19 -0
- package/dist/components/artefact-pane/artefact-pane.svelte +387 -43
- package/dist/components/artefact-pane/artefact-pane.svelte.d.ts +10 -1
- package/dist/components/conversation/conversation.svelte +74 -4
- package/dist/components/conversation/conversation.svelte.d.ts +15 -0
- package/dist/components/markdown/markdown.d.ts +30 -2
- package/dist/components/markdown/markdown.js +142 -5
- package/dist/components/markdown/markdown.svelte +130 -16
- package/dist/components/markdown/markdown.svelte.d.ts +12 -0
- package/dist/components/source-fold/index.d.ts +2 -0
- package/dist/components/source-fold/index.js +2 -0
- package/dist/components/source-fold/source-fold.svelte +225 -0
- package/dist/components/source-fold/source-fold.svelte.d.ts +17 -0
- package/dist/components/source-list/source-list.svelte +5 -3
- package/dist/components/source-list/source-list.svelte.d.ts +2 -0
- package/dist/components/thinking-row/thinking-row.svelte +38 -16
- package/dist/components/thinking-row/thinking-row.svelte.d.ts +3 -0
- package/dist/components/tool-row/tool-row.svelte +161 -49
- package/dist/components/tool-row/tool-row.svelte.d.ts +2 -3
- package/dist/components/visual-block/entity-card.svelte +94 -0
- package/dist/components/visual-block/entity-card.svelte.d.ts +7 -0
- package/dist/components/visual-block/timeline.svelte +99 -0
- package/dist/components/visual-block/timeline.svelte.d.ts +7 -0
- package/dist/components/visual-block/vega-chart.svelte +110 -0
- package/dist/components/visual-block/vega-chart.svelte.d.ts +7 -0
- package/dist/components/visual-block/visual-block.svelte +53 -0
- package/dist/components/visual-block/visual-block.svelte.d.ts +13 -0
- package/dist/components/written-card/index.d.ts +2 -0
- package/dist/components/written-card/index.js +2 -0
- package/dist/components/written-card/written-card.svelte +178 -0
- package/dist/components/written-card/written-card.svelte.d.ts +15 -0
- package/dist/copy.d.ts +73 -0
- package/dist/copy.js +56 -0
- package/dist/diff.d.ts +8 -0
- package/dist/diff.js +37 -0
- package/dist/steps.d.ts +28 -0
- package/dist/steps.js +168 -0
- package/dist/transcript.svelte.d.ts +19 -34
- package/dist/transcript.svelte.js +64 -56
- package/dist/visuals.d.ts +38 -0
- package/dist/visuals.js +82 -0
- package/dist/web-sources.d.ts +34 -0
- package/dist/web-sources.js +90 -0
- package/dist/written.d.ts +71 -0
- package/dist/written.js +83 -0
- package/package.json +14 -1
package/README.md
CHANGED
|
@@ -34,11 +34,14 @@ src/lib/
|
|
|
34
34
|
agent-transcript/ one question and everything the persona did answering it
|
|
35
35
|
document-pane/ the cited document, open at the cited passage
|
|
36
36
|
artefact-card/ an artefact's card, in the host's words
|
|
37
|
-
artefact-pane/ an artefact that is its answer's prose, in the column
|
|
37
|
+
artefact-pane/ an artefact that is its answer's prose, or one version of a written document, in the column
|
|
38
|
+
written-card/ a document the persona wrote: kind, pages, version, Open, and a download chip
|
|
39
|
+
source-fold/ what an answer drew on, folded: the library's numbered sources, then the web pages numbered on from them
|
|
40
|
+
visual-block/ a fenced block of a registered language: its renderer, Drawing… while it arrives, its code when invalid
|
|
38
41
|
composer/ the input box: attachments, one collection control, a note, send/stop
|
|
39
42
|
markdown/ sanitised, streaming-safe markdown + highlighting
|
|
40
|
-
activity-group/
|
|
41
|
-
tool-row/ one tool call
|
|
43
|
+
activity-group/ what the persona did, one collapsed row per step
|
|
44
|
+
tool-row/ one tool call, opening on its input and result
|
|
42
45
|
thinking-row/ one thought, or one line of between-tool narration
|
|
43
46
|
working/ the pre-first-token "something is happening" indicator
|
|
44
47
|
```
|
|
@@ -394,10 +397,15 @@ end that way.
|
|
|
394
397
|
|
|
395
398
|
| Concern | How |
|
|
396
399
|
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
397
|
-
| The persona's tools | `describeTool(block) => ToolWords \| undefined`: the row (`verb`, `object`, `detail`)
|
|
400
|
+
| The persona's tools | `describeTool(block) => ToolWords \| undefined`: the row (`verb`, `object`, `detail`) and what it read (`source`). `undefined` leaves a call to the package's own words |
|
|
398
401
|
| What an answer read | `ToolWords.source` lists under the answer, under `copy.sources`; tapping one calls `oncite`. Nothing checked it, so it carries no trust mark |
|
|
399
402
|
| An artefact | `turn.artefact = { title, summary }` cards under the prose once the turn settles; `isAnswer: true` is an artefact that IS the prose, and replaces it |
|
|
400
403
|
| Opening it | `onopenartefact(turn)` and `showing` (the turn id the host shows). Without a handler, an `isAnswer` artefact opens in `ArtefactPane` and any other offers no action |
|
|
404
|
+
| Editing a question | `Conversation`'s `onedit(position, text)` (`Chat.edit`) offers an edit control on each plain question while nothing is being answered; the turns from there on are dropped and the new question streams in at that position (`ask({ edit })`). Files are not sent again |
|
|
405
|
+
| Documents it wrote | `artefact` frames and a stored turn's `artefacts` (each with `title`, `kind`, `changes`, `pages`) become `turn.written`: the newest version of each is a card with Open and a download chip (Word where `formats` has it, else the source). The pane marks what a version changed since the one before: a "Changed since vN" line from `changes`, and a bar beside each changed block. `loadWritten(name, version)` and `writtenUrl(name, version, format)` (`Chat.loadArtefact` / `Chat.artefactUrl` over the transport) let the pane show a version: Markdown sanitised, HTML only in a `sandbox=""` frame |
|
|
406
|
+
| The turn's steps | One fold, "Read your attachment, searched the library and the web, and wrote a minute · 6 steps", composed from the steps (`stepsSummary`); it says what is happening now while the turn runs, and opens on one row per step (a search row counts what it found, a web search opens on the pages it found) |
|
|
407
|
+
| Web sources | Pages from the turn's supacrawl calls and the answer's Markdown links fold with the library's sources ("6 sources · 4 library · 2 web"), numbered on from the library's, and a link to one in the answer reads as that number's chip |
|
|
408
|
+
| Drawing a fence | `visuals` on `Conversation` is a registry keyed on a fence's language, `{ component, parse }` each; `defaultVisuals` (from `@poodle64/librarian/visuals`) draws `vega-lite` (one Vega-Lite v5 spec, in the app's tokens), `timeline` and `card`. An app passes `{ ...defaultVisuals, openui }`. A block whose closing fence has not arrived is "Drawing…", one that does not parse is its code |
|
|
401
409
|
| "Answered without a source" | `copy.notHeld: ''` for a persona that answers from no sources |
|
|
402
410
|
| Saying something mid-run | `Composer`'s `sendWhileRunning`: while the session works the box stays open and Send sits beside Stop. For a route that reads a message while the run works; a chat leaves it off and waits for its answer |
|
|
403
411
|
|
package/dist/chat.svelte.d.ts
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
import type { AgentEvent } from './client';
|
|
23
23
|
import type { Citation } from './citations';
|
|
24
24
|
import { type Artefact, type Turn } from './transcript.svelte';
|
|
25
|
+
import type { Written } from './written';
|
|
25
26
|
/** One conversation in the list. */
|
|
26
27
|
export interface ConversationSummary {
|
|
27
28
|
/** The agent's own session id: what resumes it, what names it in the address. */
|
|
@@ -47,6 +48,20 @@ export interface StoredTurn {
|
|
|
47
48
|
collection?: string | null;
|
|
48
49
|
/** Where this answer's read-only view lives while it is shared. */
|
|
49
50
|
shared?: string | null;
|
|
51
|
+
/** The names of the files attached to the question. */
|
|
52
|
+
files?: string[];
|
|
53
|
+
/** The document versions written answering it, in order. */
|
|
54
|
+
artefacts?: Written[];
|
|
55
|
+
/** Every tool the agent called, in order, with its (truncated) result. */
|
|
56
|
+
steps?: StoredStep[];
|
|
57
|
+
}
|
|
58
|
+
/** One tool call of a stored turn. */
|
|
59
|
+
export interface StoredStep {
|
|
60
|
+
id: string;
|
|
61
|
+
name: string;
|
|
62
|
+
input: Record<string, unknown>;
|
|
63
|
+
result: string;
|
|
64
|
+
is_error: boolean;
|
|
50
65
|
}
|
|
51
66
|
/** A conversation reopened. */
|
|
52
67
|
export interface ConversationRead extends ConversationSummary {
|
|
@@ -79,6 +94,8 @@ export interface AskRequest {
|
|
|
79
94
|
kind?: string;
|
|
80
95
|
/** The one collection to ask, by name: `client.ask()`'s `collection`. */
|
|
81
96
|
collection?: string;
|
|
97
|
+
/** The position of the question this replaces: `client.ask()`'s `edit`. */
|
|
98
|
+
edit?: number;
|
|
82
99
|
}
|
|
83
100
|
/**
|
|
84
101
|
* A persona's chat routes, as the host's own client reaches them. Every
|
|
@@ -114,6 +131,14 @@ export interface ChatTransport {
|
|
|
114
131
|
* stops sharing it. Resolves to where it is read while shared, null once
|
|
115
132
|
* it is not. */
|
|
116
133
|
share?(id: string, turn: number, share: boolean): Promise<string | null>;
|
|
134
|
+
/** `GET conversations/{id}/artefact`, as `source`: one version of a document
|
|
135
|
+
* the agent wrote. */
|
|
136
|
+
loadArtefact?(id: string, name: string, version: number): Promise<{
|
|
137
|
+
text: string;
|
|
138
|
+
mediaType: string;
|
|
139
|
+
}>;
|
|
140
|
+
/** Where that version downloads as `format` (`source`, or one of its `formats`). */
|
|
141
|
+
artefactUrl?(id: string, name: string, version: number, format: string): string;
|
|
117
142
|
}
|
|
118
143
|
/** A job's routes. */
|
|
119
144
|
export interface JobTransport {
|
|
@@ -188,6 +213,14 @@ export declare class Chat {
|
|
|
188
213
|
/** The last question again, as a new turn: the conversation is append-only,
|
|
189
214
|
* because the agent's transcript is. Same kind, same collection. */
|
|
190
215
|
again(): Promise<boolean>;
|
|
216
|
+
/**
|
|
217
|
+
* Replace the question at `position` (its place among `turns`) with `text`:
|
|
218
|
+
* the turns from there on go from the page and from the server, and the new
|
|
219
|
+
* question streams in at that position. The files that went with the old
|
|
220
|
+
* one are not sent again. Same kind and collection as the one replaced.
|
|
221
|
+
* Resolves like `ask`; a refusal puts the dropped turns back.
|
|
222
|
+
*/
|
|
223
|
+
edit(position: number, text: string): Promise<boolean>;
|
|
191
224
|
/** Stop the answer being written, wherever it is being written. */
|
|
192
225
|
stop(): Promise<void>;
|
|
193
226
|
/** Read today's allowance again. One that cannot be read is left as it was. */
|
|
@@ -207,6 +240,13 @@ export declare class Chat {
|
|
|
207
240
|
/** Share an answer the agent holds, or stop sharing it. False when that
|
|
208
241
|
* was not recorded. */
|
|
209
242
|
share(turn: Turn, share: boolean): Promise<boolean>;
|
|
243
|
+
/** One version of a document the agent wrote in the open conversation. */
|
|
244
|
+
loadArtefact(name: string, version: number): Promise<{
|
|
245
|
+
text: string;
|
|
246
|
+
mediaType: string;
|
|
247
|
+
}>;
|
|
248
|
+
/** Where that version downloads as `format`; empty with no conversation open. */
|
|
249
|
+
artefactUrl(name: string, version: number, format: string): string;
|
|
210
250
|
}
|
|
211
251
|
/** A turn as read back, ready for `Conversation`: given one and no handlers,
|
|
212
252
|
* that is the read-only view of a shared answer. */
|
package/dist/chat.svelte.js
CHANGED
|
@@ -172,6 +172,33 @@ export class Chat {
|
|
|
172
172
|
? this.ask(last.question, last.kind, last.collection)
|
|
173
173
|
: Promise.resolve(false);
|
|
174
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Replace the question at `position` (its place among `turns`) with `text`:
|
|
177
|
+
* the turns from there on go from the page and from the server, and the new
|
|
178
|
+
* question streams in at that position. The files that went with the old
|
|
179
|
+
* one are not sent again. Same kind and collection as the one replaced.
|
|
180
|
+
* Resolves like `ask`; a refusal puts the dropped turns back.
|
|
181
|
+
*/
|
|
182
|
+
async edit(position, text) {
|
|
183
|
+
const body = text.trim();
|
|
184
|
+
const resume = this.conversationId;
|
|
185
|
+
const was = this.#turns[position];
|
|
186
|
+
if (!body || !resume || !was || this.#job || this.busy || this.quota?.reached)
|
|
187
|
+
return false;
|
|
188
|
+
const removed = this.#turns.splice(position);
|
|
189
|
+
const held = removed.filter((t) => this.#held.delete(t.id)).map((t) => t.id);
|
|
190
|
+
this.#version += 1;
|
|
191
|
+
const asked = await this.#ask(body, [], false, was.kind, was.collection, position);
|
|
192
|
+
// Refused before the server dropped anything: the page is as it was left.
|
|
193
|
+
// A conversation found already being answered was shown whole instead.
|
|
194
|
+
if (!asked && this.#turns.length === position) {
|
|
195
|
+
this.#turns.push(...removed);
|
|
196
|
+
for (const id of held)
|
|
197
|
+
this.#held.add(id);
|
|
198
|
+
this.#version += 1;
|
|
199
|
+
}
|
|
200
|
+
return asked;
|
|
201
|
+
}
|
|
175
202
|
/** Stop the answer being written, wherever it is being written. */
|
|
176
203
|
async stop() {
|
|
177
204
|
const id = this.conversationId;
|
|
@@ -318,7 +345,7 @@ export class Chat {
|
|
|
318
345
|
this.#running = false;
|
|
319
346
|
this.answering = null;
|
|
320
347
|
}
|
|
321
|
-
async #ask(text, files, typed, kind, collection) {
|
|
348
|
+
async #ask(text, files, typed, kind, collection, edit) {
|
|
322
349
|
const chat = this.#chat;
|
|
323
350
|
const mine = this.#generation;
|
|
324
351
|
const stream = { controller: new AbortController(), stopWanted: false };
|
|
@@ -330,6 +357,7 @@ export class Chat {
|
|
|
330
357
|
blocks: [],
|
|
331
358
|
outcome: null,
|
|
332
359
|
citations: [],
|
|
360
|
+
files: files.map((f) => f.name),
|
|
333
361
|
kind,
|
|
334
362
|
collection,
|
|
335
363
|
artefact: this.#card(kind)
|
|
@@ -346,7 +374,8 @@ export class Chat {
|
|
|
346
374
|
resume: this.conversationId,
|
|
347
375
|
signal: stream.controller.signal,
|
|
348
376
|
kind,
|
|
349
|
-
collection
|
|
377
|
+
collection,
|
|
378
|
+
edit
|
|
350
379
|
})) {
|
|
351
380
|
const named = event.type === 'system' && event.subtype === 'init' ? event.session_id : undefined;
|
|
352
381
|
if (stream.stopWanted) {
|
|
@@ -415,6 +444,18 @@ export class Chat {
|
|
|
415
444
|
this.#finish();
|
|
416
445
|
return true;
|
|
417
446
|
}
|
|
447
|
+
/** One version of a document the agent wrote in the open conversation. */
|
|
448
|
+
loadArtefact(name, version) {
|
|
449
|
+
const id = this.conversationId;
|
|
450
|
+
if (!id || !this.#chat?.loadArtefact)
|
|
451
|
+
return Promise.reject(new Error('no artefact'));
|
|
452
|
+
return this.#chat.loadArtefact(id, name, version);
|
|
453
|
+
}
|
|
454
|
+
/** Where that version downloads as `format`; empty with no conversation open. */
|
|
455
|
+
artefactUrl(name, version, format) {
|
|
456
|
+
const id = this.conversationId;
|
|
457
|
+
return id && this.#chat?.artefactUrl ? this.#chat.artefactUrl(id, name, version, format) : '';
|
|
458
|
+
}
|
|
418
459
|
/** What a turn of `kind` opens, if the host says it opens anything. */
|
|
419
460
|
#card(kind) {
|
|
420
461
|
return kind ? this.#artefact?.(kind) : undefined;
|
|
@@ -547,13 +588,26 @@ export class Chat {
|
|
|
547
588
|
/** A turn as read back, ready for `Conversation`: given one and no handlers,
|
|
548
589
|
* that is the read-only view of a shared answer. */
|
|
549
590
|
export function storedTurn(conversation, index, stored) {
|
|
591
|
+
const blocks = (stored.steps ?? []).map((step, at) => ({
|
|
592
|
+
kind: 'tool',
|
|
593
|
+
index: at,
|
|
594
|
+
id: step.id,
|
|
595
|
+
name: step.name,
|
|
596
|
+
rawInput: JSON.stringify(step.input ?? {}),
|
|
597
|
+
result: step.result,
|
|
598
|
+
isError: step.is_error
|
|
599
|
+
}));
|
|
600
|
+
if (stored.answer)
|
|
601
|
+
blocks.push({ kind: 'text', index: blocks.length, text: stored.answer });
|
|
550
602
|
return {
|
|
551
603
|
id: `${conversation}:${index}`,
|
|
552
604
|
question: stored.question,
|
|
553
605
|
at: epoch(stored.at) ?? undefined,
|
|
554
|
-
blocks
|
|
606
|
+
blocks,
|
|
555
607
|
outcome: null,
|
|
556
608
|
citations: stored.citations ?? [],
|
|
609
|
+
files: stored.files ?? [],
|
|
610
|
+
written: stored.artefacts ?? [],
|
|
557
611
|
kind: stored.kind ?? undefined,
|
|
558
612
|
collection: stored.collection ?? undefined,
|
|
559
613
|
shared: stored.shared ?? undefined
|
package/dist/client.d.ts
CHANGED
|
@@ -71,6 +71,9 @@ export interface AskOptions {
|
|
|
71
71
|
/** The one collection this question is asked of, by name. Absent asks
|
|
72
72
|
* across every collection the person may read. */
|
|
73
73
|
collection?: string;
|
|
74
|
+
/** The position (0-based) of the person's question this one replaces: the
|
|
75
|
+
* server drops that turn and every one after it. Needs `resume`. */
|
|
76
|
+
edit?: number;
|
|
74
77
|
/** Files the persona reads for this question. Switches the request to multipart. */
|
|
75
78
|
files?: File[];
|
|
76
79
|
/** What kind of turn this ask should become. Absent means an ordinary
|
package/dist/client.js
CHANGED
|
@@ -29,7 +29,8 @@ function requestInit(options, signal) {
|
|
|
29
29
|
question: options.question,
|
|
30
30
|
resume: options.resume ?? null,
|
|
31
31
|
collection: options.collection,
|
|
32
|
-
kind: options.kind
|
|
32
|
+
kind: options.kind,
|
|
33
|
+
edit: options.edit
|
|
33
34
|
}),
|
|
34
35
|
signal
|
|
35
36
|
};
|
|
@@ -44,6 +45,8 @@ function requestInit(options, signal) {
|
|
|
44
45
|
form.append('files[]', file, file.name);
|
|
45
46
|
if (options.kind)
|
|
46
47
|
form.append('kind', options.kind);
|
|
48
|
+
if (options.edit !== undefined)
|
|
49
|
+
form.append('edit', String(options.edit));
|
|
47
50
|
return { method: 'POST', credentials: 'include', body: form, signal };
|
|
48
51
|
}
|
|
49
52
|
/**
|
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
|
|
2
|
+
What the persona did before it answered, folded behind one line.
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
behind a disclosure for the reader who wants them.
|
|
4
|
+
The line is a sentence composed from the steps ("Read your attachment,
|
|
5
|
+
searched the library and the web, and wrote a minute · 6 steps"), and while
|
|
6
|
+
the turn runs it says what is happening now. It opens on one row per step,
|
|
7
|
+
in order, the way Claude Code's own panel lists them: each says what the step
|
|
8
|
+
did in a reader's words and opens on what went in and what came back.
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
`text` block, not a `thinking` one, so `segment()`
|
|
13
|
-
tool call still to come into this group; it renders
|
|
14
|
-
the tools, folded like the rest.
|
|
10
|
+
The persona's between-tool commentary is a step in here as well. It reaches
|
|
11
|
+
the caller as an ordinary `text` block, not a `thinking` one, so `segment()`
|
|
12
|
+
re-homes any text with a tool call still to come into this group; it renders
|
|
13
|
+
as a ThinkingRow beside the tools, folded like the rest.
|
|
15
14
|
-->
|
|
16
15
|
<script lang="ts">
|
|
17
|
-
import
|
|
18
|
-
import
|
|
16
|
+
import ChevronDownIcon from '@lucide/svelte/icons/chevron-down';
|
|
17
|
+
import ChevronUpIcon from '@lucide/svelte/icons/chevron-up';
|
|
18
|
+
import type { ActivityGroup } from '../../transcript.svelte';
|
|
19
|
+
import { resolveCopy, type ChatCopy } from '../../copy';
|
|
20
|
+
import { stepsNow, stepsSummary } from '../../steps';
|
|
21
|
+
import type { Written } from '../../written';
|
|
19
22
|
import ToolRow from '../tool-row/tool-row.svelte';
|
|
20
23
|
import ThinkingRow from '../thinking-row/thinking-row.svelte';
|
|
21
24
|
|
|
@@ -23,43 +26,45 @@
|
|
|
23
26
|
group: ActivityGroup;
|
|
24
27
|
/** True while this group is the live one. */
|
|
25
28
|
live: boolean;
|
|
29
|
+
/** The documents the turn wrote: a step that wrote one says its kind. */
|
|
30
|
+
written?: Written[];
|
|
31
|
+
copy?: Partial<ChatCopy>;
|
|
26
32
|
}
|
|
27
33
|
|
|
28
|
-
let { group, live }: Props = $props();
|
|
29
|
-
let
|
|
34
|
+
let { group, live, written = [], copy }: Props = $props();
|
|
35
|
+
let open = $state(false);
|
|
30
36
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
37
|
+
const words = $derived(resolveCopy(copy));
|
|
38
|
+
const summary = $derived(stepsSummary(group.steps, written, words));
|
|
39
|
+
const now = $derived(stepsNow(group.steps, written, words));
|
|
34
40
|
</script>
|
|
35
41
|
|
|
36
|
-
<div>
|
|
42
|
+
<div class="ds-lib-steps">
|
|
37
43
|
<button
|
|
38
44
|
type="button"
|
|
39
|
-
class="ds-lib-
|
|
40
|
-
onclick={() => (
|
|
41
|
-
aria-expanded={
|
|
45
|
+
class="ds-lib-steps-head"
|
|
46
|
+
onclick={() => (open = !open)}
|
|
47
|
+
aria-expanded={open}
|
|
42
48
|
>
|
|
43
|
-
<span class="ds-lib-
|
|
44
|
-
|
|
49
|
+
<span class="ds-lib-steps-line">
|
|
50
|
+
{#if live}
|
|
51
|
+
<span class="ds-lib-steps-now">{now}</span>
|
|
52
|
+
{:else}
|
|
53
|
+
{summary.sentence}<span class="ds-lib-steps-trail">{` · ${summary.trail}`}</span>
|
|
54
|
+
{/if}
|
|
55
|
+
</span>
|
|
56
|
+
<span class="ds-lib-steps-chevron" aria-hidden="true">
|
|
57
|
+
{#if open}<ChevronUpIcon size={15} />{:else}<ChevronDownIcon size={15} />{/if}
|
|
45
58
|
</span>
|
|
46
|
-
{#if live}
|
|
47
|
-
<span class="ds-lib-activity-dot"></span>
|
|
48
|
-
{/if}
|
|
49
|
-
<span>{label}</span>
|
|
50
59
|
</button>
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
60
|
+
|
|
61
|
+
{#if open}
|
|
62
|
+
<div class="ds-lib-steps-rows">
|
|
63
|
+
{#each group.steps as step, i (step.block.index)}
|
|
54
64
|
{#if step.block.kind === 'tool'}
|
|
55
|
-
<ToolRow
|
|
56
|
-
block={step.block}
|
|
57
|
-
repeats={step.repeats}
|
|
58
|
-
words={step.words}
|
|
59
|
-
running={live}
|
|
60
|
-
/>
|
|
65
|
+
<ToolRow block={step.block} words={step.words} running={live} {copy} />
|
|
61
66
|
{:else}
|
|
62
|
-
<ThinkingRow block={step.block} active={
|
|
67
|
+
<ThinkingRow block={step.block} active={live && i === group.steps.length - 1} {copy} />
|
|
63
68
|
{/if}
|
|
64
69
|
{/each}
|
|
65
70
|
</div>
|
|
@@ -67,67 +72,57 @@
|
|
|
67
72
|
</div>
|
|
68
73
|
|
|
69
74
|
<style>
|
|
70
|
-
.ds-lib-
|
|
75
|
+
.ds-lib-steps-head {
|
|
71
76
|
display: flex;
|
|
72
|
-
|
|
73
|
-
|
|
77
|
+
width: 100%;
|
|
78
|
+
align-items: flex-start;
|
|
79
|
+
gap: 0.5rem;
|
|
74
80
|
border: 0;
|
|
75
81
|
background: none;
|
|
76
|
-
padding: 0;
|
|
82
|
+
padding: 0.25rem 0;
|
|
77
83
|
color: var(--ds-color-muted-foreground);
|
|
78
84
|
font: inherit;
|
|
79
85
|
font-size: 0.875rem;
|
|
80
|
-
line-height: 1.
|
|
86
|
+
line-height: 1.375rem;
|
|
87
|
+
text-align: start;
|
|
81
88
|
cursor: pointer;
|
|
82
|
-
transition: color 150ms ease;
|
|
83
89
|
}
|
|
84
90
|
|
|
85
|
-
.ds-lib-
|
|
91
|
+
.ds-lib-steps-head:hover {
|
|
86
92
|
color: var(--ds-color-foreground);
|
|
87
93
|
}
|
|
88
94
|
|
|
89
|
-
.ds-lib-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
transition: transform 150ms ease;
|
|
95
|
+
.ds-lib-steps-head:focus-visible {
|
|
96
|
+
outline: 2px solid var(--ds-color-ring);
|
|
97
|
+
outline-offset: 2px;
|
|
93
98
|
}
|
|
94
99
|
|
|
95
|
-
.ds-lib-
|
|
96
|
-
|
|
100
|
+
.ds-lib-steps-line {
|
|
101
|
+
flex: 1;
|
|
97
102
|
}
|
|
98
103
|
|
|
99
|
-
.ds-lib-
|
|
100
|
-
|
|
101
|
-
|
|
104
|
+
.ds-lib-steps-now {
|
|
105
|
+
color: var(--ds-color-foreground);
|
|
106
|
+
font-weight: 500;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.ds-lib-steps-trail {
|
|
110
|
+
font-size: var(--ds-text-2xs);
|
|
111
|
+
opacity: 0.8;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.ds-lib-steps-chevron {
|
|
115
|
+
display: flex;
|
|
102
116
|
flex: none;
|
|
103
|
-
|
|
104
|
-
background: var(--ds-color-status-info);
|
|
105
|
-
animation: ds-lib-activity-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
|
117
|
+
padding-top: 0.25rem;
|
|
106
118
|
}
|
|
107
119
|
|
|
108
|
-
.ds-lib-
|
|
120
|
+
.ds-lib-steps-rows {
|
|
109
121
|
display: flex;
|
|
110
122
|
flex-direction: column;
|
|
111
|
-
gap: 0.
|
|
112
|
-
margin-top: 0.
|
|
113
|
-
margin-inline-start: 0.4375rem;
|
|
123
|
+
gap: 0.125rem;
|
|
124
|
+
margin-top: 0.25rem;
|
|
114
125
|
border-inline-start: 1px solid var(--ds-color-border);
|
|
115
126
|
padding-inline-start: 0.75rem;
|
|
116
127
|
}
|
|
117
|
-
|
|
118
|
-
@keyframes ds-lib-activity-pulse {
|
|
119
|
-
50% {
|
|
120
|
-
opacity: 0.4;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
@media (prefers-reduced-motion: reduce) {
|
|
125
|
-
.ds-lib-activity-dot {
|
|
126
|
-
animation: none;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
.ds-lib-activity-chevron {
|
|
130
|
-
transition: none;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
128
|
</style>
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ActivityGroup } from '../../transcript.svelte';
|
|
2
|
+
import { type ChatCopy } from '../../copy';
|
|
3
|
+
import type { Written } from '../../written';
|
|
2
4
|
interface Props {
|
|
3
5
|
group: ActivityGroup;
|
|
4
6
|
/** True while this group is the live one. */
|
|
5
7
|
live: boolean;
|
|
8
|
+
/** The documents the turn wrote: a step that wrote one says its kind. */
|
|
9
|
+
written?: Written[];
|
|
10
|
+
copy?: Partial<ChatCopy>;
|
|
6
11
|
}
|
|
7
12
|
declare const ActivityGroup: import("svelte").Component<Props, {}, "">;
|
|
8
13
|
type ActivityGroup = ReturnType<typeof ActivityGroup>;
|