@classic-homes/agent-chat 0.6.0 → 0.8.0

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.
Files changed (34) hide show
  1. package/README.md +5 -1
  2. package/dist/components/AgentChat.svelte +46 -3
  3. package/dist/components/AgentChat.svelte.d.ts +14 -3
  4. package/dist/components/AgentChat.svelte.d.ts.map +1 -1
  5. package/dist/components/AssistantMessage.svelte +6 -0
  6. package/dist/components/AssistantMessage.svelte.d.ts +3 -0
  7. package/dist/components/AssistantMessage.svelte.d.ts.map +1 -1
  8. package/dist/components/Composer.svelte +4 -0
  9. package/dist/components/Composer.svelte.d.ts +2 -0
  10. package/dist/components/Composer.svelte.d.ts.map +1 -1
  11. package/dist/components/MessageActions.svelte +11 -1
  12. package/dist/components/MessageActions.svelte.d.ts +4 -0
  13. package/dist/components/MessageActions.svelte.d.ts.map +1 -1
  14. package/dist/components/MessageFeedback.svelte +89 -0
  15. package/dist/components/MessageFeedback.svelte.d.ts +13 -0
  16. package/dist/components/MessageFeedback.svelte.d.ts.map +1 -0
  17. package/dist/components/MessageItem.svelte +2 -0
  18. package/dist/components/MessageItem.svelte.d.ts +2 -0
  19. package/dist/components/MessageItem.svelte.d.ts.map +1 -1
  20. package/dist/components/MessageList.svelte +12 -0
  21. package/dist/components/MessageList.svelte.d.ts +3 -0
  22. package/dist/components/MessageList.svelte.d.ts.map +1 -1
  23. package/dist/components/agent-picker.svelte.test.d.ts +2 -0
  24. package/dist/components/agent-picker.svelte.test.d.ts.map +1 -0
  25. package/dist/components/agent-picker.svelte.test.js +84 -0
  26. package/dist/components/feedback.svelte.test.d.ts +2 -0
  27. package/dist/components/feedback.svelte.test.d.ts.map +1 -0
  28. package/dist/components/feedback.svelte.test.js +99 -0
  29. package/dist/reducer.d.ts +3 -1
  30. package/dist/reducer.d.ts.map +1 -1
  31. package/dist/session.svelte.d.ts +8 -1
  32. package/dist/session.svelte.d.ts.map +1 -1
  33. package/dist/session.svelte.js +37 -1
  34. package/package.json +6 -6
package/README.md CHANGED
@@ -44,13 +44,17 @@ agent-api origin that serves `/frame`; artifacts and MCP-UI resources only ever
44
44
 
45
45
  - `ChatHeader`, `ConversationSidebar`: title and conversation actions; the history rail.
46
46
  - `MessageList`: transcript with day separators and a jump-to-latest button.
47
- - `MessageItem` → `UserMessage` / `AssistantMessage`, with `MessageActions` (copy, time),
47
+ - `MessageItem` → `UserMessage` / `AssistantMessage`, with `MessageActions` (copy, time,
48
+ `MessageFeedback` thumbs up/down with an optional note, Regenerate and Edit on the latest turn),
48
49
  `ArtifactChip`, `MessageFiles` (image lightbox, downloads) and `ProposalCard` (read-only
49
50
  document edits; applying stays in the Office add-in).
50
51
  - `Composer`: attach by button, drop or paste; `prompts` turns on the `/` saved-prompt menu
51
52
  (`ComposerPrompt.text` may be a loader, e.g. fetching the template).
52
53
  - `ToolTimeline`: tool calls with status and duration, delegations nested; `toolLabels` maps
53
54
  tool names to readable ones (`AgentChat` passes both through).
55
+ - Agent picker: pass `agents` (from `/api/agents`); with more than one, a new conversation offers
56
+ a picker in the composer until its first message. The conversation keeps that agent; `header`
57
+ receives `{ agent, agentName }` and `onagentchange` reports it.
54
58
  - `Welcome`: the empty conversation, with a greeting (`userName`), a capability line and up to
55
59
  four `suggestions` that send when chosen. `AgentChat` shows it unless the host passes an
56
60
  `empty` snippet, a loading skeleton while an existing conversation's transcript arrives, and
@@ -2,6 +2,7 @@
2
2
  import type { AgentApiClient } from '@classic-homes/agent-client';
3
3
  import type {
4
4
  AgentKey,
5
+ AgentSummary,
5
6
  ClientKind,
6
7
  DocumentContext,
7
8
  FileRef,
@@ -13,6 +14,7 @@
13
14
  cn,
14
15
  Icon,
15
16
  ResizablePanes,
17
+ Select,
16
18
  Sheet,
17
19
  Skeleton,
18
20
  } from '@classic-homes/theme-svelte';
@@ -34,7 +36,15 @@
34
36
  conversationId?: string | null;
35
37
  /** Backend mode from `/api/me`; shows the mock banner before any turn runs. */
36
38
  mode?: BackendMode;
39
+ /** Agent for a new conversation; a loaded conversation keeps its own. */
37
40
  agent?: AgentKey;
41
+ /**
42
+ * Agents the user may address (`/api/agents`). With more than one, a new conversation offers
43
+ * a picker in the composer until its first message; the agent is fixed after that.
44
+ */
45
+ agents?: AgentSummary[];
46
+ /** The agent of this conversation changed (picked, or learned from a loaded conversation). */
47
+ onagentchange?: (agent: AgentKey) => void;
38
48
  clientKind?: ClientKind;
39
49
  variant?: 'page' | 'panel';
40
50
  getContext?: () => DocumentContext | null | undefined;
@@ -63,8 +73,8 @@
63
73
  welcomeTitle?: string;
64
74
  /** Replaces the welcome's capability line. */
65
75
  welcomeDescription?: string;
66
- /** Rendered above the transcript, e.g. a `ChatHeader`. */
67
- header?: import('svelte').Snippet;
76
+ /** Rendered above the transcript, e.g. a `ChatHeader`; receives the conversation's agent. */
77
+ header?: import('svelte').Snippet<[{ agent: AgentKey; agentName?: string }]>;
68
78
  context?: import('svelte').Snippet;
69
79
  /** Replaces the default `Welcome` on an empty conversation. */
70
80
  empty?: import('svelte').Snippet;
@@ -76,6 +86,8 @@
76
86
  conversationId = null,
77
87
  mode,
78
88
  agent = 'classic-agent',
89
+ agents,
90
+ onagentchange,
79
91
  clientKind,
80
92
  variant = 'page',
81
93
  getContext,
@@ -163,6 +175,19 @@
163
175
  const last = session.messages[session.messages.length - 1];
