@poodle64/librarian 2026.10.2 → 2026.10.4
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 +10 -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 +21 -108
- package/dist/components/activity-group/activity-group.svelte.d.ts +3 -1
- package/dist/components/agent-transcript/agent-transcript.svelte +164 -4
- package/dist/components/agent-transcript/agent-transcript.svelte.d.ts +18 -0
- package/dist/components/artefact-pane/artefact-pane.svelte +162 -18
- package/dist/components/artefact-pane/artefact-pane.svelte.d.ts +10 -1
- package/dist/components/conversation/conversation.svelte +83 -6
- package/dist/components/conversation/conversation.svelte.d.ts +15 -0
- package/dist/components/fair-use-notice/fair-use-notice.svelte +1 -1
- package/dist/components/markdown/markdown.d.ts +11 -0
- package/dist/components/markdown/markdown.js +62 -2
- package/dist/components/markdown/markdown.svelte +24 -3
- package/dist/components/markdown/markdown.svelte.d.ts +4 -0
- package/dist/components/scope-statement/scope-statement.svelte +17 -20
- package/dist/components/thinking-row/thinking-row.svelte +1 -0
- package/dist/components/thinking-row/thinking-row.svelte.d.ts +1 -0
- package/dist/components/tool-row/tool-row.svelte +40 -20
- package/dist/components/tool-row/tool-row.svelte.d.ts +2 -3
- package/dist/components/web-list/index.d.ts +2 -0
- package/dist/components/web-list/index.js +2 -0
- package/dist/components/web-list/web-list.svelte +84 -0
- package/dist/components/web-list/web-list.svelte.d.ts +9 -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 +130 -0
- package/dist/components/written-card/written-card.svelte.d.ts +15 -0
- package/dist/copy.d.ts +19 -0
- package/dist/copy.js +13 -0
- package/dist/transcript.svelte.d.ts +15 -34
- package/dist/transcript.svelte.js +52 -55
- package/dist/web-sources.d.ts +18 -0
- package/dist/web-sources.js +58 -0
- package/dist/written.d.ts +46 -0
- package/dist/written.js +41 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -34,11 +34,13 @@ 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: version, Open, downloads
|
|
39
|
+
web-list/ the web pages an answer drew on, apart from its numbered sources
|
|
38
40
|
composer/ the input box: attachments, one collection control, a note, send/stop
|
|
39
41
|
markdown/ sanitised, streaming-safe markdown + highlighting
|
|
40
|
-
activity-group/
|
|
41
|
-
tool-row/ one tool call
|
|
42
|
+
activity-group/ what the persona did, one collapsed row per step
|
|
43
|
+
tool-row/ one tool call, opening on its input and result
|
|
42
44
|
thinking-row/ one thought, or one line of between-tool narration
|
|
43
45
|
working/ the pre-first-token "something is happening" indicator
|
|
44
46
|
```
|
|
@@ -394,10 +396,14 @@ end that way.
|
|
|
394
396
|
|
|
395
397
|
| Concern | How |
|
|
396
398
|
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
397
|
-
| The persona's tools | `describeTool(block) => ToolWords \| undefined`: the row (`verb`, `object`, `detail`)
|
|
399
|
+
| 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
400
|
| 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
401
|
| 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
402
|
| 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 |
|
|
403
|
+
| 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 |
|
|
404
|
+
| Documents it wrote | `artefact` frames and a stored turn's `artefacts` become `turn.written`, carded with Open and downloads. `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 |
|
|
405
|
+
| Web sources | Pages from the turn's supacrawl calls and the answer's Markdown links list under `copy.fromTheWeb`, unnumbered, apart from the library's `[n]` sources |
|
|
406
|
+
| Drawing a fence | `fences={{ chart: Chart }}` on `Conversation` draws a fenced block of that language with the host's component (`source` is its text), once the answer has settled |
|
|
401
407
|
| "Answered without a source" | `copy.notHeld: ''` for a persona that answers from no sources |
|
|
402
408
|
| 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
409
|
|
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,19 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
|
|
2
|
+
What the persona did before it answered, one collapsed row per step, in
|
|
3
|
+
order — the way Claude Code's own panel lists them.
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
never to mention. So there is exactly one row in every state — "Working…"
|
|
8
|
-
while it runs, a count of what was done once it settles — and the steps are
|
|
9
|
-
behind a disclosure for the reader who wants them.
|
|
5
|
+
Each row says what the step did in a reader's words and opens on what went
|
|
6
|
+
in and what came back. A thinking block is a folded row too: "Thinking…"
|
|
7
|
+
while it streams, "Thought" once done.
|
|
10
8
|
|
|
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.
|
|
9
|
+
The persona's between-tool commentary is a step in here as well. It reaches
|
|
10
|
+
the caller as an ordinary `text` block, not a `thinking` one, so `segment()`
|
|
11
|
+
re-homes any text with a tool call still to come into this group; it renders
|
|
12
|
+
as a ThinkingRow beside the tools, folded like the rest.
|
|
15
13
|
-->
|
|
16
14
|
<script lang="ts">
|
|
17
|
-
import
|
|
18
|
-
import {
|
|
15
|
+
import type { ActivityGroup } from '../../transcript.svelte';
|
|
16
|
+
import type { ChatCopy } from '../../copy';
|
|
19
17
|
import ToolRow from '../tool-row/tool-row.svelte';
|
|
20
18
|
import ThinkingRow from '../thinking-row/thinking-row.svelte';
|
|
21
19
|
|
|
@@ -23,111 +21,26 @@
|
|
|
23
21
|
group: ActivityGroup;
|
|
24
22
|
/** True while this group is the live one. */
|
|
25
23
|
live: boolean;
|
|
24
|
+
copy?: Partial<ChatCopy>;
|
|
26
25
|
}
|
|
27
26
|
|
|
28
|
-
let { group, live }: Props = $props();
|
|
29
|
-
let expanded = $state(false);
|
|
30
|
-
|
|
31
|
-
// A live group has nothing to count yet, so counting it reads as "0
|
|
32
|
-
// searches" flickering up to the real number.
|
|
33
|
-
const label = $derived(live ? 'Working…' : summariseActivity(group));
|
|
27
|
+
let { group, live, copy }: Props = $props();
|
|
34
28
|
</script>
|
|
35
29
|
|
|
36
|
-
<div>
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
>
|
|
43
|
-
<span class="ds-lib-activity-chevron" class:is-open={expanded}>
|
|
44
|
-
<ChevronRightIcon size={14} />
|
|
45
|
-
</span>
|
|
46
|
-
{#if live}
|
|
47
|
-
<span class="ds-lib-activity-dot"></span>
|
|
30
|
+
<div class="ds-lib-activity-steps">
|
|
31
|
+
{#each group.steps as step, i (step.block.index)}
|
|
32
|
+
{#if step.block.kind === 'tool'}
|
|
33
|
+
<ToolRow block={step.block} words={step.words} running={live} {copy} />
|
|
34
|
+
{:else}
|
|
35
|
+
<ThinkingRow block={step.block} active={live && i === group.steps.length - 1} />
|
|
48
36
|
{/if}
|
|
49
|
-
|
|
50
|
-
</button>
|
|
51
|
-
{#if expanded}
|
|
52
|
-
<div class="ds-lib-activity-steps">
|
|
53
|
-
{#each group.steps as step (step.block.index)}
|
|
54
|
-
{#if step.block.kind === 'tool'}
|
|
55
|
-
<ToolRow
|
|
56
|
-
block={step.block}
|
|
57
|
-
repeats={step.repeats}
|
|
58
|
-
words={step.words}
|
|
59
|
-
running={live}
|
|
60
|
-
/>
|
|
61
|
-
{:else}
|
|
62
|
-
<ThinkingRow block={step.block} active={false} />
|
|
63
|
-
{/if}
|
|
64
|
-
{/each}
|
|
65
|
-
</div>
|
|
66
|
-
{/if}
|
|
37
|
+
{/each}
|
|
67
38
|
</div>
|
|
68
39
|
|
|
69
40
|
<style>
|
|
70
|
-
.ds-lib-activity-toggle {
|
|
71
|
-
display: flex;
|
|
72
|
-
align-items: center;
|
|
73
|
-
gap: 0.375rem;
|
|
74
|
-
border: 0;
|
|
75
|
-
background: none;
|
|
76
|
-
padding: 0;
|
|
77
|
-
color: var(--ds-color-muted-foreground);
|
|
78
|
-
font: inherit;
|
|
79
|
-
font-size: 0.875rem;
|
|
80
|
-
line-height: 1.25rem;
|
|
81
|
-
cursor: pointer;
|
|
82
|
-
transition: color 150ms ease;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
.ds-lib-activity-toggle:hover {
|
|
86
|
-
color: var(--ds-color-foreground);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
.ds-lib-activity-chevron {
|
|
90
|
-
display: flex;
|
|
91
|
-
flex: none;
|
|
92
|
-
transition: transform 150ms ease;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
.ds-lib-activity-chevron.is-open {
|
|
96
|
-
transform: rotate(90deg);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
.ds-lib-activity-dot {
|
|
100
|
-
width: 0.375rem;
|
|
101
|
-
height: 0.375rem;
|
|
102
|
-
flex: none;
|
|
103
|
-
border-radius: var(--ds-radius-full);
|
|
104
|
-
background: var(--ds-color-status-info);
|
|
105
|
-
animation: ds-lib-activity-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
41
|
.ds-lib-activity-steps {
|
|
109
42
|
display: flex;
|
|
110
43
|
flex-direction: column;
|
|
111
|
-
gap: 0.
|
|
112
|
-
margin-top: 0.5rem;
|
|
113
|
-
margin-inline-start: 0.4375rem;
|
|
114
|
-
border-inline-start: 1px solid var(--ds-color-border);
|
|
115
|
-
padding-inline-start: 0.75rem;
|
|
116
|
-
}
|
|
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
|
-
}
|
|
44
|
+
gap: 0.25rem;
|
|
132
45
|
}
|
|
133
46
|
</style>
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ActivityGroup } from '../../transcript.svelte';
|
|
2
|
+
import type { ChatCopy } from '../../copy';
|
|
2
3
|
interface Props {
|
|
3
4
|
group: ActivityGroup;
|
|
4
5
|
/** True while this group is the live one. */
|
|
5
6
|
live: boolean;
|
|
7
|
+
copy?: Partial<ChatCopy>;
|
|
6
8
|
}
|
|
7
9
|
declare const ActivityGroup: import("svelte").Component<Props, {}, "">;
|
|
8
10
|
type ActivityGroup = ReturnType<typeof ActivityGroup>;
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
<script lang="ts">
|
|
16
16
|
import CheckIcon from '@lucide/svelte/icons/check';
|
|
17
17
|
import CopyIcon from '@lucide/svelte/icons/copy';
|
|
18
|
+
import PencilIcon from '@lucide/svelte/icons/pencil';
|
|
19
|
+
import PaperclipIcon from '@lucide/svelte/icons/paperclip';
|
|
18
20
|
import RefreshCwIcon from '@lucide/svelte/icons/refresh-cw';
|
|
19
21
|
import {
|
|
20
22
|
outcomeLine,
|
|
@@ -34,6 +36,8 @@
|
|
|
34
36
|
type Citation
|
|
35
37
|
} from '../../citations';
|
|
36
38
|
import { DEFAULT_PERSONA, personaName, resolveCopy, type ChatCopy } from '../../copy';
|
|
39
|
+
import { webSources } from '../../web-sources';
|
|
40
|
+
import { writtenKey, type Written, type WrittenUrl } from '../../written';
|
|
37
41
|
import type { Verdict } from '../../chat.svelte';
|
|
38
42
|
import AnswerMark from '../answer-mark/answer-mark.svelte';
|
|
39
43
|
import AnswerShare from '../answer-share/answer-share.svelte';
|
|
@@ -41,7 +45,10 @@
|
|
|
41
45
|
import ActivityGroup from '../activity-group/activity-group.svelte';
|
|
42
46
|
import ArtefactCard from '../artefact-card/artefact-card.svelte';
|
|
43
47
|
import Markdown from '../markdown/markdown.svelte';
|
|
48
|
+
import type { Fences } from '../markdown/markdown';
|
|
44
49
|
import SourceList from '../source-list/source-list.svelte';
|
|
50
|
+
import WebList from '../web-list/web-list.svelte';
|
|
51
|
+
import WrittenCard from '../written-card/written-card.svelte';
|
|
45
52
|
|
|
46
53
|
/** Everything one turn renders from: what `Conversation` hands a host's
|
|
47
54
|
* own `turn` snippet, so it can render this component or its own. */
|
|
@@ -76,6 +83,22 @@
|
|
|
76
83
|
onopenartefact?: () => void;
|
|
77
84
|
/** The persona's own words for its tools, and what they read. */
|
|
78
85
|
describeTool?: DescribeTool;
|
|
86
|
+
/** The names of the files that went with the question, shown as chips. */
|
|
87
|
+
files?: string[];
|
|
88
|
+
/** Replaces this question with `text` and asks it again from here
|
|
89
|
+
* (`Chat.edit`), resolving false when that did not go through. Omit
|
|
90
|
+
* and no edit control renders: not while an answer is being written. */
|
|
91
|
+
onedit?: (text: string) => Promise<boolean>;
|
|
92
|
+
/** The documents the persona wrote answering it, carded under the answer. */
|
|
93
|
+
written?: Written[];
|
|
94
|
+
/** The version the reading column shows now, by `writtenKey`. */
|
|
95
|
+
showingWritten?: string;
|
|
96
|
+
/** Opens a version in the reading column. Omit and the cards only link. */
|
|
97
|
+
onopenwritten?: (written: Written) => void;
|
|
98
|
+
/** Where a version downloads. Omit and no download links render. */
|
|
99
|
+
writtenUrl?: WrittenUrl;
|
|
100
|
+
/** Languages of fenced code the host draws with its own component. */
|
|
101
|
+
fences?: Fences;
|
|
79
102
|
/** When an answer this page did not start began, epoch ms: it is still
|
|
80
103
|
* being written, and the clock counts from then. */
|
|
81
104
|
answering?: number;
|
|
@@ -111,6 +134,13 @@
|
|
|
111
134
|
artefactOpen = false,
|
|
112
135
|
onopenartefact,
|
|
113
136
|
describeTool,
|
|
137
|
+
files = [],
|
|
138
|
+
onedit,
|
|
139
|
+
written = [],
|
|
140
|
+
showingWritten,
|
|
141
|
+
onopenwritten,
|
|
142
|
+
writtenUrl,
|
|
143
|
+
fences,
|
|
114
144
|
answering,
|
|
115
145
|
onmark,
|
|
116
146
|
shared,
|
|
@@ -183,6 +213,28 @@
|
|
|
183
213
|
|
|
184
214
|
const hasAnswer = $derived(texts.length > 0);
|
|
185
215
|
|
|
216
|
+
// Pages the answer drew on, once it has settled: a link still being typed
|
|
217
|
+
// is not a source.
|
|
218
|
+
const web = $derived(running ? [] : webSources(blocks, answer));
|
|
219
|
+
|
|
220
|
+
let editing = $state(false);
|
|
221
|
+
let edited = $state('');
|
|
222
|
+
let sending = $state(false);
|
|
223
|
+
|
|
224
|
+
function startEdit() {
|
|
225
|
+
edited = asked;
|
|
226
|
+
editing = true;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async function sendEdit() {
|
|
230
|
+
const text = edited.trim();
|
|
231
|
+
if (!text || !onedit || sending) return;
|
|
232
|
+
sending = true;
|
|
233
|
+
const sent = await onedit(text);
|
|
234
|
+
sending = false;
|
|
235
|
+
if (sent) editing = false;
|
|
236
|
+
}
|
|
237
|
+
|
|
186
238
|
/**
|
|
187
239
|
* What went wrong, if anything did — in words, whether or not the run gave
|
|
188
240
|
* any.
|
|
@@ -251,12 +303,51 @@
|
|
|
251
303
|
VS Code-style panel: this surface is read by people who arrive with
|
|
252
304
|
those two as their model of what a chat looks like. A session whose
|
|
253
305
|
stream never carried its prompt has no words to put here. -->
|
|
254
|
-
{#if asked}
|
|
306
|
+
{#if asked && editing}
|
|
307
|
+
<form
|
|
308
|
+
class="ds-lib-ask ds-lib-edit"
|
|
309
|
+
onsubmit={(event) => {
|
|
310
|
+
event.preventDefault();
|
|
311
|
+
void sendEdit();
|
|
312
|
+
}}
|
|
313
|
+
>
|
|
314
|
+
<textarea
|
|
315
|
+
class="ds-lib-edit-text"
|
|
316
|
+
rows="3"
|
|
317
|
+
aria-label={words.editQuestion}
|
|
318
|
+
bind:value={edited}
|
|
319
|
+
></textarea>
|
|
320
|
+
{#if files.length > 0}<p class="ds-lib-edit-note">{words.editFilesNote}</p>{/if}
|
|
321
|
+
<div class="ds-lib-edit-actions">
|
|
322
|
+
<button type="button" class="ds-lib-action" onclick={() => (editing = false)}>
|
|
323
|
+
{words.cancel}
|
|
324
|
+
</button>
|
|
325
|
+
<button type="submit" class="ds-lib-action" disabled={!edited.trim() || sending}>
|
|
326
|
+
{words.editSend}
|
|
327
|
+
</button>
|
|
328
|
+
</div>
|
|
329
|
+
</form>
|
|
330
|
+
{:else if asked}
|
|
255
331
|
<div class="ds-lib-ask">
|
|
256
332
|
<div class="ds-lib-bubble">{asked}</div>
|
|
257
|
-
{#if
|
|
258
|
-
<
|
|
333
|
+
{#if files.length > 0}
|
|
334
|
+
<ul class="ds-lib-ask-files">
|
|
335
|
+
{#each files as file, i (i)}
|
|
336
|
+
<li class="ds-lib-ask-file"><PaperclipIcon size={12} />{file}</li>
|
|
337
|
+
{/each}
|
|
338
|
+
</ul>
|
|
259
339
|
{/if}
|
|
340
|
+
<div class="ds-lib-ask-meta">
|
|
341
|
+
{#if onedit && !running}
|
|
342
|
+
<button type="button" class="ds-lib-action" onclick={startEdit}>
|
|
343
|
+
<PencilIcon size={14} />
|
|
344
|
+
<span>{words.editQuestion}</span>
|
|
345
|
+
</button>
|
|
346
|
+
{/if}
|
|
347
|
+
{#if clock}
|
|
348
|
+
<time class="ds-lib-ask-time" datetime={clock.iso}>{clock.text}</time>
|
|
349
|
+
{/if}
|
|
350
|
+
</div>
|
|
260
351
|
</div>
|
|
261
352
|
{/if}
|
|
262
353
|
|
|
@@ -279,7 +370,7 @@
|
|
|
279
370
|
|
|
280
371
|
{#each segments as seg (seg.index)}
|
|
281
372
|
{#if seg.kind === 'activity'}
|
|
282
|
-
<ActivityGroup group={seg} live={running && seg.index === lastIndex} />
|
|
373
|
+
<ActivityGroup group={seg} live={running && seg.index === lastIndex} copy={words} />
|
|
283
374
|
{:else if !isArtefact}
|
|
284
375
|
<Markdown
|
|
285
376
|
content={seg.index === lastTextIndex && split.citations.length > 0
|
|
@@ -287,6 +378,7 @@
|
|
|
287
378
|
: seg.text}
|
|
288
379
|
streaming={running && seg.index === lastIndex}
|
|
289
380
|
{collectionNames}
|
|
381
|
+
{fences}
|
|
290
382
|
citationNumbers={numbers}
|
|
291
383
|
oncite={(n) => {
|
|
292
384
|
const found = sources.find((c) => c.n === n);
|
|
@@ -300,10 +392,24 @@
|
|
|
300
392
|
<ArtefactCard {artefact} open={artefactOpen} onopen={onopenartefact} copy={words} />
|
|
301
393
|
{/if}
|
|
302
394
|
|
|
395
|
+
{#each written as doc (writtenKey(doc))}
|
|
396
|
+
<WrittenCard
|
|
397
|
+
written={doc}
|
|
398
|
+
open={showingWritten === writtenKey(doc)}
|
|
399
|
+
onopen={onopenwritten ? () => onopenwritten(doc) : undefined}
|
|
400
|
+
url={writtenUrl}
|
|
401
|
+
copy={words}
|
|
402
|
+
/>
|
|
403
|
+
{/each}
|
|
404
|
+
|
|
303
405
|
{#if !isArtefact && sources.length > 0}
|
|
304
406
|
<SourceList {sources} {words} {oncite} />
|
|
305
407
|
{/if}
|
|
306
408
|
|
|
409
|
+
{#if !isArtefact && web.length > 0}
|
|
410
|
+
<WebList sources={web} {words} />
|
|
411
|
+
{/if}
|
|
412
|
+
|
|
307
413
|
<!-- An empty `notHeld` is a host whose persona answers from no sources,
|
|
308
414
|
where "without a source" would be a claim about nothing. -->
|
|
309
415
|
{#if unanswered && asked}
|
|
@@ -395,6 +501,60 @@
|
|
|
395
501
|
white-space: pre-wrap;
|
|
396
502
|
}
|
|
397
503
|
|
|
504
|
+
.ds-lib-ask-meta {
|
|
505
|
+
display: flex;
|
|
506
|
+
align-items: center;
|
|
507
|
+
gap: 0.5rem;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
.ds-lib-ask-files {
|
|
511
|
+
display: flex;
|
|
512
|
+
flex-wrap: wrap;
|
|
513
|
+
justify-content: flex-end;
|
|
514
|
+
gap: 0.25rem;
|
|
515
|
+
margin: 0;
|
|
516
|
+
padding: 0;
|
|
517
|
+
list-style: none;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
.ds-lib-ask-file {
|
|
521
|
+
display: flex;
|
|
522
|
+
align-items: center;
|
|
523
|
+
gap: 0.25rem;
|
|
524
|
+
border: 1px solid var(--ds-color-border);
|
|
525
|
+
border-radius: var(--ds-radius-md);
|
|
526
|
+
padding: 0.125rem 0.5rem;
|
|
527
|
+
color: var(--ds-color-muted-foreground);
|
|
528
|
+
font-size: var(--ds-text-2xs);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
.ds-lib-edit {
|
|
532
|
+
align-items: stretch;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.ds-lib-edit-text {
|
|
536
|
+
width: 100%;
|
|
537
|
+
resize: vertical;
|
|
538
|
+
border: 1px solid var(--ds-color-border);
|
|
539
|
+
border-radius: var(--ds-radius-lg);
|
|
540
|
+
background: var(--ds-color-surface-1);
|
|
541
|
+
padding: 0.5rem 0.75rem;
|
|
542
|
+
color: var(--ds-color-foreground);
|
|
543
|
+
font: inherit;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
.ds-lib-edit-note {
|
|
547
|
+
margin: 0;
|
|
548
|
+
color: var(--ds-color-muted-foreground);
|
|
549
|
+
font-size: var(--ds-text-2xs);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
.ds-lib-edit-actions {
|
|
553
|
+
display: flex;
|
|
554
|
+
justify-content: flex-end;
|
|
555
|
+
gap: 0.25rem;
|
|
556
|
+
}
|
|
557
|
+
|
|
398
558
|
.ds-lib-ask-time,
|
|
399
559
|
.ds-lib-answer-time {
|
|
400
560
|
color: var(--ds-color-muted-foreground);
|