@poodle64/librarian 2026.9.21 → 2026.9.22
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 +129 -6
- package/dist/chat.svelte.d.ts +189 -0
- package/dist/chat.svelte.js +554 -0
- package/dist/client.d.ts +29 -6
- package/dist/client.js +11 -3
- package/dist/components/agent-transcript/agent-transcript.svelte +39 -5
- package/dist/components/agent-transcript/agent-transcript.svelte.d.ts +9 -0
- package/dist/components/answer-mark/answer-mark.svelte +223 -0
- package/dist/components/answer-mark/answer-mark.svelte.d.ts +11 -0
- package/dist/components/answer-mark/index.d.ts +2 -0
- package/dist/components/answer-mark/index.js +2 -0
- package/dist/components/conversation/conversation.svelte +21 -3
- package/dist/components/conversation/conversation.svelte.d.ts +9 -0
- package/dist/components/conversation-list/conversation-list.svelte +496 -0
- package/dist/components/conversation-list/conversation-list.svelte.d.ts +26 -0
- package/dist/components/conversation-list/index.d.ts +2 -0
- package/dist/components/conversation-list/index.js +2 -0
- package/dist/components/fair-use-notice/fair-use-notice.svelte +97 -0
- package/dist/components/fair-use-notice/fair-use-notice.svelte.d.ts +11 -0
- package/dist/components/fair-use-notice/index.d.ts +2 -0
- package/dist/components/fair-use-notice/index.js +2 -0
- package/dist/components/working/working.svelte +8 -3
- package/dist/components/working/working.svelte.d.ts +3 -0
- package/dist/copy.d.ts +40 -0
- package/dist/copy.js +36 -1
- package/dist/session.svelte.js +1 -1
- package/dist/transcript.svelte.js +1 -1
- package/package.json +5 -1
|
@@ -33,6 +33,8 @@
|
|
|
33
33
|
type Citation
|
|
34
34
|
} from '../../citations';
|
|
35
35
|
import { DEFAULT_PERSONA, personaName, resolveCopy, type LibrarianCopy } from '../../copy';
|
|
36
|
+
import type { Verdict } from '../../chat.svelte';
|
|
37
|
+
import AnswerMark from '../answer-mark/answer-mark.svelte';
|
|
36
38
|
import Working from '../working/working.svelte';
|
|
37
39
|
import ActivityGroup from '../activity-group/activity-group.svelte';
|
|
38
40
|
import ArtefactCard from '../artefact-card/artefact-card.svelte';
|
|
@@ -76,6 +78,14 @@
|
|
|
76
78
|
onopenartefact?: () => void;
|
|
77
79
|
/** The persona's own words for its tools, and what they read. */
|
|
78
80
|
describeTool?: DescribeTool;
|
|
81
|
+
/** The question waits behind somebody else's. */
|
|
82
|
+
waiting?: boolean;
|
|
83
|
+
/** When an answer this page did not start began, epoch ms: it is still
|
|
84
|
+
* being written, and the clock counts from then. */
|
|
85
|
+
answering?: number;
|
|
86
|
+
/** Records a verdict on this answer. Omit and no mark renders: a
|
|
87
|
+
* control that records nothing is worse than none. */
|
|
88
|
+
onmark?: (verdict: Verdict) => Promise<boolean>;
|
|
79
89
|
}
|
|
80
90
|
|
|
81
91
|
let {
|
|
@@ -95,7 +105,10 @@
|
|
|
95
105
|
artefact,
|
|
96
106
|
artefactOpen = false,
|
|
97
107
|
onopenartefact,
|
|
98
|
-
describeTool
|
|
108
|
+
describeTool,
|
|
109
|
+
waiting = false,
|
|
110
|
+
answering,
|
|
111
|
+
onmark
|
|
99
112
|
}: Props = $props();
|
|
100
113
|
|
|
101
114
|
// The prose IS the artefact: it reads in the column, and the transcript
|
|
@@ -186,11 +199,16 @@
|
|
|
186
199
|
: (outcome?.error ?? (outcome?.isError ? words.answerFailed : null))
|
|
187
200
|
);
|
|
188
201
|
|
|
202
|
+
// A turn nothing is answering any more that said nothing and ended with
|
|
203
|
+
// nothing: stopped before a word came, often while it waited its turn.
|
|
204
|
+
const unanswered = $derived(!running && !hasAnswer && outcome === null && !artefact);
|
|
205
|
+
|
|
189
206
|
// A run that ended has settled, whatever it said: a failed one, where "Ask
|
|
190
207
|
// again" is the one thing a reader wants, and one that handed in its answer
|
|
191
208
|
// under a schema and said nothing at all, whose artefact is the answer. A
|
|
192
|
-
// turn read back from history has no outcome and settles on its prose
|
|
193
|
-
|
|
209
|
+
// turn read back from history has no outcome and settles on its prose, and
|
|
210
|
+
// an unanswered one settles on nothing, offering "Ask again".
|
|
211
|
+
const settled = $derived(!running && (hasAnswer || outcome !== null || unanswered));
|
|
194
212
|
|
|
195
213
|
/**
|
|
196
214
|
* The persona answered and cited nothing, and we WATCHED it happen.
|
|
@@ -252,7 +270,15 @@
|
|
|
252
270
|
</header>
|
|
253
271
|
|
|
254
272
|
{#if running && segments.length === 0}
|
|
255
|
-
<Working
|
|
273
|
+
<Working
|
|
274
|
+
copy={words}
|
|
275
|
+
label={waiting
|
|
276
|
+
? words.waiting
|
|
277
|
+
: answering !== undefined
|
|
278
|
+
? words.stillAnswering
|
|
279
|
+
: undefined}
|
|
280
|
+
since={answering}
|
|
281
|
+
/>
|
|
256
282
|
{/if}
|
|
257
283
|
|
|
258
284
|
{#each segments as seg (seg.index)}
|
|
@@ -284,6 +310,10 @@
|
|
|
284
310
|
|
|
285
311
|
<!-- An empty `notHeld` is a host whose persona answers from no shelf,
|
|
286
312
|
where "without a source" would be a claim about nothing. -->
|
|
313
|
+
{#if unanswered && asked}
|
|
314
|
+
<p class="ds-lib-not-held">{words.noAnswer}</p>
|
|
315
|
+
{/if}
|
|
316
|
+
|
|
287
317
|
{#if !isArtefact && notHeld && words.notHeld}
|
|
288
318
|
<p class="ds-lib-not-held">{words.notHeld}</p>
|
|
289
319
|
{/if}
|
|
@@ -316,7 +346,7 @@
|
|
|
316
346
|
<p class="ds-lib-failure" role="alert">{failure}</p>
|
|
317
347
|
{/if}
|
|
318
348
|
|
|
319
|
-
{#if !isArtefact && settled}
|
|
349
|
+
{#if !isArtefact && settled && (hasAnswer || onregenerate || (outcome && !failure))}
|
|
320
350
|
<div class="ds-lib-actions">
|
|
321
351
|
{#if hasAnswer}
|
|
322
352
|
<button
|
|
@@ -329,6 +359,9 @@
|
|
|
329
359
|
<span>{copied ? words.copiedAnswer : words.copyAnswer}</span>
|
|
330
360
|
</button>
|
|
331
361
|
{/if}
|
|
362
|
+
{#if onmark && hasAnswer && !failure}
|
|
363
|
+
<AnswerMark {onmark} copy={words} />
|
|
364
|
+
{/if}
|
|
332
365
|
{#if onregenerate}
|
|
333
366
|
<button
|
|
334
367
|
type="button"
|
|
@@ -515,6 +548,7 @@
|
|
|
515
548
|
|
|
516
549
|
.ds-lib-actions {
|
|
517
550
|
display: flex;
|
|
551
|
+
flex-wrap: wrap;
|
|
518
552
|
align-items: center;
|
|
519
553
|
gap: 0.25rem;
|
|
520
554
|
margin-inline-start: -0.375rem;
|
|
@@ -1,6 +1,7 @@
|
|
|
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 LibrarianCopy } from '../../copy';
|
|
4
|
+
import type { Verdict } from '../../chat.svelte';
|
|
4
5
|
interface Props {
|
|
5
6
|
question: string;
|
|
6
7
|
blocks: Block[];
|
|
@@ -38,6 +39,14 @@ interface Props {
|
|
|
38
39
|
onopenartefact?: () => void;
|
|
39
40
|
/** The persona's own words for its tools, and what they read. */
|
|
40
41
|
describeTool?: DescribeTool;
|
|
42
|
+
/** The question waits behind somebody else's. */
|
|
43
|
+
waiting?: boolean;
|
|
44
|
+
/** When an answer this page did not start began, epoch ms: it is still
|
|
45
|
+
* being written, and the clock counts from then. */
|
|
46
|
+
answering?: number;
|
|
47
|
+
/** Records a verdict on this answer. Omit and no mark renders: a
|
|
48
|
+
* control that records nothing is worse than none. */
|
|
49
|
+
onmark?: (verdict: Verdict) => Promise<boolean>;
|
|
41
50
|
}
|
|
42
51
|
declare const AgentTranscript: import("svelte").Component<Props, {}, "">;
|
|
43
52
|
type AgentTranscript = ReturnType<typeof AgentTranscript>;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Helpful or not, on the answer a reader has just read, with an optional note.
|
|
3
|
+
|
|
4
|
+
Rendered by `AgentTranscript` inside its action row, between copy and ask
|
|
5
|
+
again, and only when the host passes `onmark`: the Console asks its own
|
|
6
|
+
library, where nothing can take a mark, and a control a host cannot wire is
|
|
7
|
+
worse than none (cadmus#114, where this began).
|
|
8
|
+
|
|
9
|
+
Several roots on purpose: the two thumbs sit in the row, and the note drops
|
|
10
|
+
to a line of its own under it (`order`, `flex-basis`), so the keyboard goes
|
|
11
|
+
verdict, note, ask again, which is the order a reader thinks in.
|
|
12
|
+
|
|
13
|
+
The note is optional and opens only after a verdict, so the fast path is one
|
|
14
|
+
tap. A verdict shows at once and is taken back if it was not recorded; a
|
|
15
|
+
note says "noted" only once the far side confirms it.
|
|
16
|
+
-->
|
|
17
|
+
<script lang="ts">
|
|
18
|
+
import ThumbsDownIcon from '@lucide/svelte/icons/thumbs-down';
|
|
19
|
+
import ThumbsUpIcon from '@lucide/svelte/icons/thumbs-up';
|
|
20
|
+
import type { Verdict } from '../../chat.svelte';
|
|
21
|
+
import { resolveCopy, type LibrarianCopy } from '../../copy';
|
|
22
|
+
|
|
23
|
+
interface Props {
|
|
24
|
+
/** Records a verdict, replacing any before it. Resolves true once it is
|
|
25
|
+
* recorded. */
|
|
26
|
+
onmark: (verdict: Verdict) => Promise<boolean>;
|
|
27
|
+
copy?: Partial<LibrarianCopy>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let { onmark, copy }: Props = $props();
|
|
31
|
+
|
|
32
|
+
const words = $derived(resolveCopy(copy));
|
|
33
|
+
|
|
34
|
+
let helpful = $state<boolean | null>(null);
|
|
35
|
+
let note = $state('');
|
|
36
|
+
/** The note last recorded, sent again when the verdict flips: the far side
|
|
37
|
+
* replaces the whole mark. */
|
|
38
|
+
let recorded = $state<string | null>(null);
|
|
39
|
+
let saving = $state(false);
|
|
40
|
+
let failed = $state(false);
|
|
41
|
+
let form = $state<HTMLFormElement | null>(null);
|
|
42
|
+
|
|
43
|
+
// The note opens under the last answer, which is where the fold is: it
|
|
44
|
+
// brings itself into view, and leaves the keyboard where it was, so a
|
|
45
|
+
// thumb's tap on a phone does not raise one.
|
|
46
|
+
$effect(() => {
|
|
47
|
+
form?.scrollIntoView({ block: 'nearest' });
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
async function send(verdict: Verdict): Promise<boolean> {
|
|
51
|
+
saving = true;
|
|
52
|
+
failed = false;
|
|
53
|
+
try {
|
|
54
|
+
const ok = await onmark(verdict);
|
|
55
|
+
failed = !ok;
|
|
56
|
+
return ok;
|
|
57
|
+
} finally {
|
|
58
|
+
saving = false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function mark(next: boolean) {
|
|
63
|
+
if (saving || helpful === next) return;
|
|
64
|
+
const previous = helpful;
|
|
65
|
+
helpful = next;
|
|
66
|
+
if (!(await send({ helpful: next, note: recorded }))) helpful = previous;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function submitNote(event: SubmitEvent) {
|
|
70
|
+
event.preventDefault();
|
|
71
|
+
const text = note.trim();
|
|
72
|
+
if (!text || helpful === null || saving) return;
|
|
73
|
+
if (await send({ helpful, note: text })) recorded = text;
|
|
74
|
+
}
|
|
75
|
+
</script>
|
|
76
|
+
|
|
77
|
+
<button
|
|
78
|
+
type="button"
|
|
79
|
+
class="ds-lib-mark"
|
|
80
|
+
class:is-helpful={helpful === true}
|
|
81
|
+
aria-pressed={helpful === true}
|
|
82
|
+
aria-label={words.markHelpful}
|
|
83
|
+
title={words.markHelpful}
|
|
84
|
+
disabled={saving}
|
|
85
|
+
onclick={() => mark(true)}
|
|
86
|
+
>
|
|
87
|
+
<ThumbsUpIcon size={14} />
|
|
88
|
+
</button>
|
|
89
|
+
<button
|
|
90
|
+
type="button"
|
|
91
|
+
class="ds-lib-mark"
|
|
92
|
+
class:is-not-helpful={helpful === false}
|
|
93
|
+
aria-pressed={helpful === false}
|
|
94
|
+
aria-label={words.markNotHelpful}
|
|
95
|
+
title={words.markNotHelpful}
|
|
96
|
+
disabled={saving}
|
|
97
|
+
onclick={() => mark(false)}
|
|
98
|
+
>
|
|
99
|
+
<ThumbsDownIcon size={14} />
|
|
100
|
+
</button>
|
|
101
|
+
|
|
102
|
+
<!-- A note that failed keeps its box, and the words in it, to try again. -->
|
|
103
|
+
{#if recorded !== null && !failed}
|
|
104
|
+
<p class="ds-lib-mark-line" role="status">{words.markNoted}</p>
|
|
105
|
+
{:else if helpful !== null}
|
|
106
|
+
<form bind:this={form} class="ds-lib-mark-line ds-lib-mark-form" onsubmit={submitNote}>
|
|
107
|
+
<input
|
|
108
|
+
bind:value={note}
|
|
109
|
+
maxlength={500}
|
|
110
|
+
placeholder={words.markNote}
|
|
111
|
+
aria-label={words.markNote}
|
|
112
|
+
class="ds-lib-mark-input"
|
|
113
|
+
/>
|
|
114
|
+
<button type="submit" class="ds-lib-mark-send" disabled={!note.trim() || saving}>
|
|
115
|
+
{words.markSend}
|
|
116
|
+
</button>
|
|
117
|
+
</form>
|
|
118
|
+
{/if}
|
|
119
|
+
{#if failed}
|
|
120
|
+
<p class="ds-lib-mark-line ds-lib-mark-failed" role="status">{words.markFailed}</p>
|
|
121
|
+
{/if}
|
|
122
|
+
|
|
123
|
+
<style>
|
|
124
|
+
.ds-lib-mark {
|
|
125
|
+
display: flex;
|
|
126
|
+
width: 1.75rem;
|
|
127
|
+
height: 1.75rem;
|
|
128
|
+
align-items: center;
|
|
129
|
+
justify-content: center;
|
|
130
|
+
border: 0;
|
|
131
|
+
border-radius: var(--ds-radius-md);
|
|
132
|
+
background: none;
|
|
133
|
+
color: inherit;
|
|
134
|
+
cursor: pointer;
|
|
135
|
+
transition:
|
|
136
|
+
color 150ms ease,
|
|
137
|
+
background-color 150ms ease;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.ds-lib-mark:hover:not(:disabled) {
|
|
141
|
+
background: var(--ds-color-surface-1);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* A verdict keeps its colour under the pointer that just gave it. */
|
|
145
|
+
.ds-lib-mark:hover:not(:disabled, [aria-pressed='true']) {
|
|
146
|
+
color: var(--ds-color-foreground);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.ds-lib-mark:disabled {
|
|
150
|
+
cursor: default;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.ds-lib-mark:focus-visible,
|
|
154
|
+
.ds-lib-mark-send:focus-visible {
|
|
155
|
+
outline: 2px solid var(--ds-color-ring);
|
|
156
|
+
outline-offset: 2px;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.ds-lib-mark.is-helpful {
|
|
160
|
+
color: var(--ds-color-status-success);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.ds-lib-mark.is-not-helpful {
|
|
164
|
+
color: var(--ds-color-status-error);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/* Its own line under the row, whatever sits after the thumbs in it. */
|
|
168
|
+
.ds-lib-mark-line {
|
|
169
|
+
order: 1;
|
|
170
|
+
flex-basis: 100%;
|
|
171
|
+
margin: 0.25rem 0 0 0.375rem;
|
|
172
|
+
font-size: var(--ds-text-2xs);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.ds-lib-mark-failed {
|
|
176
|
+
color: var(--ds-color-status-error);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.ds-lib-mark-form {
|
|
180
|
+
display: flex;
|
|
181
|
+
max-width: 28rem;
|
|
182
|
+
min-width: 0;
|
|
183
|
+
gap: 0.5rem;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.ds-lib-mark-input {
|
|
187
|
+
min-width: 0;
|
|
188
|
+
flex: 1;
|
|
189
|
+
border: 1px solid var(--ds-color-border);
|
|
190
|
+
border-radius: var(--ds-radius-md);
|
|
191
|
+
background: var(--ds-color-surface-1);
|
|
192
|
+
padding: 0.375rem 0.625rem;
|
|
193
|
+
color: var(--ds-color-foreground);
|
|
194
|
+
font: inherit;
|
|
195
|
+
font-size: 0.875rem;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.ds-lib-mark-input::placeholder {
|
|
199
|
+
color: var(--ds-color-muted-foreground);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.ds-lib-mark-input:focus {
|
|
203
|
+
border-color: color-mix(in oklab, var(--ds-color-primary) 60%, transparent);
|
|
204
|
+
outline: none;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.ds-lib-mark-send {
|
|
208
|
+
flex: none;
|
|
209
|
+
border: 1px solid var(--ds-color-border);
|
|
210
|
+
border-radius: var(--ds-radius-md);
|
|
211
|
+
background: none;
|
|
212
|
+
padding: 0.25rem 0.75rem;
|
|
213
|
+
color: var(--ds-color-foreground);
|
|
214
|
+
font: inherit;
|
|
215
|
+
font-size: 0.8125rem;
|
|
216
|
+
cursor: pointer;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.ds-lib-mark-send:disabled {
|
|
220
|
+
cursor: default;
|
|
221
|
+
opacity: 0.4;
|
|
222
|
+
}
|
|
223
|
+
</style>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Verdict } from '../../chat.svelte';
|
|
2
|
+
import { type LibrarianCopy } from '../../copy';
|
|
3
|
+
interface Props {
|
|
4
|
+
/** Records a verdict, replacing any before it. Resolves true once it is
|
|
5
|
+
* recorded. */
|
|
6
|
+
onmark: (verdict: Verdict) => Promise<boolean>;
|
|
7
|
+
copy?: Partial<LibrarianCopy>;
|
|
8
|
+
}
|
|
9
|
+
declare const AnswerMark: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type AnswerMark = ReturnType<typeof AnswerMark>;
|
|
11
|
+
export default AnswerMark;
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import type { Snippet } from 'svelte';
|
|
13
13
|
import ArrowDownIcon from '@lucide/svelte/icons/arrow-down';
|
|
14
14
|
import type { DescribeTool, Turn } from '../../transcript.svelte';
|
|
15
|
+
import type { Verdict } from '../../chat.svelte';
|
|
15
16
|
import type { Citation, LoadDocument } from '../../citations';
|
|
16
17
|
import { DEFAULT_PERSONA, personaName, resolveCopy, type LibrarianCopy } from '../../copy';
|
|
17
18
|
import { FollowScroll } from '../../follow-scroll.svelte';
|
|
@@ -51,6 +52,14 @@
|
|
|
51
52
|
/** Asks a follow-up the librarian suggested. Offered on the last answer
|
|
52
53
|
* only; without it the suggestion chips do not render. */
|
|
53
54
|
onsuggest?: (question: string) => void;
|
|
55
|
+
/** Records a verdict on an answer. Offered on the last answer only, as
|
|
56
|
+
* asking again is; without it no mark renders. */
|
|
57
|
+
onmark?: (turn: Turn, verdict: Verdict) => Promise<boolean>;
|
|
58
|
+
/** The last question waits behind somebody else's (`Chat.waiting`). */
|
|
59
|
+
waiting?: boolean;
|
|
60
|
+
/** When the last answer began, epoch ms, where this page did not see it
|
|
61
|
+
* start (`Chat.answering`): it reads as still being written. */
|
|
62
|
+
answering?: number | null;
|
|
54
63
|
/** Overrides for the package's own words. */
|
|
55
64
|
copy?: Partial<LibrarianCopy>;
|
|
56
65
|
/** Enables the source pane. Without it, chips render but do not open. */
|
|
@@ -85,6 +94,9 @@
|
|
|
85
94
|
scope,
|
|
86
95
|
onregenerate,
|
|
87
96
|
onsuggest,
|
|
97
|
+
onmark,
|
|
98
|
+
waiting = false,
|
|
99
|
+
answering = null,
|
|
88
100
|
copy,
|
|
89
101
|
loadDocument,
|
|
90
102
|
oncite,
|
|
@@ -256,11 +268,14 @@
|
|
|
256
268
|
{/if}
|
|
257
269
|
|
|
258
270
|
{#each turns as turn, index (turn.id)}
|
|
271
|
+
{@const last = index === turns.length - 1}
|
|
259
272
|
<AgentTranscript
|
|
260
273
|
question={turn.question}
|
|
261
274
|
blocks={turn.blocks}
|
|
262
275
|
outcome={turn.outcome}
|
|
263
|
-
running={running &&
|
|
276
|
+
running={running && last}
|
|
277
|
+
waiting={waiting && last}
|
|
278
|
+
answering={last ? (answering ?? undefined) : undefined}
|
|
264
279
|
citations={turn.citations ?? []}
|
|
265
280
|
suggestions={turn.suggestions ?? []}
|
|
266
281
|
{collectionNames}
|
|
@@ -272,8 +287,11 @@
|
|
|
272
287
|
onopenartefact={opener(turn)}
|
|
273
288
|
{describeTool}
|
|
274
289
|
oncite={oncite || loadDocument ? cite : undefined}
|
|
275
|
-
onregenerate={
|
|
276
|
-
onsuggest={
|
|
290
|
+
onregenerate={last && !running ? onregenerate : undefined}
|
|
291
|
+
onsuggest={last && !running ? onsuggest : undefined}
|
|
292
|
+
onmark={last && !running && onmark
|
|
293
|
+
? (verdict) => onmark(turn, verdict)
|
|
294
|
+
: undefined}
|
|
277
295
|
/>
|
|
278
296
|
{/each}
|
|
279
297
|
</div>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
import type { DescribeTool, Turn } from '../../transcript.svelte';
|
|
3
|
+
import type { Verdict } from '../../chat.svelte';
|
|
3
4
|
import type { Citation, LoadDocument } from '../../citations';
|
|
4
5
|
import { type LibrarianCopy } from '../../copy';
|
|
5
6
|
interface Props {
|
|
@@ -28,6 +29,14 @@ interface Props {
|
|
|
28
29
|
/** Asks a follow-up the librarian suggested. Offered on the last answer
|
|
29
30
|
* only; without it the suggestion chips do not render. */
|
|
30
31
|
onsuggest?: (question: string) => void;
|
|
32
|
+
/** Records a verdict on an answer. Offered on the last answer only, as
|
|
33
|
+
* asking again is; without it no mark renders. */
|
|
34
|
+
onmark?: (turn: Turn, verdict: Verdict) => Promise<boolean>;
|
|
35
|
+
/** The last question waits behind somebody else's (`Chat.waiting`). */
|
|
36
|
+
waiting?: boolean;
|
|
37
|
+
/** When the last answer began, epoch ms, where this page did not see it
|
|
38
|
+
* start (`Chat.answering`): it reads as still being written. */
|
|
39
|
+
answering?: number | null;
|
|
31
40
|
/** Overrides for the package's own words. */
|
|
32
41
|
copy?: Partial<LibrarianCopy>;
|
|
33
42
|
/** Enables the source pane. Without it, chips render but do not open. */
|