@poodle64/librarian 2026.10.6 → 2026.10.7
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/chat.svelte.d.ts +6 -4
- package/dist/chat.svelte.js +21 -11
- package/dist/components/agent-transcript/agent-transcript.svelte +18 -6
- package/dist/components/agent-transcript/agent-transcript.svelte.d.ts +7 -0
- package/dist/components/composer/composer.svelte +25 -14
- package/dist/components/composer/scope-picker.svelte +3 -2
- package/dist/components/conversation/conversation.svelte +6 -1
- package/dist/components/conversation/conversation.svelte.d.ts +3 -0
- package/package.json +1 -1
package/dist/chat.svelte.d.ts
CHANGED
|
@@ -205,11 +205,13 @@ export declare class Chat {
|
|
|
205
205
|
new(): void;
|
|
206
206
|
/**
|
|
207
207
|
* Ask `question`, or what is in the box, as a `kind` of turn when it is not
|
|
208
|
-
* a plain question, narrowed to one `collection` by name when given.
|
|
209
|
-
*
|
|
210
|
-
*
|
|
208
|
+
* a plain question, narrowed to one `collection` by name when given. A
|
|
209
|
+
* question handed in carries `files` with it, the box's own going with a
|
|
210
|
+
* typed one. Resolves once the turn settles: true if it was asked, false if
|
|
211
|
+
* it was refused, here or by the server, in which case the question and its
|
|
212
|
+
* files go back into the box, handed or typed.
|
|
211
213
|
*/
|
|
212
|
-
ask(question?: string, kind?: string, collection?: string): Promise<boolean>;
|
|
214
|
+
ask(question?: string, kind?: string, collection?: string, files?: File[]): Promise<boolean>;
|
|
213
215
|
/** The last question again, as a new turn: the conversation is append-only,
|
|
214
216
|
* because the agent's transcript is. Same kind, same collection. */
|
|
215
217
|
again(): Promise<boolean>;
|
package/dist/chat.svelte.js
CHANGED
|
@@ -144,25 +144,31 @@ export class Chat {
|
|
|
144
144
|
}
|
|
145
145
|
/**
|
|
146
146
|
* Ask `question`, or what is in the box, as a `kind` of turn when it is not
|
|
147
|
-
* a plain question, narrowed to one `collection` by name when given.
|
|
148
|
-
*
|
|
149
|
-
*
|
|
147
|
+
* a plain question, narrowed to one `collection` by name when given. A
|
|
148
|
+
* question handed in carries `files` with it, the box's own going with a
|
|
149
|
+
* typed one. Resolves once the turn settles: true if it was asked, false if
|
|
150
|
+
* it was refused, here or by the server, in which case the question and its
|
|
151
|
+
* files go back into the box, handed or typed.
|
|
150
152
|
*/
|
|
151
|
-
async ask(question, kind, collection) {
|
|
153
|
+
async ask(question, kind, collection, files = []) {
|
|
152
154
|
const typed = question === undefined;
|
|
153
155
|
const text = (question ?? this.draft).trim();
|
|
154
156
|
if (!text)
|
|
155
157
|
return false;
|
|
156
158
|
if (this.#job)
|
|
157
159
|
return this.#message(text, typed);
|
|
158
|
-
if (this.busy || this.quota?.reached)
|
|
160
|
+
if (this.busy || this.quota?.reached) {
|
|
161
|
+
// A handed question has no other home: the box takes it, as it takes a refused one.
|
|
162
|
+
if (!typed)
|
|
163
|
+
this.#restore(text, files);
|
|
159
164
|
return false;
|
|
160
|
-
|
|
165
|
+
}
|
|
166
|
+
const sent = typed ? this.files : files;
|
|
161
167
|
if (typed) {
|
|
162
168
|
this.draft = '';
|
|
163
169
|
this.files = [];
|
|
164
170
|
}
|
|
165
|
-
return this.#ask(text,
|
|
171
|
+
return this.#ask(text, sent, typed, kind, collection);
|
|
166
172
|
}
|
|
167
173
|
/** The last question again, as a new turn: the conversation is append-only,
|
|
168
174
|
* because the agent's transcript is. Same kind, same collection. */
|
|
@@ -419,10 +425,7 @@ export class Chat {
|
|
|
419
425
|
const at = this.#turns.findIndex((t) => t.id === live.id);
|
|
420
426
|
if (at >= 0)
|
|
421
427
|
this.#turns.splice(at, 1);
|
|
422
|
-
|
|
423
|
-
this.draft = text;
|
|
424
|
-
this.files = files;
|
|
425
|
-
}
|
|
428
|
+
this.#restore(text, files);
|
|
426
429
|
this.#finish();
|
|
427
430
|
// Asked elsewhere and still being answered: show that instead.
|
|
428
431
|
if (ending === 'answering' && id)
|
|
@@ -564,6 +567,11 @@ export class Chat {
|
|
|
564
567
|
return;
|
|
565
568
|
}
|
|
566
569
|
}
|
|
570
|
+
/** A question that was not asked goes back into the box with its files, ahead of anything typed since. */
|
|
571
|
+
#restore(text, files) {
|
|
572
|
+
this.draft = this.draft ? `${text}\n\n${this.draft}` : text;
|
|
573
|
+
this.files = [...files, ...this.files];
|
|
574
|
+
}
|
|
567
575
|
async #message(text, typed) {
|
|
568
576
|
const id = this.conversationId;
|
|
569
577
|
if (!id)
|
|
@@ -576,6 +584,8 @@ export class Chat {
|
|
|
576
584
|
catch {
|
|
577
585
|
if (typed)
|
|
578
586
|
this.draft = text;
|
|
587
|
+
else
|
|
588
|
+
this.#restore(text, []);
|
|
579
589
|
return false;
|
|
580
590
|
}
|
|
581
591
|
// A settled job takes it as a new run, already started when the
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import PencilIcon from '@lucide/svelte/icons/pencil';
|
|
19
19
|
import FileTextIcon from '@lucide/svelte/icons/file-text';
|
|
20
20
|
import RefreshCwIcon from '@lucide/svelte/icons/refresh-cw';
|
|
21
|
+
import type { Snippet } from 'svelte';
|
|
21
22
|
import {
|
|
22
23
|
outcomeLine,
|
|
23
24
|
readerQuestion,
|
|
@@ -116,6 +117,9 @@
|
|
|
116
117
|
* ("about A$0.34"). The package never converts currency itself,
|
|
117
118
|
* and a zero-cost run (a local model) shows no cost regardless. */
|
|
118
119
|
formatCost?: (usd: number) => string;
|
|
120
|
+
/** Drawn in place of the package's own "working" line while the answer has
|
|
121
|
+
* shown nothing yet; given the line's `label` and `since`. */
|
|
122
|
+
working?: Snippet<[{ label?: string; since?: number }]>;
|
|
119
123
|
}
|
|
120
124
|
|
|
121
125
|
let {
|
|
@@ -145,7 +149,8 @@
|
|
|
145
149
|
onmark,
|
|
146
150
|
shared,
|
|
147
151
|
onshare,
|
|
148
|
-
formatCost
|
|
152
|
+
formatCost,
|
|
153
|
+
working
|
|
149
154
|
}: AgentTranscriptProps = $props();
|
|
150
155
|
|
|
151
156
|
// The prose IS the artefact: it reads in the column, and the transcript
|
|
@@ -390,11 +395,18 @@
|
|
|
390
395
|
</header>
|
|
391
396
|
|
|
392
397
|
{#if running && segments.length === 0}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
+
{#if working}
|
|
399
|
+
{@render working({
|
|
400
|
+
label: answering !== undefined ? words.stillAnswering : undefined,
|
|
401
|
+
since: answering
|
|
402
|
+
})}
|
|
403
|
+
{:else}
|
|
404
|
+
<Working
|
|
405
|
+
copy={words}
|
|
406
|
+
label={answering !== undefined ? words.stillAnswering : undefined}
|
|
407
|
+
since={answering}
|
|
408
|
+
/>
|
|
409
|
+
{/if}
|
|
398
410
|
{/if}
|
|
399
411
|
|
|
400
412
|
{#each segments as seg (seg.index)}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
1
2
|
import { type Artefact, type Block, type DescribeTool, type Outcome } from '../../transcript.svelte';
|
|
2
3
|
import { type Citation } from '../../citations';
|
|
3
4
|
import { type ChatCopy } from '../../copy';
|
|
@@ -71,6 +72,12 @@ export interface AgentTranscriptProps {
|
|
|
71
72
|
* ("about A$0.34"). The package never converts currency itself,
|
|
72
73
|
* and a zero-cost run (a local model) shows no cost regardless. */
|
|
73
74
|
formatCost?: (usd: number) => string;
|
|
75
|
+
/** Drawn in place of the package's own "working" line while the answer has
|
|
76
|
+
* shown nothing yet; given the line's `label` and `since`. */
|
|
77
|
+
working?: Snippet<[{
|
|
78
|
+
label?: string;
|
|
79
|
+
since?: number;
|
|
80
|
+
}]>;
|
|
74
81
|
}
|
|
75
82
|
declare const AgentTranscript: import("svelte").Component<AgentTranscriptProps, {}, "">;
|
|
76
83
|
type AgentTranscript = ReturnType<typeof AgentTranscript>;
|
|
@@ -120,8 +120,13 @@
|
|
|
120
120
|
|
|
121
121
|
// Who the question is put to: every collection the person may read, or one
|
|
122
122
|
// of them. A person with one collection (or none) has nothing to pick, so
|
|
123
|
-
// nothing renders rather than a control that only reselects itself
|
|
124
|
-
|
|
123
|
+
// nothing renders rather than a control that only reselects itself, unless
|
|
124
|
+
// the question is already narrowed to the one held: a page that has not yet
|
|
125
|
+
// had the list shows the collection its address names, and the control stays
|
|
126
|
+
// put as the rest of the list arrives.
|
|
127
|
+
const hasChoice = $derived(
|
|
128
|
+
collections.length > 1 || (collection !== undefined && collections.length > 0)
|
|
129
|
+
);
|
|
125
130
|
|
|
126
131
|
let box = $state<HTMLElement | null>(null);
|
|
127
132
|
let wrapper = $state<HTMLElement | null>(null);
|
|
@@ -137,8 +142,10 @@
|
|
|
137
142
|
const el = textarea;
|
|
138
143
|
if (!el) return;
|
|
139
144
|
el.style.height = 'auto';
|
|
140
|
-
const
|
|
141
|
-
|
|
145
|
+
const style = getComputedStyle(el);
|
|
146
|
+
const line = parseFloat(style.lineHeight) || 27;
|
|
147
|
+
const padding = parseFloat(style.paddingTop) + parseFloat(style.paddingBottom);
|
|
148
|
+
el.style.height = `${Math.min(el.scrollHeight, line * 6 + padding)}px`;
|
|
142
149
|
}
|
|
143
150
|
|
|
144
151
|
$effect(() => {
|
|
@@ -280,7 +287,7 @@
|
|
|
280
287
|
bind:value
|
|
281
288
|
oninput={grow}
|
|
282
289
|
onkeydown={keydown}
|
|
283
|
-
rows="
|
|
290
|
+
rows="3"
|
|
284
291
|
disabled={waiting}
|
|
285
292
|
placeholder={waiting ? words.answeringPlaceholder : words.askPlaceholder}
|
|
286
293
|
class="ds-lib-input"
|
|
@@ -305,7 +312,7 @@
|
|
|
305
312
|
disabled={running || files.length >= MAX_FILES}
|
|
306
313
|
aria-label="Attach a file"
|
|
307
314
|
>
|
|
308
|
-
<PaperclipIcon size={
|
|
315
|
+
<PaperclipIcon size={20} />
|
|
309
316
|
</button>
|
|
310
317
|
{/if}
|
|
311
318
|
|
|
@@ -318,7 +325,7 @@
|
|
|
318
325
|
aria-label="Ask for a briefing"
|
|
319
326
|
title="Ask for a briefing"
|
|
320
327
|
>
|
|
321
|
-
<FileTextIcon size={
|
|
328
|
+
<FileTextIcon size={20} />
|
|
322
329
|
</button>
|
|
323
330
|
{/if}
|
|
324
331
|
|
|
@@ -339,7 +346,7 @@
|
|
|
339
346
|
<span class="ds-lib-spacer"></span>
|
|
340
347
|
{#if running}
|
|
341
348
|
<button type="button" class="ds-lib-stop" onclick={onstop} aria-label="Stop">
|
|
342
|
-
<SquareIcon size={
|
|
349
|
+
<SquareIcon size={16} fill="currentColor" />
|
|
343
350
|
</button>
|
|
344
351
|
{/if}
|
|
345
352
|
{#if !waiting}
|
|
@@ -350,7 +357,7 @@
|
|
|
350
357
|
disabled={!value.trim()}
|
|
351
358
|
aria-label="Send"
|
|
352
359
|
>
|
|
353
|
-
<ArrowUpIcon size={
|
|
360
|
+
<ArrowUpIcon size={20} />
|
|
354
361
|
</button>
|
|
355
362
|
{/if}
|
|
356
363
|
</div>
|
|
@@ -441,10 +448,14 @@
|
|
|
441
448
|
resize: none;
|
|
442
449
|
border: 0;
|
|
443
450
|
background: transparent;
|
|
444
|
-
padding:
|
|
451
|
+
padding: var(--ds-spacing-md) var(--ds-spacing-md) var(--ds-spacing-sm);
|
|
445
452
|
color: var(--ds-color-foreground);
|
|
446
453
|
font: inherit;
|
|
447
|
-
|
|
454
|
+
/* No token carries a body size or a control size, so these are raw:
|
|
455
|
+
a resting box that reads as a chat composer on every page (an app
|
|
456
|
+
overriding them is a defect here). Three lines, 18px text. */
|
|
457
|
+
font-size: 1.125rem;
|
|
458
|
+
line-height: 1.5;
|
|
448
459
|
outline: none;
|
|
449
460
|
}
|
|
450
461
|
|
|
@@ -462,7 +473,7 @@
|
|
|
462
473
|
align-items: center;
|
|
463
474
|
gap: 0.5rem;
|
|
464
475
|
row-gap: 0.375rem;
|
|
465
|
-
padding: 0
|
|
476
|
+
padding: 0 var(--ds-spacing-sm) var(--ds-spacing-sm);
|
|
466
477
|
}
|
|
467
478
|
|
|
468
479
|
.ds-lib-picker {
|
|
@@ -473,8 +484,8 @@
|
|
|
473
484
|
.ds-lib-stop,
|
|
474
485
|
.ds-lib-send {
|
|
475
486
|
display: flex;
|
|
476
|
-
width:
|
|
477
|
-
height:
|
|
487
|
+
width: 2.75rem;
|
|
488
|
+
height: 2.75rem;
|
|
478
489
|
flex: none;
|
|
479
490
|
align-items: center;
|
|
480
491
|
justify-content: center;
|
|
@@ -292,10 +292,11 @@
|
|
|
292
292
|
border: 1px solid transparent;
|
|
293
293
|
border-radius: var(--ds-radius-full);
|
|
294
294
|
background: none;
|
|
295
|
-
|
|
295
|
+
min-height: 2.75rem;
|
|
296
|
+
padding: 0 var(--ds-spacing-sm) 0 0.75rem;
|
|
296
297
|
color: var(--ds-color-muted-foreground);
|
|
297
298
|
font: inherit;
|
|
298
|
-
font-size:
|
|
299
|
+
font-size: 0.875rem;
|
|
299
300
|
cursor: pointer;
|
|
300
301
|
transition:
|
|
301
302
|
color 150ms ease,
|
|
@@ -137,6 +137,9 @@
|
|
|
137
137
|
* transcript. It stays put while the transcript scrolls and narrows
|
|
138
138
|
* with it when a pane opens. */
|
|
139
139
|
toolbar?: Snippet;
|
|
140
|
+
/** Drawn in place of each answer's "working" line before it shows anything
|
|
141
|
+
* (`AgentTranscriptProps.working`). */
|
|
142
|
+
working?: AgentTranscriptProps['working'];
|
|
140
143
|
}
|
|
141
144
|
|
|
142
145
|
let {
|
|
@@ -168,7 +171,8 @@
|
|
|
168
171
|
turn: presentTurn,
|
|
169
172
|
lead,
|
|
170
173
|
toolbar,
|
|
171
|
-
formatCost
|
|
174
|
+
formatCost,
|
|
175
|
+
working
|
|
172
176
|
}: Props = $props();
|
|
173
177
|
|
|
174
178
|
// Resolved ONCE, here, and handed down whole: every child takes `copy` and
|
|
@@ -392,6 +396,7 @@
|
|
|
392
396
|
? (text: string) => onedit(index, text)
|
|
393
397
|
: undefined,
|
|
394
398
|
formatCost,
|
|
399
|
+
working,
|
|
395
400
|
oncite: oncite || loadDocument ? cite : undefined,
|
|
396
401
|
onregenerate: last && !running ? onregenerate : undefined,
|
|
397
402
|
onmark:
|
|
@@ -96,6 +96,9 @@ interface Props {
|
|
|
96
96
|
* transcript. It stays put while the transcript scrolls and narrows
|
|
97
97
|
* with it when a pane opens. */
|
|
98
98
|
toolbar?: Snippet;
|
|
99
|
+
/** Drawn in place of each answer's "working" line before it shows anything
|
|
100
|
+
* (`AgentTranscriptProps.working`). */
|
|
101
|
+
working?: AgentTranscriptProps['working'];
|
|
99
102
|
}
|
|
100
103
|
declare const Conversation: import("svelte").Component<Props, {}, "">;
|
|
101
104
|
type Conversation = ReturnType<typeof Conversation>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@poodle64/librarian",
|
|
3
|
-
"version": "2026.10.
|
|
3
|
+
"version": "2026.10.7",
|
|
4
4
|
"description": "An agent's conversation surface as a consumable Svelte 5 package: the stream client, transcript state and self-styling chat components (transcript, composer, markdown) every household app renders instead of rebuilding, speaking as whichever persona the app names.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|