164
176
  if (last?.role === 'assistant') void session.retry(last.requestId);
165
177
  }
178
+ const agentName = $derived(agents?.find((a) => a.key === session.agent)?.name);
179
+ const pickAgent = $derived(
180
+ (agents?.length ?? 0) > 1 && !conversationId && session.messages.length === 0,
181
+ );
182
+ let reportedAgent: AgentKey | undefined;
183
+ $effect(() => {
184
+ const current = session.agent;
185
+ if (current !== reportedAgent) {
186
+ reportedAgent = current;
187
+ onagentchange?.(current);
188
+ }
189
+ });
190
+
166
191
  // An existing conversation whose transcript has not arrived yet: show a skeleton, not the
167
192
  // welcome (which would flash before the messages).
168
193
  const pending = $derived(
@@ -181,6 +206,17 @@
181
206
  }
182
207
  </script>
183
208
 
209
+ {#snippet agentPicker()}
210
+ <Select
211
+ value={session.agent}
212
+ onValueChange={(value) => (session.agent = value as AgentKey)}
213
+ options={(agents ?? []).map((a) => ({ value: a.key, label: a.name }))}
214
+ aria-label="Agent"
215
+ disabled={session.status !== 'idle'}
216
+ class="h-8 w-auto min-w-36 border-0 bg-transparent text-xs shadow-none hover:bg-muted"
217
+ />
218
+ {/snippet}
219
+
184
220
  {#snippet emptyState()}
185
221
  {#if pending}
186
222
  <div
@@ -220,7 +256,7 @@
220
256
 
221
257
  {#snippet chat()}
222
258
  <div class="flex h-full min-w-0 flex-1 flex-col">
223
- {#if header}{@render header()}{/if}
259
+ {#if header}{@render header({ agent: session.agent, agentName })}{/if}
224
260
  <ModeBanner mode={session.mode} />
225
261
  {#if session.loadError}
226
262
  <div class="mx-auto w-full max-w-3xl px-4 pt-3">
@@ -256,6 +292,12 @@
256
292
  onretry={retry}
257
293
  onregenerate={(requestId) => void session.regenerate(requestId)}
258
294
  onedit={(requestId, text) => void session.edit(requestId, text)}
295
+ onrate={session.conversationId
296
+ ? (requestId, rating, comment) =>
297
+ session
298
+ .rate(requestId, rating, comment)
299
+ .catch(() => onnotify?.('Could not save your feedback. Try again.'))
300
+ : undefined}
259
301
  oncontinue={() => void session.send('Continue.')}
260
302
  empty={emptyState}
261
303
  />
@@ -271,6 +313,7 @@
271
313
  onsend={send}
272
314
  oncancel={() => session.cancel()}
273
315
  {context}
316
+ tools={pickAgent ? agentPicker : undefined}
274
317
  />
275
318
  </div>
276
319
  {/snippet}
@@ -1,5 +1,5 @@
1
1
  import type { AgentApiClient } from '@classic-homes/agent-client';
2
- import type { AgentKey, ClientKind, DocumentContext } from '@classic-homes/agent-contracts';
2
+ import type { AgentKey, AgentSummary, ClientKind, DocumentContext } from '@classic-homes/agent-contracts';
3
3
  import type { BackendMode } from '@classic-homes/agent-contracts';
4
4
  import type { ComposerPrompt } from '../composer-prompts.js';
5
5
  import type { WelcomeSuggestion } from '../welcome.js';
@@ -10,7 +10,15 @@ interface Props {
10
10
  conversationId?: string | null;
11
11
  /** Backend mode from `/api/me`; shows the mock banner before any turn runs. */
12
12
  mode?: BackendMode;
13
+ /** Agent for a new conversation; a loaded conversation keeps its own. */
13
14
  agent?: AgentKey;
15
+ /**
16
+ * Agents the user may address (`/api/agents`). With more than one, a new conversation offers
17
+ * a picker in the composer until its first message; the agent is fixed after that.
18
+ */
19
+ agents?: AgentSummary[];
20
+ /** The agent of this conversation changed (picked, or learned from a loaded conversation). */
21
+ onagentchange?: (agent: AgentKey) => void;
14
22
  clientKind?: ClientKind;
15
23
  variant?: 'page' | 'panel';
16
24
  getContext?: () => DocumentContext | null | undefined;
@@ -39,8 +47,11 @@ interface Props {
39
47
  welcomeTitle?: string;
40
48
  /** Replaces the welcome's capability line. */
41
49
  welcomeDescription?: string;
42
- /** Rendered above the transcript, e.g. a `ChatHeader`. */
43
- header?: import('svelte').Snippet;
50
+ /** Rendered above the transcript, e.g. a `ChatHeader`; receives the conversation's agent. */
51
+ header?: import('svelte').Snippet<[{
52
+ agent: AgentKey;
53
+ agentName?: string;
54
+ }]>;
44
55
  context?: import('svelte').Snippet;
45
56
  /** Replaces the default `Welcome` on an empty conversation. */
46
57
  empty?: import('svelte').Snippet;
@@ -1 +1 @@
1
- {"version":3,"file":"AgentChat.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/AgentChat.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,KAAK,EACR,QAAQ,EACR,UAAU,EACV,eAAe,EAEhB,MAAM,gCAAgC,CAAC;AAC1C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAYlE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAQrD,UAAU,KAAK;IACb,MAAM,EAAE,cAAc,CAAC;IACvB,oEAAoE;IACpE,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,+EAA+E;IAC/E,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,eAAe,GAAG,IAAI,GAAG,SAAS,CAAC;IACtD,qBAAqB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,CAAC;IACzD,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACtD,gFAAgF;IAChF,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,MAAM,EAAE,MAAM,CAAC;KAChB,KAAK,IAAI,CAAC;IACX,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,iGAAiG;IACjG,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wDAAwD;IACxD,OAAO,CAAC,EAAE,cAAc,EAAE,CAAC;IAC3B,oFAAoF;IACpF,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAClC,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8CAA8C;IAC9C,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,0DAA0D;IAC1D,MAAM,CAAC,EAAE,OAAO,QAAQ,EAAE,OAAO,CAAC;IAClC,OAAO,CAAC,EAAE,OAAO,QAAQ,EAAE,OAAO,CAAC;IACnC,+DAA+D;IAC/D,KAAK,CAAC,EAAE,OAAO,QAAQ,EAAE,OAAO,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA8MH,QAAA,MAAM,SAAS,2CAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"AgentChat.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/AgentChat.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,KAAK,EACR,QAAQ,EACR,YAAY,EACZ,UAAU,EACV,eAAe,EAEhB,MAAM,gCAAgC,CAAC;AAC1C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAalE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAQrD,UAAU,KAAK;IACb,MAAM,EAAE,cAAc,CAAC;IACvB,oEAAoE;IACpE,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,+EAA+E;IAC/E,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,yEAAyE;IACzE,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB;;;OAGG;IACH,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC;IACxB,8FAA8F;IAC9F,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;IAC1C,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,eAAe,GAAG,IAAI,GAAG,SAAS,CAAC;IACtD,qBAAqB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,CAAC;IACzD,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACtD,gFAAgF;IAChF,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,MAAM,EAAE,MAAM,CAAC;KAChB,KAAK,IAAI,CAAC;IACX,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,iGAAiG;IACjG,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wDAAwD;IACxD,OAAO,CAAC,EAAE,cAAc,EAAE,CAAC;IAC3B,oFAAoF;IACpF,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAClC,mDAAmD;IACnD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8CAA8C;IAC9C,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,6FAA6F;IAC7F,MAAM,CAAC,EAAE,OAAO,QAAQ,EAAE,OAAO,CAAC,CAAC;QAAE,KAAK,EAAE,QAAQ,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;IAC7E,OAAO,CAAC,EAAE,OAAO,QAAQ,EAAE,OAAO,CAAC;IACnC,+DAA+D;IAC/D,KAAK,CAAC,EAAE,OAAO,QAAQ,EAAE,OAAO,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAsOH,QAAA,MAAM,SAAS,2CAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
@@ -5,6 +5,7 @@
5
5
  -->
6
6
  <script lang="ts">
7
7
  import { Alert, Button, Icon } from '@classic-homes/theme-svelte';
8
+ import type { TurnRating } from '@classic-homes/agent-contracts';
8
9
  import type { AssistantMessage } from '../reducer.js';
9
10
  import { errorTitle } from '../reducer.js';
10
11
  import { parseMarkdown } from '../markdown.js';
@@ -32,6 +33,8 @@
32
33
  onretry?: () => void;
33
34
  /** Ask for a new answer to the same question (the latest finished answer). */
34
35
  onregenerate?: () => void;
36
+ /** Rate this answer; offered once it is done. */
37
+ onrate?: (rating: TurnRating | null, comment?: string) => void | Promise<void>;
35
38
  /** Ask the agent to pick up a stopped or cut-short answer. */
36
39
  oncontinue?: () => void;
37
40
  }
@@ -49,6 +52,7 @@
49
52
  onnotify,
50
53
  onretry,
51
54
  onregenerate,
55
+ onrate,
52
56
  oncontinue,
53
57
  }: Props = $props();
54
58
 
@@ -183,6 +187,8 @@
183
187
  createdAt={message.createdAt}
184
188
  visible={last}
185
189
  {onregenerate}
190
+ rating={message.feedback}
191
+ onrate={message.status === 'done' ? onrate : undefined}
186
192
  class="-ml-1.5"
187
193
  />
188
194
  {/if}
@@ -1,3 +1,4 @@
1
+ import type { TurnRating } from '@classic-homes/agent-contracts';
1
2
  import type { AssistantMessage } from '../reducer.js';
2
3
  interface Props {
3
4
  message: AssistantMessage;
@@ -15,6 +16,8 @@ interface Props {
15
16
  onretry?: () => void;
16
17
  /** Ask for a new answer to the same question (the latest finished answer). */
17
18
  onregenerate?: () => void;
19
+ /** Rate this answer; offered once it is done. */
20
+ onrate?: (rating: TurnRating | null, comment?: string) => void | Promise<void>;
18
21
  /** Ask the agent to pick up a stopped or cut-short answer. */
19
22
  oncontinue?: () => void;
20
23
  }
@@ -1 +1 @@
1
- {"version":3,"file":"AssistantMessage.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/AssistantMessage.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAYpD,UAAU,KAAK;IACb,OAAO,EAAE,gBAAgB,CAAC;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,uFAAuF;IACvF,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACtE,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,8EAA8E;IAC9E,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AAyJH;;;GAGG;AACH,QAAA,MAAM,gBAAgB,2CAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"AssistantMessage.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/AssistantMessage.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAYpD,UAAU,KAAK;IACb,OAAO,EAAE,gBAAgB,CAAC;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,uFAAuF;IACvF,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACtE,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,8EAA8E;IAC9E,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,iDAAiD;IACjD,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/E,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AA2JH;;;GAGG;AACH,QAAA,MAAM,gBAAgB,2CAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
@@ -27,6 +27,8 @@
27
27
  onremovefile?: (fileId: string) => void;
28
28
  /** Slot for a host-provided context chip (page context toggle). */
29
29
  context?: import('svelte').Snippet;
30
+ /** Toolbar controls before the context chip (e.g. the agent picker). */
31
+ tools?: import('svelte').Snippet;
30
32
  }
31
33
  let {
32
34
  disabled = false,
@@ -40,6 +42,7 @@
40
42
  oncancel,
41
43
  onremovefile,
42
44
  context,
45
+ tools,
43
46
  }: Props = $props();
44
47
 
45
48
  const uid = $props.id();
@@ -272,6 +275,7 @@
272
275
  <Icon name="paperclip" size={18} />
273
276
  </Button>
274
277
  {/if}
278
+ {#if tools}<div class="min-w-0">{@render tools()}</div>{/if}
275
279
  {#if context}<div class="min-w-0">{@render context()}</div>{/if}
276
280
  <div class="ml-auto flex items-center gap-2">
277
281
  {#if showCount}
@@ -15,6 +15,8 @@ interface Props {
15
15
  onremovefile?: (fileId: string) => void;
16
16
  /** Slot for a host-provided context chip (page context toggle). */
17
17
  context?: import('svelte').Snippet;
18
+ /** Toolbar controls before the context chip (e.g. the agent picker). */
19
+ tools?: import('svelte').Snippet;
18
20
  }
19
21
  /**
20
22
  * The message box: an autosizing textarea in a card with attach, the host's context chip and
@@ -1 +1 @@
1
- {"version":3,"file":"Composer.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/Composer.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAC;AAG9D,OAAO,EAA8B,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAIvF,UAAU,KAAK;IACb,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iFAAiF;IACjF,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1C,oEAAoE;IACpE,OAAO,CAAC,EAAE,cAAc,EAAE,CAAC;IAC3B,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,mEAAmE;IACnE,OAAO,CAAC,EAAE,OAAO,QAAQ,EAAE,OAAO,CAAC;CACpC;AA2PH;;;;GAIG;AACH,QAAA,MAAM,QAAQ;iBA/NO,IAAI;qBA+N6B,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"Composer.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/Composer.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAC;AAG9D,OAAO,EAA8B,KAAK,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAIvF,UAAU,KAAK;IACb,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iFAAiF;IACjF,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1C,oEAAoE;IACpE,OAAO,CAAC,EAAE,cAAc,EAAE,CAAC;IAC3B,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,mEAAmE;IACnE,OAAO,CAAC,EAAE,OAAO,QAAQ,EAAE,OAAO,CAAC;IACnC,wEAAwE;IACxE,KAAK,CAAC,EAAE,OAAO,QAAQ,EAAE,OAAO,CAAC;CAClC;AA6PH;;;;GAIG;AACH,QAAA,MAAM,QAAQ;iBAhOO,IAAI;qBAgO6B,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
@@ -6,7 +6,9 @@
6
6
  have no hover.
7
7
  -->
8
8
  <script lang="ts">
9
+ import type { TurnRating } from '@classic-homes/agent-contracts';
9
10
  import { Button, cn, CopyButton, Icon, Tooltip } from '@classic-homes/theme-svelte';
11
+ import MessageFeedback from './MessageFeedback.svelte';
10
12
  import { formatDateTime, formatTime } from '../message-format.js';
11
13
 
12
14
  interface Props {
@@ -19,6 +21,9 @@
19
21
  onregenerate?: () => void;
20
22
  /** Offer "Edit" (the latest question). */
21
23
  onedit?: () => void;
24
+ /** The answer's rating; with `onrate`, thumbs up/down are offered. */
25
+ rating?: TurnRating;
26
+ onrate?: (rating: TurnRating | null, comment?: string) => void | Promise<void>;
22
27
  class?: string;
23
28
  }
24
29
  let {
@@ -28,13 +33,15 @@
28
33
  align = 'start',
29
34
  onregenerate,
30
35
  onedit,
36
+ rating,
37
+ onrate,
31
38
  class: className,
32
39
  }: Props = $props();
33
40
  </script>
34
41
 
35
42
  <div
36
43
  class={cn(
37
- 'flex items-center gap-0.5 text-xs text-muted-foreground transition-opacity',
44
+ 'flex flex-wrap items-center gap-0.5 text-xs text-muted-foreground transition-opacity',
38
45
  align === 'end' && 'justify-end',
39
46
  !visible &&
40
47
  'opacity-0 group-hover/message:opacity-100 group-focus-within/message:opacity-100 [@media(hover:none)]:opacity-100',
@@ -52,6 +59,9 @@
52
59
  class="h-7 w-7"
53
60
  />
54
61
  {/if}
62
+ {#if onrate}
63
+ <MessageFeedback {rating} {onrate} />
64
+ {/if}
55
65
  {#if onedit}
56
66
  <Button
57
67
  variant="ghost"
@@ -1,3 +1,4 @@
1
+ import type { TurnRating } from '@classic-homes/agent-contracts';
1
2
  interface Props {
2
3
  text: string;
3
4
  createdAt: number;
@@ -8,6 +9,9 @@ interface Props {
8
9
  onregenerate?: () => void;
9
10
  /** Offer "Edit" (the latest question). */
10
11
  onedit?: () => void;
12
+ /** The answer's rating; with `onrate`, thumbs up/down are offered. */
13
+ rating?: TurnRating;
14
+ onrate?: (rating: TurnRating | null, comment?: string) => void | Promise<void>;
11
15
  class?: string;
12
16
  }
13
17
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"MessageActions.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/MessageActions.svelte.ts"],"names":[],"mappings":"AAOE,UAAU,KAAK;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,6DAA6D;IAC7D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,GAAG,KAAK,CAAC;IACxB,8CAA8C;IAC9C,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,0CAA0C;IAC1C,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA8CH;;;;;GAKG;AACH,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"MessageActions.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/MessageActions.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAM/D,UAAU,KAAK;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,6DAA6D;IAC7D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,GAAG,KAAK,CAAC;IACxB,8CAA8C;IAC9C,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,0CAA0C;IAC1C,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,sEAAsE;IACtE,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAqDH;;;;;GAKG;AACH,QAAA,MAAM,cAAc,2CAAwC,CAAC;AAC7D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AACxD,eAAe,cAAc,CAAC"}
@@ -0,0 +1,89 @@
1
+ <!--
2
+ @component
3
+ Thumbs up / down for one answer. Choosing the current rating again withdraws it. After a thumbs
4
+ down, an optional note can follow ("What went wrong?").
5
+ -->
6
+ <script lang="ts">
7
+ import type { TurnRating } from '@classic-homes/agent-contracts';
8
+ import { Button, cn, Icon, Input } from '@classic-homes/theme-svelte';
9
+ import { tick } from 'svelte';
10
+
11
+ interface Props {
12
+ rating?: TurnRating;
13
+ onrate: (rating: TurnRating | null, comment?: string) => void | Promise<void>;
14
+ }
15
+ let { rating, onrate }: Props = $props();
16
+
17
+ let asking = $state(false);
18
+ let note = $state('');
19
+ const noteId = $props.id();
20
+ const NOTE_MAX = 1000;
21
+
22
+ async function choose(value: TurnRating): Promise<void> {
23
+ const next = rating === value ? null : value;
24
+ asking = next === 'down';
25
+ note = '';
26
+ await onrate(next);
27
+ if (asking) {
28
+ await tick();
29
+ document.getElementById(noteId)?.focus();
30
+ }
31
+ }
32
+ async function sendNote(): Promise<void> {
33
+ const text = note.trim();
34
+ asking = false;
35
+ if (text) await onrate('down', text);
36
+ }
37
+ </script>
38
+
39
+ <span class="inline-flex items-center" role="group" aria-label="Rate this answer">
40
+ <Button
41
+ variant="ghost"
42
+ size="icon"
43
+ class={cn('h-7 w-7', rating === 'up' && 'text-foreground')}
44
+ aria-label="Good answer"
45
+ aria-pressed={rating === 'up'}
46
+ title="Good answer"
47
+ onclick={() => void choose('up')}
48
+ >
49
+ <Icon name="thumbs-up" size={14} class={cn(rating === 'up' && 'fill-current')} />
50
+ </Button>
51
+ <Button
52
+ variant="ghost"
53
+ size="icon"
54
+ class={cn('h-7 w-7', rating === 'down' && 'text-foreground')}
55
+ aria-label="Bad answer"
56
+ aria-pressed={rating === 'down'}
57
+ title="Bad answer"
58
+ onclick={() => void choose('down')}
59
+ >
60
+ <Icon name="thumbs-down" size={14} class={cn(rating === 'down' && 'fill-current')} />
61
+ </Button>
62
+ </span>
63
+ {#if asking}
64
+ <form
65
+ class="flex basis-full items-center gap-2 pt-1"
66
+ aria-label="Feedback note"
67
+ onsubmit={(e) => {
68
+ e.preventDefault();
69
+ void sendNote();
70
+ }}
71
+ >
72
+ <Input
73
+ id={noteId}
74
+ bind:value={note}
75
+ maxlength={NOTE_MAX}
76
+ placeholder="What went wrong? (optional)"
77
+ aria-label="What went wrong?"
78
+ class="h-8 max-w-sm text-sm"
79
+ onkeydown={(e: KeyboardEvent) => {
80
+ if (e.key === 'Escape') {
81
+ e.preventDefault();
82
+ asking = false;
83
+ }
84
+ }}
85
+ />
86
+ <Button type="submit" size="sm" variant="outline" disabled={!note.trim()}>Send</Button>
87
+ <Button type="button" size="sm" variant="ghost" onclick={() => (asking = false)}>Skip</Button>
88
+ </form>
89
+ {/if}
@@ -0,0 +1,13 @@
1
+ import type { TurnRating } from '@classic-homes/agent-contracts';
2
+ interface Props {
3
+ rating?: TurnRating;
4
+ onrate: (rating: TurnRating | null, comment?: string) => void | Promise<void>;
5
+ }
6
+ /**
7
+ * Thumbs up / down for one answer. Choosing the current rating again withdraws it. After a thumbs
8
+ * down, an optional note can follow ("What went wrong?").
9
+ */
10
+ declare const MessageFeedback: import("svelte").Component<Props, {}, "">;
11
+ type MessageFeedback = ReturnType<typeof MessageFeedback>;
12
+ export default MessageFeedback;
13
+ //# sourceMappingURL=MessageFeedback.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MessageFeedback.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/MessageFeedback.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAK/D,UAAU,KAAK;IACb,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,MAAM,EAAE,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/E;AAyDH;;;GAGG;AACH,QAAA,MAAM,eAAe,2CAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
@@ -3,6 +3,7 @@
3
3
  One transcript entry: a `UserMessage` or an `AssistantMessage`.
4
4
  -->
5
5
  <script lang="ts">
6
+ import type { TurnRating } from '@classic-homes/agent-contracts';
6
7
  import type { ChatMessage } from '../reducer.js';
7
8
  import AssistantMessage from './AssistantMessage.svelte';
8
9
  import UserMessage from './UserMessage.svelte';
@@ -21,6 +22,7 @@
21
22
  onnotify?: (message: string) => void;
22
23
  onretry?: () => void;
23
24
  onregenerate?: () => void;
25
+ onrate?: (rating: TurnRating | null, comment?: string) => void | Promise<void>;
24
26
  oncontinue?: () => void;
25
27
  /** User messages only: edit and resend. */
26
28
  onedit?: (text: string) => void;
@@ -1,3 +1,4 @@
1
+ import type { TurnRating } from '@classic-homes/agent-contracts';
1
2
  import type { ChatMessage } from '../reducer.js';
2
3
  interface Props {
3
4
  message: ChatMessage;
@@ -13,6 +14,7 @@ interface Props {
13
14
  onnotify?: (message: string) => void;
14
15
  onretry?: () => void;
15
16
  onregenerate?: () => void;
17
+ onrate?: (rating: TurnRating | null, comment?: string) => void | Promise<void>;
16
18
  oncontinue?: () => void;
17
19
  /** User messages only: edit and resend. */
18
20
  onedit?: (text: string) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"MessageItem.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/MessageItem.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAK/C,UAAU,KAAK;IACb,OAAO,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACtE,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,2CAA2C;IAC3C,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAkBH,sEAAsE;AACtE,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"MessageItem.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/MessageItem.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAK/C,UAAU,KAAK;IACb,OAAO,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACtE,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/E,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,2CAA2C;IAC3C,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC;AAmBH,sEAAsE;AACtE,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { tick } from 'svelte';
3
3
  import { Button, Icon } from '@classic-homes/theme-svelte';
4
+ import type { TurnRating } from '@classic-homes/agent-contracts';
4
5
  import type { ChatMessage } from '../reducer.js';
5
6
  import { daySeparators, formatDay } from '../message-format.js';
6
7
  import MessageItem from './MessageItem.svelte';
@@ -19,6 +20,12 @@
19
20
  onretry?: () => void;
20
21
  /** Regenerate the latest answer, by its request id. */
21
22
  onregenerate?: (requestId: string) => void;
23
+ /** Rate an answer, by its request id. */
24
+ onrate?: (
25
+ requestId: string,
26
+ rating: TurnRating | null,
27
+ comment?: string,
28
+ ) => void | Promise<void>;
22
29
  /** Edit the latest question, by its request id. */
23
30
  onedit?: (requestId: string, text: string) => void;
24
31
  oncontinue?: () => void;
@@ -37,6 +44,7 @@
37
44
  onnotify,
38
45
  onretry,
39
46
  onregenerate,
47
+ onrate,
40
48
  onedit,
41
49
  oncontinue,
42
50
  empty,
@@ -163,6 +171,10 @@
163
171
  message.status === 'done'
164
172
  ? () => onregenerate(latest)
165
173
  : undefined}
174
+ onrate={onrate && message.role === 'assistant'
175
+ ? (rating: TurnRating | null, comment?: string) =>
176
+ onrate(message.requestId, rating, comment)
177
+ : undefined}
166
178
  onedit={latest && onedit && i === messages.length - 2
167
179
  ? (text: string) => onedit(latest, text)
168
180
  : undefined}
@@ -1,3 +1,4 @@
1
+ import type { TurnRating } from '@classic-homes/agent-contracts';
1
2
  import type { ChatMessage } from '../reducer.js';
2
3
  interface Props {
3
4
  messages: ChatMessage[];
@@ -13,6 +14,8 @@ interface Props {
13
14
  onretry?: () => void;
14
15
  /** Regenerate the latest answer, by its request id. */
15
16
  onregenerate?: (requestId: string) => void;
17
+ /** Rate an answer, by its request id. */
18
+ onrate?: (requestId: string, rating: TurnRating | null, comment?: string) => void | Promise<void>;
16
19
  /** Edit the latest question, by its request id. */
17
20
  onedit?: (requestId: string, text: string) => void;
18
21
  oncontinue?: () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"MessageList.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/MessageList.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAK/C,UAAU,KAAK;IACb,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACtE,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,uDAAuD;IACvD,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,mDAAmD;IACnD,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,KAAK,CAAC,EAAE,OAAO,QAAQ,EAAE,OAAO,CAAC;CAClC;AA6IH,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"MessageList.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/MessageList.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAK/C,UAAU,KAAK;IACb,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACtE,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,uDAAuD;IACvD,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,yCAAyC;IACzC,MAAM,CAAC,EAAE,CACP,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,UAAU,GAAG,IAAI,EACzB,OAAO,CAAC,EAAE,MAAM,KACb,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,mDAAmD;IACnD,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,KAAK,CAAC,EAAE,OAAO,QAAQ,EAAE,OAAO,CAAC;CAClC;AAkJH,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=agent-picker.svelte.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-picker.svelte.test.d.ts","sourceRoot":"","sources":["../../src/lib/components/agent-picker.svelte.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,84 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+ import { cleanup, render, screen, waitFor } from '@testing-library/svelte';
3
+ import userEvent from '@testing-library/user-event';
4
+ import { ChatSession } from '../session.svelte.js';
5
+ import AgentChat from './AgentChat.svelte';
6
+ afterEach(() => cleanup());
7
+ const agents = [
8
+ { key: 'classic-agent', name: 'Classic Agent' },
9
+ { key: 'elevation-studio', name: 'Elevation Studio' },
10
+ ];
11
+ function fakeClient() {
12
+ return {
13
+ fileUrl: (id) => `#${id}`,
14
+ uploadFile: vi.fn(),
15
+ cancel: vi.fn(),
16
+ resumeTurn: vi.fn(),
17
+ sendFeedback: vi.fn(),
18
+ getConversation: vi.fn().mockResolvedValue({
19
+ conversation: { conversationId: 'c-9', agent: 'elevation-studio' },
20
+ messages: [],
21
+ files: [],
22
+ }),
23
+ streamChat: vi.fn(async (req, opts) => {
24
+ [
25
+ { type: 'start', conversationId: 'c-1', mode: 'live' },
26
+ { type: 'delta', text: 'Hello' },
27
+ { type: 'done', finishReason: 'stop' },
28
+ ].forEach((e, seq) => opts.onEvent({ requestId: req.requestId, seq, ...e }));
29
+ return { kind: 'done' };
30
+ }),
31
+ };
32
+ }
33
+ const base = { sandboxOrigin: 'https://frame.test' };
34
+ describe('ChatSession agent', () => {
35
+ it('sends the chosen agent and adopts a loaded conversation’s agent', async () => {
36
+ const client = fakeClient();
37
+ const session = new ChatSession({ client: client });
38
+ expect(session.agent).toBe('classic-agent');
39
+ session.agent = 'elevation-studio';
40
+ await session.send('hi');
41
+ expect(client.streamChat.mock.calls[0][0]).toMatchObject({ agent: 'elevation-studio' });
42
+ const other = new ChatSession({ client: client, conversationId: 'c-9' });
43
+ await other.load('c-9');
44
+ expect(other.agent).toBe('elevation-studio');
45
+ });
46
+ });
47
+ describe('AgentChat agent picker', () => {
48
+ it('offers a picker for a new conversation only, and hides it after the first message', async () => {
49
+ const user = userEvent.setup();
50
+ const client = fakeClient();
51
+ const onagentchange = vi.fn();
52
+ render(AgentChat, {
53
+ props: { ...base, client: client, agents, onagentchange },
54
+ });
55
+ expect(onagentchange).toHaveBeenLastCalledWith('classic-agent');
56
+ const picker = screen.getByRole('button', { name: 'Agent' });
57
+ await user.click(picker);
58
+ await user.click(await screen.findByRole('option', { name: 'Elevation Studio' }));
59
+ await waitFor(() => expect(onagentchange).toHaveBeenLastCalledWith('elevation-studio'));
60
+ await user.type(screen.getByRole('textbox', { name: 'Message' }), 'hi{Enter}');
61
+ await screen.findAllByText('Hello');
62
+ expect(client.streamChat.mock.calls[0][0]).toMatchObject({ agent: 'elevation-studio' });
63
+ expect(screen.queryByRole('button', { name: 'Agent' })).toBeNull();
64
+ });
65
+ it('has no picker with a single agent or on an existing conversation', async () => {
66
+ const { unmount } = render(AgentChat, {
67
+ props: { ...base, client: fakeClient(), agents: agents.slice(0, 1) },
68
+ });
69
+ expect(screen.queryByRole('button', { name: 'Agent' })).toBeNull();
70
+ unmount();
71
+ const onagentchange = vi.fn();
72
+ render(AgentChat, {
73
+ props: {
74
+ ...base,
75
+ client: fakeClient(),
76
+ agents,
77
+ conversationId: 'c-9',
78
+ onagentchange,
79
+ },
80
+ });
81
+ expect(screen.queryByRole('button', { name: 'Agent' })).toBeNull();
82
+ await waitFor(() => expect(onagentchange).toHaveBeenLastCalledWith('elevation-studio'));
83
+ });
84
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=feedback.svelte.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feedback.svelte.test.d.ts","sourceRoot":"","sources":["../../src/lib/components/feedback.svelte.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,99 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+ import { cleanup, render, screen, waitFor } from '@testing-library/svelte';
3
+ import userEvent from '@testing-library/user-event';
4
+ import { ChatSession } from '../session.svelte.js';
5
+ import AgentChat from './AgentChat.svelte';
6
+ afterEach(() => cleanup());
7
+ function fakeClient(sendFeedback = vi.fn().mockResolvedValue(undefined)) {
8
+ return {
9
+ fileUrl: (id) => `#${id}`,
10
+ uploadFile: vi.fn(),
11
+ cancel: vi.fn(),
12
+ resumeTurn: vi.fn(),
13
+ sendFeedback,
14
+ getConversation: vi.fn().mockResolvedValue({
15
+ conversation: { conversationId: 'c-1' },
16
+ messages: [
17
+ { id: 'u-r1', seq: 0, role: 'user', content: [{ type: 'text', text: 'Q' }], createdAt: 1 },
18
+ {
19
+ id: 'a-r1',
20
+ seq: 1,
21
+ role: 'assistant',
22
+ content: [{ type: 'text', text: 'A' }],
23
+ createdAt: 2,
24
+ },
25
+ ],
26
+ files: [],
27
+ feedback: { r1: 'up' },
28
+ }),
29
+ streamChat: vi.fn(async (req, opts) => {
30
+ [
31
+ { type: 'start', conversationId: 'c-1', mode: 'live' },
32
+ { type: 'delta', text: 'Answer' },
33
+ { type: 'done', finishReason: 'stop' },
34
+ ].forEach((e, seq) => opts.onEvent({ requestId: req.requestId, seq, ...e }));
35
+ return { kind: 'done' };
36
+ }),
37
+ };
38
+ }
39
+ describe('ChatSession.rate', () => {
40
+ it('loads stored ratings and sends changes', async () => {
41
+ const client = fakeClient();
42
+ const session = new ChatSession({ client: client });
43
+ await session.load('c-1');
44
+ expect(session.messages[1]).toMatchObject({ feedback: 'up' });
45
+ await session.rate('r1', 'down', ' Wrong lot ');
46
+ expect(client.sendFeedback).toHaveBeenCalledWith('c-1', 'r1', {
47
+ rating: 'down',
48
+ comment: 'Wrong lot',
49
+ });
50
+ expect(session.messages[1]).toMatchObject({ feedback: 'down' });
51
+ await session.rate('r1', null);
52
+ expect(session.messages[1]).not.toHaveProperty('feedback');
53
+ });
54
+ it('puts the old rating back when the runtime refuses', async () => {
55
+ const client = fakeClient(vi.fn().mockRejectedValue(new Error('nope')));
56
+ const session = new ChatSession({ client: client });
57
+ await session.load('c-1');
58
+ await expect(session.rate('r1', 'down')).rejects.toThrow('nope');
59
+ expect(session.messages[1]).toMatchObject({ feedback: 'up' });
60
+ });
61
+ });
62
+ describe('AgentChat feedback', () => {
63
+ it('rates an answer, asks for a note after a thumbs down, and toggles off', async () => {
64
+ const user = userEvent.setup();
65
+ const client = fakeClient();
66
+ render(AgentChat, { props: { client: client, sandboxOrigin: 'https://frame.test' } });
67
+ await user.type(screen.getByRole('textbox', { name: 'Message' }), 'hi{Enter}');
68
+ await screen.findAllByText('Answer');
69
+ const requestId = client.streamChat.mock.calls[0][0].requestId;
70
+ const down = screen.getByRole('button', { name: 'Bad answer' });
71
+ await user.click(down);
72
+ expect(client.sendFeedback).toHaveBeenLastCalledWith('c-1', requestId, { rating: 'down' });
73
+ expect(down.getAttribute('aria-pressed')).toBe('true');
74
+ const note = await screen.findByRole('textbox', { name: 'What went wrong?' });
75
+ await waitFor(() => expect(document.activeElement).toBe(note));
76
+ await user.type(note, 'Old prices{Enter}');
77
+ expect(client.sendFeedback).toHaveBeenLastCalledWith('c-1', requestId, {
78
+ rating: 'down',
79
+ comment: 'Old prices',
80
+ });
81
+ expect(screen.queryByRole('textbox', { name: 'What went wrong?' })).toBeNull();
82
+ await user.click(screen.getByRole('button', { name: 'Bad answer' }));
83
+ expect(client.sendFeedback).toHaveBeenLastCalledWith('c-1', requestId, { rating: null });
84
+ expect(screen.getByRole('button', { name: 'Bad answer' }).getAttribute('aria-pressed')).toBe('false');
85
+ });
86
+ it('tells the host when a rating could not be saved', async () => {
87
+ const user = userEvent.setup();
88
+ const onnotify = vi.fn();
89
+ const client = fakeClient(vi.fn().mockRejectedValue(new Error('down')));
90
+ render(AgentChat, {
91
+ props: { client: client, sandboxOrigin: 'https://frame.test', onnotify },
92
+ });
93
+ await user.type(screen.getByRole('textbox', { name: 'Message' }), 'hi{Enter}');
94
+ await screen.findAllByText('Answer');
95
+ await user.click(screen.getByRole('button', { name: 'Good answer' }));
96
+ await waitFor(() => expect(onnotify).toHaveBeenCalledWith(expect.stringMatching(/feedback/)));
97
+ expect(screen.getByRole('button', { name: 'Good answer' }).getAttribute('aria-pressed')).toBe('false');
98
+ });
99
+ });
package/dist/reducer.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ArtifactMediaType, BackendMode, ChatEvent, ClientKind, DocumentContext, FileRef, ProposedOperation, ToolResultStatus } from '@classic-homes/agent-contracts';
1
+ import type { ArtifactMediaType, BackendMode, ChatEvent, ClientKind, DocumentContext, FileRef, ProposedOperation, ToolResultStatus, TurnRating } from '@classic-homes/agent-contracts';
2
2
  import type { ClientErrorCode } from '@classic-homes/agent-client';
3
3
  /**
4
4
  * Pure state model for one conversation view. `applyEvent` folds stream events into the
@@ -78,6 +78,8 @@ export interface AssistantMessage {
78
78
  };
79
79
  finishReason?: 'stop' | 'length' | 'cancelled' | 'error';
80
80
  mode?: BackendMode;
81
+ /** The user's rating of this answer, when given. */
82
+ feedback?: TurnRating;
81
83
  createdAt: number;
82
84
  }
83
85
  export type ChatMessage = UserMessage | AssistantMessage;
@@ -1 +1 @@
1
- {"version":3,"file":"reducer.d.ts","sourceRoot":"","sources":["../src/lib/reducer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"reducer.d.ts","sourceRoot":"","sources":["../src/lib/reducer.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import type { AgentApiClient } from '@classic-homes/agent-client';
2
- import type { AgentKey, BackendMode, ChatEvent, ClientKind, DocumentContext, FileRef } from '@classic-homes/agent-contracts';
2
+ import type { AgentKey, BackendMode, ChatEvent, ClientKind, DocumentContext, FileRef, TurnRating } from '@classic-homes/agent-contracts';
3
3
  import { type AssistantMessage, type ChatMessage } from './reducer.js';
4
4
  export interface ChatSessionOptions {
5
5
  client: AgentApiClient;
@@ -35,6 +35,8 @@ export declare class ChatSession {
35
35
  /** The conversation whose transcript `messages` holds (loaded or started here); null before. */
36
36
  loadedConversationId: string | null;
37
37
  mode: "mock" | "live" | undefined;
38
+ /** Who answers. Set before the first message; a loaded conversation keeps its own agent. */
39
+ agent: string;
38
40
  loadError: string | undefined;
39
41
  pendingFiles: {
40
42
  fileId: string;
@@ -69,6 +71,11 @@ export declare class ChatSession {
69
71
  edit(requestId: string, text: string): Promise<void>;
70
72
  /** Re-attach to a turn after reload: replays events after the last seq we hold (none here). */
71
73
  resume(requestId: string): Promise<void>;
74
+ /**
75
+ * Rate an answer (`null` withdraws the rating). Shown at once; put back and rethrown when the
76
+ * runtime refuses it.
77
+ */
78
+ rate(requestId: string, rating: TurnRating | null, comment?: string): Promise<void>;
72
79
  cancel(): void;
73
80
  private run;
74
81
  private toolName;
@@ -1 +1 @@
1
- {"version":3,"file":"session.svelte.d.ts","sourceRoot":"","sources":["../src/lib/session.svelte.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE,OAAO,KAAK,EACV,QAAQ,EACR,WAAW,EACX,SAAS,EAET,UAAU,EACV,eAAe,EACf,OAAO,EACR,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAML,KAAK,gBAAgB,EACrB,KAAK,WAAW,EACjB,MAAM,cAAc,CAAC;AAEtB,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,cAAc,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,mGAAmG;IACnG,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,eAAe,GAAG,IAAI,GAAG,SAAS,CAAC;IACtD,qBAAqB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,CAAC;IACzD,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACtD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;IACrC;;;OAGG;IACH,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAClG;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,SAAS,GAAG,WAAW,CAAC;AAE7D;;;GAGG;AACH,qBAAa,WAAW;IACtB,QAAQ,gBAA6B;IACrC,MAAM,gBAAiC;IACvC,cAAc,gBAA+B;IAC7C,gGAAgG;IAChG,oBAAoB,gBAA+B;IACnD,IAAI,8BAA8C;IAClD,SAAS,qBAAyC;IAClD,YAAY;;;;;;QAAyB;IAErC,OAAO,CAAC,UAAU,CAA8B;IAChD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;gBAEjC,OAAO,EAAE,kBAAkB;IAMvC,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,IAAI,OAAO,IAAI,gBAAgB,GAAG,SAAS,CAG1C;IAED,yFAAyF;IACnF,IAAI,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA+BjD;;;OAGG;IACG,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IA2B/F;;;OAGG;IACG,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ7C,oFAAoF;IAC9E,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQlD,2EAA2E;IACrE,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ1D,+FAA+F;IACzF,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAa9C,MAAM,IAAI,IAAI;YAOA,GAAG;IA2DjB,OAAO,CAAC,QAAQ;IAOhB,OAAO,CAAC,MAAM;CAKf;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,WAAW,CAE1E"}
1
+ {"version":3,"file":"session.svelte.d.ts","sourceRoot":"","sources":["../src/lib/session.svelte.ts"],"names":[],"mappings":"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"}
@@ -11,6 +11,8 @@ export class ChatSession {
11
11
  /** The conversation whose transcript `messages` holds (loaded or started here); null before. */
12
12
  loadedConversationId = $state(null);
13
13
  mode = $state(undefined);
14
+ /** Who answers. Set before the first message; a loaded conversation keeps its own agent. */
15
+ agent = $state('classic-agent');
14
16
  loadError = $state(undefined);
15
17
  pendingFiles = $state([]);
16
18
  controller;
@@ -19,6 +21,7 @@ export class ChatSession {
19
21
  this.options = options;
20
22
  this.conversationId = options.conversationId ?? null;
21
23
  this.mode = options.mode;
24
+ this.agent = options.agent ?? 'classic-agent';
22
25
  }
23
26
  get streaming() {
24
27
  return this.status === 'streaming';
@@ -47,7 +50,11 @@ export class ChatSession {
47
50
  ...newAssistantMessage(m.id.replace(/^a-/, ''), m.createdAt),
48
51
  text: m.content.map((c) => c.text).join('\n'),
49
52
  status: 'done',
53
+ ...(detail.feedback?.[m.id.replace(/^a-/, '')]
54
+ ? { feedback: detail.feedback[m.id.replace(/^a-/, '')] }
55
+ : {}),
50
56
  });
57
+ this.agent = detail.conversation.agent;
51
58
  const active = detail.conversation.activeRequestId;
52
59
  this.status = 'idle';
53
60
  if (active)
@@ -72,7 +79,7 @@ export class ChatSession {
72
79
  this.pendingFiles = [];
73
80
  const request = {
74
81
  requestId,
75
- agent: this.options.agent ?? 'classic-agent',
82
+ agent: this.agent,
76
83
  message,
77
84
  ...(this.conversationId ? { conversationId: this.conversationId } : {}),
78
85
  ...(context ? { context } : {}),
@@ -133,6 +140,35 @@ export class ChatSession {
133
140
  const conversationId = this.conversationId;
134
141
  await this.run(requestId, (signal, onEvent) => this.options.client.resumeTurn(conversationId, requestId, -1, { signal, onEvent }));
135
142
  }
143
+ /**
144
+ * Rate an answer (`null` withdraws the rating). Shown at once; put back and rethrown when the
145
+ * runtime refuses it.
146
+ */
147
+ async rate(requestId, rating, comment) {
148
+ const conversationId = this.conversationId;
149
+ if (!conversationId)
150
+ return;
151
+ const before = this.messages.find((m) => m.role === 'assistant' && m.requestId === requestId)?.feedback;
152
+ const set = (value) => this.update(requestId, (m) => {
153
+ const next = { ...m };
154
+ if (value)
155
+ next.feedback = value;
156
+ else
157
+ delete next.feedback;
158
+ return next;
159
+ });
160
+ set(rating ?? undefined);
161
+ try {
162
+ await this.options.client.sendFeedback(conversationId, requestId, {
163
+ rating,
164
+ ...(comment?.trim() ? { comment: comment.trim() } : {}),
165
+ });
166
+ }
167
+ catch (err) {
168
+ set(before);
169
+ throw err;
170
+ }
171
+ }
136
172
  cancel() {
137
173
  const current = this.current;
138
174
  if (!this.controller || !current)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classic-homes/agent-chat",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Svelte 5 chat UI for Classic Agent: session state, message list, composer, tool timeline, artifacts and MCP-UI sandbox frames. Consumed by the Classic App.",
@@ -17,16 +17,16 @@
17
17
  "dist"
18
18
  ],
19
19
  "dependencies": {
20
- "@classic-homes/agent-client": "0.6.0",
21
- "@classic-homes/agent-contracts": "0.6.0",
20
+ "@classic-homes/agent-client": "0.8.0",
21
+ "@classic-homes/agent-contracts": "0.8.0",
22
22
  "dompurify": "3.4.17"
23
23
  },
24
24
  "peerDependencies": {
25
- "@classic-homes/theme-svelte": "^1.39.0",
26
- "svelte": "^5.0.0"
25
+ "@classic-homes/theme-svelte": "^1.42.2",
26
+ "svelte": "^5.31.0"
27
27
  },
28
28
  "devDependencies": {
29
- "@classic-homes/theme-svelte": "1.39.0",
29
+ "@classic-homes/theme-svelte": "1.42.2",
30
30
  "@sveltejs/package": "3.0.2",
31
31
  "@sveltejs/vite-plugin-svelte": "7.3.1",
32
32
  "@testing-library/svelte": "5.4.2",