@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
|
@@ -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 FileTextIcon from '@lucide/svelte/icons/file-text';
|
|
18
20
|
import RefreshCwIcon from '@lucide/svelte/icons/refresh-cw';
|
|
19
21
|
import {
|
|
20
22
|
outcomeLine,
|
|
@@ -33,7 +35,10 @@
|
|
|
33
35
|
splitSources,
|
|
34
36
|
type Citation
|
|
35
37
|
} from '../../citations';
|
|
36
|
-
import { DEFAULT_PERSONA, personaName, resolveCopy, type ChatCopy } from '../../copy';
|
|
38
|
+
import { DEFAULT_PERSONA, fill, personaName, resolveCopy, type ChatCopy } from '../../copy';
|
|
39
|
+
import { webKey, webSources } from '../../web-sources';
|
|
40
|
+
import { newestPerName, writtenKey, type Written, type WrittenUrl } from '../../written';
|
|
41
|
+
import type { Visual } from '../../visuals';
|
|
37
42
|
import type { Verdict } from '../../chat.svelte';
|
|
38
43
|
import AnswerMark from '../answer-mark/answer-mark.svelte';
|
|
39
44
|
import AnswerShare from '../answer-share/answer-share.svelte';
|
|
@@ -41,7 +46,8 @@
|
|
|
41
46
|
import ActivityGroup from '../activity-group/activity-group.svelte';
|
|
42
47
|
import ArtefactCard from '../artefact-card/artefact-card.svelte';
|
|
43
48
|
import Markdown from '../markdown/markdown.svelte';
|
|
44
|
-
import
|
|
49
|
+
import SourceFold from '../source-fold/source-fold.svelte';
|
|
50
|
+
import WrittenCard from '../written-card/written-card.svelte';
|
|
45
51
|
|
|
46
52
|
/** Everything one turn renders from: what `Conversation` hands a host's
|
|
47
53
|
* own `turn` snippet, so it can render this component or its own. */
|
|
@@ -76,6 +82,23 @@
|
|
|
76
82
|
onopenartefact?: () => void;
|
|
77
83
|
/** The persona's own words for its tools, and what they read. */
|
|
78
84
|
describeTool?: DescribeTool;
|
|
85
|
+
/** The names of the files that went with the question, shown as chips. */
|
|
86
|
+
files?: string[];
|
|
87
|
+
/** Replaces this question with `text` and asks it again from here
|
|
88
|
+
* (`Chat.edit`), resolving false when that did not go through. Omit
|
|
89
|
+
* and no edit control renders: not while an answer is being written. */
|
|
90
|
+
onedit?: (text: string) => Promise<boolean>;
|
|
91
|
+
/** The documents the persona wrote answering it, carded under the answer. */
|
|
92
|
+
written?: Written[];
|
|
93
|
+
/** The version the reading column shows now, by `writtenKey`. */
|
|
94
|
+
showingWritten?: string;
|
|
95
|
+
/** Opens a version in the reading column. Omit and the cards only link. */
|
|
96
|
+
onopenwritten?: (written: Written) => void;
|
|
97
|
+
/** Where a version downloads. Omit and no download links render. */
|
|
98
|
+
writtenUrl?: WrittenUrl;
|
|
99
|
+
/** Renderers by a fence's language: a block of one is drawn by its
|
|
100
|
+
* component in the answer. Absent, every fence is code. */
|
|
101
|
+
visuals?: Record<string, Visual>;
|
|
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
|
+
visuals,
|
|
114
144
|
answering,
|
|
115
145
|
onmark,
|
|
116
146
|
shared,
|
|
@@ -183,6 +213,51 @@
|
|
|
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. They are numbered on from the library's, and a link to
|
|
218
|
+
// one reads as the same chip.
|
|
219
|
+
const web = $derived.by(() => {
|
|
220
|
+
if (running) return [];
|
|
221
|
+
const from = Math.max(0, ...sources.map((c) => c.n));
|
|
222
|
+
return webSources(blocks, answer).map((page, i) => ({ ...page, n: from + i + 1 }));
|
|
223
|
+
});
|
|
224
|
+
const webNumbers = $derived(new Map(web.map((page) => [webKey(page.url), page.n])));
|
|
225
|
+
|
|
226
|
+
// Which day the pages were read: today only for a turn asked today.
|
|
227
|
+
const readWhen = $derived.by(() => {
|
|
228
|
+
if (at === undefined) return '';
|
|
229
|
+
const when = new Date(at);
|
|
230
|
+
if (new Date().toDateString() === when.toDateString()) return words.readToday;
|
|
231
|
+
return fill(words.readOn, {
|
|
232
|
+
date: when.toLocaleDateString(undefined, { day: 'numeric', month: 'short', year: 'numeric' })
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const cards = $derived(newestPerName(written));
|
|
237
|
+
|
|
238
|
+
const fileType = (file: string): string => {
|
|
239
|
+
const ext = /\.([A-Za-z0-9]{1,5})$/.exec(file)?.[1];
|
|
240
|
+
return ext ? ext.toUpperCase() : '';
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
let editing = $state(false);
|
|
244
|
+
let edited = $state('');
|
|
245
|
+
let sending = $state(false);
|
|
246
|
+
|
|
247
|
+
function startEdit() {
|
|
248
|
+
edited = asked;
|
|
249
|
+
editing = true;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function sendEdit() {
|
|
253
|
+
const text = edited.trim();
|
|
254
|
+
if (!text || !onedit || sending) return;
|
|
255
|
+
sending = true;
|
|
256
|
+
const sent = await onedit(text);
|
|
257
|
+
sending = false;
|
|
258
|
+
if (sent) editing = false;
|
|
259
|
+
}
|
|
260
|
+
|
|
186
261
|
/**
|
|
187
262
|
* What went wrong, if anything did — in words, whether or not the run gave
|
|
188
263
|
* any.
|
|
@@ -251,12 +326,57 @@
|
|
|
251
326
|
VS Code-style panel: this surface is read by people who arrive with
|
|
252
327
|
those two as their model of what a chat looks like. A session whose
|
|
253
328
|
stream never carried its prompt has no words to put here. -->
|
|
254
|
-
{#if asked}
|
|
329
|
+
{#if asked && editing}
|
|
330
|
+
<form
|
|
331
|
+
class="ds-lib-ask ds-lib-edit"
|
|
332
|
+
onsubmit={(event) => {
|
|
333
|
+
event.preventDefault();
|
|
334
|
+
void sendEdit();
|
|
335
|
+
}}
|
|
336
|
+
>
|
|
337
|
+
<textarea
|
|
338
|
+
class="ds-lib-edit-text"
|
|
339
|
+
rows="3"
|
|
340
|
+
aria-label={words.editQuestion}
|
|
341
|
+
bind:value={edited}
|
|
342
|
+
></textarea>
|
|
343
|
+
{#if files.length > 0}<p class="ds-lib-edit-note">{words.editFilesNote}</p>{/if}
|
|
344
|
+
<div class="ds-lib-edit-actions">
|
|
345
|
+
<button type="button" class="ds-lib-action" onclick={() => (editing = false)}>
|
|
346
|
+
{words.cancel}
|
|
347
|
+
</button>
|
|
348
|
+
<button type="submit" class="ds-lib-action" disabled={!edited.trim() || sending}>
|
|
349
|
+
{words.editSend}
|
|
350
|
+
</button>
|
|
351
|
+
</div>
|
|
352
|
+
</form>
|
|
353
|
+
{:else if asked}
|
|
255
354
|
<div class="ds-lib-ask">
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
355
|
+
{#if files.length > 0}
|
|
356
|
+
<ul class="ds-lib-ask-files">
|
|
357
|
+
{#each files as file, i (i)}
|
|
358
|
+
<li class="ds-lib-ask-file">
|
|
359
|
+
<span class="ds-lib-ask-file-icon" aria-hidden="true"><FileTextIcon size={16} /></span>
|
|
360
|
+
<span class="ds-lib-ask-file-text">
|
|
361
|
+
<span class="ds-lib-ask-file-name">{file}</span>
|
|
362
|
+
{#if fileType(file)}<span class="ds-lib-ask-file-type">{fileType(file)}</span>{/if}
|
|
363
|
+
</span>
|
|
364
|
+
</li>
|
|
365
|
+
{/each}
|
|
366
|
+
</ul>
|
|
259
367
|
{/if}
|
|
368
|
+
<div class="ds-lib-bubble">{asked}</div>
|
|
369
|
+
<div class="ds-lib-ask-meta">
|
|
370
|
+
{#if onedit && !running}
|
|
371
|
+
<button type="button" class="ds-lib-action" onclick={startEdit}>
|
|
372
|
+
<PencilIcon size={14} />
|
|
373
|
+
<span>{words.editQuestion}</span>
|
|
374
|
+
</button>
|
|
375
|
+
{/if}
|
|
376
|
+
{#if clock}
|
|
377
|
+
<time class="ds-lib-ask-time" datetime={clock.iso}>{clock.text}</time>
|
|
378
|
+
{/if}
|
|
379
|
+
</div>
|
|
260
380
|
</div>
|
|
261
381
|
{/if}
|
|
262
382
|
|
|
@@ -279,7 +399,12 @@
|
|
|
279
399
|
|
|
280
400
|
{#each segments as seg (seg.index)}
|
|
281
401
|
{#if seg.kind === 'activity'}
|
|
282
|
-
<ActivityGroup
|
|
402
|
+
<ActivityGroup
|
|
403
|
+
group={seg}
|
|
404
|
+
live={running && seg.index === lastIndex}
|
|
405
|
+
{written}
|
|
406
|
+
copy={words}
|
|
407
|
+
/>
|
|
283
408
|
{:else if !isArtefact}
|
|
284
409
|
<Markdown
|
|
285
410
|
content={seg.index === lastTextIndex && split.citations.length > 0
|
|
@@ -287,7 +412,10 @@
|
|
|
287
412
|
: seg.text}
|
|
288
413
|
streaming={running && seg.index === lastIndex}
|
|
289
414
|
{collectionNames}
|
|
415
|
+
{visuals}
|
|
416
|
+
copy={words}
|
|
290
417
|
citationNumbers={numbers}
|
|
418
|
+
{webNumbers}
|
|
291
419
|
oncite={(n) => {
|
|
292
420
|
const found = sources.find((c) => c.n === n);
|
|
293
421
|
if (found) oncite?.(found);
|
|
@@ -300,8 +428,18 @@
|
|
|
300
428
|
<ArtefactCard {artefact} open={artefactOpen} onopen={onopenartefact} copy={words} />
|
|
301
429
|
{/if}
|
|
302
430
|
|
|
303
|
-
{#
|
|
304
|
-
<
|
|
431
|
+
{#each cards as doc (writtenKey(doc))}
|
|
432
|
+
<WrittenCard
|
|
433
|
+
written={doc}
|
|
434
|
+
open={showingWritten === writtenKey(doc)}
|
|
435
|
+
onopen={onopenwritten ? () => onopenwritten(doc) : undefined}
|
|
436
|
+
url={writtenUrl}
|
|
437
|
+
copy={words}
|
|
438
|
+
/>
|
|
439
|
+
{/each}
|
|
440
|
+
|
|
441
|
+
{#if !isArtefact && (sources.length > 0 || web.length > 0)}
|
|
442
|
+
<SourceFold library={sources} {web} {words} {oncite} {readWhen} />
|
|
305
443
|
{/if}
|
|
306
444
|
|
|
307
445
|
<!-- An empty `notHeld` is a host whose persona answers from no sources,
|
|
@@ -395,6 +533,84 @@
|
|
|
395
533
|
white-space: pre-wrap;
|
|
396
534
|
}
|
|
397
535
|
|
|
536
|
+
.ds-lib-ask-meta {
|
|
537
|
+
display: flex;
|
|
538
|
+
align-items: center;
|
|
539
|
+
gap: 0.5rem;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
.ds-lib-ask-files {
|
|
543
|
+
display: flex;
|
|
544
|
+
flex-wrap: wrap;
|
|
545
|
+
justify-content: flex-end;
|
|
546
|
+
gap: 0.25rem;
|
|
547
|
+
margin: 0;
|
|
548
|
+
padding: 0;
|
|
549
|
+
list-style: none;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
.ds-lib-ask-file {
|
|
553
|
+
display: flex;
|
|
554
|
+
max-width: 100%;
|
|
555
|
+
align-items: center;
|
|
556
|
+
gap: 0.625rem;
|
|
557
|
+
border: 1px solid var(--ds-color-border);
|
|
558
|
+
border-radius: var(--ds-radius-full);
|
|
559
|
+
padding: 0.375rem 0.875rem 0.375rem 0.5rem;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
.ds-lib-ask-file-icon {
|
|
563
|
+
display: flex;
|
|
564
|
+
flex: none;
|
|
565
|
+
color: var(--ds-color-muted-foreground);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
.ds-lib-ask-file-text {
|
|
569
|
+
display: flex;
|
|
570
|
+
min-width: 0;
|
|
571
|
+
flex-direction: column;
|
|
572
|
+
font-size: var(--ds-text-2xs);
|
|
573
|
+
line-height: 1.25;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
.ds-lib-ask-file-name {
|
|
577
|
+
color: var(--ds-color-foreground);
|
|
578
|
+
font-size: 0.8125rem;
|
|
579
|
+
font-weight: 600;
|
|
580
|
+
overflow-wrap: anywhere;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
.ds-lib-ask-file-type {
|
|
584
|
+
color: var(--ds-color-muted-foreground);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
.ds-lib-edit {
|
|
588
|
+
align-items: stretch;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
.ds-lib-edit-text {
|
|
592
|
+
width: 100%;
|
|
593
|
+
resize: vertical;
|
|
594
|
+
border: 1px solid var(--ds-color-border);
|
|
595
|
+
border-radius: var(--ds-radius-lg);
|
|
596
|
+
background: var(--ds-color-surface-1);
|
|
597
|
+
padding: 0.5rem 0.75rem;
|
|
598
|
+
color: var(--ds-color-foreground);
|
|
599
|
+
font: inherit;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
.ds-lib-edit-note {
|
|
603
|
+
margin: 0;
|
|
604
|
+
color: var(--ds-color-muted-foreground);
|
|
605
|
+
font-size: var(--ds-text-2xs);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
.ds-lib-edit-actions {
|
|
609
|
+
display: flex;
|
|
610
|
+
justify-content: flex-end;
|
|
611
|
+
gap: 0.25rem;
|
|
612
|
+
}
|
|
613
|
+
|
|
398
614
|
.ds-lib-ask-time,
|
|
399
615
|
.ds-lib-answer-time {
|
|
400
616
|
color: var(--ds-color-muted-foreground);
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type Artefact, type Block, type DescribeTool, type Outcome } from '../../transcript.svelte';
|
|
2
2
|
import { type Citation } from '../../citations';
|
|
3
3
|
import { type ChatCopy } from '../../copy';
|
|
4
|
+
import { type Written, type WrittenUrl } from '../../written';
|
|
5
|
+
import type { Visual } from '../../visuals';
|
|
4
6
|
import type { Verdict } from '../../chat.svelte';
|
|
5
7
|
/** Everything one turn renders from: what `Conversation` hands a host's
|
|
6
8
|
* own `turn` snippet, so it can render this component or its own. */
|
|
@@ -35,6 +37,23 @@ export interface AgentTranscriptProps {
|
|
|
35
37
|
onopenartefact?: () => void;
|
|
36
38
|
/** The persona's own words for its tools, and what they read. */
|
|
37
39
|
describeTool?: DescribeTool;
|
|
40
|
+
/** The names of the files that went with the question, shown as chips. */
|
|
41
|
+
files?: string[];
|
|
42
|
+
/** Replaces this question with `text` and asks it again from here
|
|
43
|
+
* (`Chat.edit`), resolving false when that did not go through. Omit
|
|
44
|
+
* and no edit control renders: not while an answer is being written. */
|
|
45
|
+
onedit?: (text: string) => Promise<boolean>;
|
|
46
|
+
/** The documents the persona wrote answering it, carded under the answer. */
|
|
47
|
+
written?: Written[];
|
|
48
|
+
/** The version the reading column shows now, by `writtenKey`. */
|
|
49
|
+
showingWritten?: string;
|
|
50
|
+
/** Opens a version in the reading column. Omit and the cards only link. */
|
|
51
|
+
onopenwritten?: (written: Written) => void;
|
|
52
|
+
/** Where a version downloads. Omit and no download links render. */
|
|
53
|
+
writtenUrl?: WrittenUrl;
|
|
54
|
+
/** Renderers by a fence's language: a block of one is drawn by its
|
|
55
|
+
* component in the answer. Absent, every fence is code. */
|
|
56
|
+
visuals?: Record<string, Visual>;
|
|
38
57
|
/** When an answer this page did not start began, epoch ms: it is still
|
|
39
58
|
* being written, and the clock counts from then. */
|
|
40
59
|
answering?: number;
|