@classic-homes/agent-chat 0.1.3 → 0.2.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.
- package/README.md +40 -0
- package/dist/components/AgentChat.svelte +65 -39
- package/dist/components/AgentChat.svelte.d.ts +2 -0
- package/dist/components/AgentChat.svelte.d.ts.map +1 -1
- package/dist/components/ArtifactPanel.svelte +79 -96
- package/dist/components/ArtifactPanel.svelte.d.ts +2 -0
- package/dist/components/ArtifactPanel.svelte.d.ts.map +1 -1
- package/dist/components/ChatHeader.svelte +122 -0
- package/dist/components/ChatHeader.svelte.d.ts +27 -0
- package/dist/components/ChatHeader.svelte.d.ts.map +1 -0
- package/dist/components/Composer.svelte +78 -121
- package/dist/components/Composer.svelte.d.ts.map +1 -1
- package/dist/components/ConversationDialogs.svelte +134 -0
- package/dist/components/ConversationDialogs.svelte.d.ts +25 -0
- package/dist/components/ConversationDialogs.svelte.d.ts.map +1 -0
- package/dist/components/ConversationSidebar.svelte +135 -114
- package/dist/components/ConversationSidebar.svelte.d.ts +8 -2
- package/dist/components/ConversationSidebar.svelte.d.ts.map +1 -1
- package/dist/components/Markdown.svelte +48 -0
- package/dist/components/Markdown.svelte.d.ts +16 -0
- package/dist/components/Markdown.svelte.d.ts.map +1 -0
- package/dist/components/MessageItem.svelte +27 -116
- package/dist/components/MessageItem.svelte.d.ts.map +1 -1
- package/dist/components/MessageList.svelte +23 -34
- package/dist/components/ModeBanner.svelte +7 -13
- package/dist/components/ModeBanner.svelte.d.ts.map +1 -1
- package/dist/components/SandboxFrame.svelte +43 -0
- package/dist/components/SandboxFrame.svelte.d.ts +13 -0
- package/dist/components/SandboxFrame.svelte.d.ts.map +1 -0
- package/dist/components/ToolTimeline.svelte +17 -68
- package/dist/components/ToolTimeline.svelte.d.ts.map +1 -1
- package/dist/components/UiFrame.svelte +9 -55
- package/dist/components/UiFrame.svelte.d.ts.map +1 -1
- package/dist/components/conversation-chrome.svelte.test.d.ts +2 -0
- package/dist/components/conversation-chrome.svelte.test.d.ts.map +1 -0
- package/dist/components/conversation-chrome.svelte.test.js +127 -0
- package/dist/components/markdown-prose.svelte.test.d.ts +2 -0
- package/dist/components/markdown-prose.svelte.test.d.ts.map +1 -0
- package/dist/components/markdown-prose.svelte.test.js +26 -0
- package/dist/composer-keys.d.ts +11 -0
- package/dist/composer-keys.d.ts.map +1 -0
- package/dist/composer-keys.js +10 -0
- package/dist/composer-keys.test.d.ts +2 -0
- package/dist/composer-keys.test.d.ts.map +1 -0
- package/dist/composer-keys.test.js +16 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/reducer.d.ts +10 -0
- package/dist/reducer.d.ts.map +1 -1
- package/dist/reducer.js +19 -1
- package/dist/reducer.test.js +29 -1
- package/dist/session.svelte.d.ts +6 -0
- package/dist/session.svelte.d.ts.map +1 -1
- package/dist/session.svelte.js +15 -1
- package/dist/ui-frame-protocol.d.ts +2 -2
- package/dist/ui-frame-protocol.js +2 -2
- package/dist/ui-frame-relay.d.ts +16 -0
- package/dist/ui-frame-relay.d.ts.map +1 -0
- package/dist/ui-frame-relay.js +22 -0
- package/dist/ui-frame-relay.test.d.ts +2 -0
- package/dist/ui-frame-relay.test.d.ts.map +1 -0
- package/dist/ui-frame-relay.test.js +36 -0
- package/package.json +8 -5
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
cn,
|
|
5
|
+
DropdownMenu,
|
|
6
|
+
EmptyState,
|
|
7
|
+
Icon,
|
|
8
|
+
Input,
|
|
9
|
+
Skeleton,
|
|
10
|
+
Spinner,
|
|
11
|
+
} from '@classic-homes/theme-svelte';
|
|
3
12
|
import type { ConversationSummary } from '@classic-homes/agent-contracts';
|
|
13
|
+
import ConversationDialogs, { type ConversationRef } from './ConversationDialogs.svelte';
|
|
4
14
|
|
|
5
15
|
interface Props {
|
|
6
16
|
conversations: ConversationSummary[];
|
|
@@ -10,10 +20,16 @@
|
|
|
10
20
|
query?: string;
|
|
11
21
|
onselect?: (conversationId: string) => void;
|
|
12
22
|
onnew?: () => void;
|
|
23
|
+
/** Called with the trimmed query once typing pauses for `searchDelay` ms. */
|
|
13
24
|
onsearch?: (query: string) => void;
|
|
25
|
+
searchDelay?: number;
|
|
14
26
|
onmore?: () => void;
|
|
15
|
-
|
|
16
|
-
|
|
27
|
+
/** May return a promise; a rejection keeps the rename dialog open with its message. */
|
|
28
|
+
onrename?: (conversationId: string, title: string) => void | Promise<void>;
|
|
29
|
+
/** Called after the user confirms; a rejection is passed to `onerror`. */
|
|
30
|
+
ondelete?: (conversationId: string) => void | Promise<void>;
|
|
31
|
+
onerror?: (message: string) => void;
|
|
32
|
+
class?: string;
|
|
17
33
|
}
|
|
18
34
|
let {
|
|
19
35
|
conversations,
|
|
@@ -24,9 +40,12 @@
|
|
|
24
40
|
onselect,
|
|
25
41
|
onnew,
|
|
26
42
|
onsearch,
|
|
43
|
+
searchDelay = 250,
|
|
27
44
|
onmore,
|
|
28
45
|
onrename,
|
|
29
46
|
ondelete,
|
|
47
|
+
onerror,
|
|
48
|
+
class: className,
|
|
30
49
|
}: Props = $props();
|
|
31
50
|
|
|
32
51
|
function groupLabel(iso: string): string {
|
|
@@ -49,140 +68,142 @@
|
|
|
49
68
|
}
|
|
50
69
|
return [...map.entries()];
|
|
51
70
|
});
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
71
|
+
let renaming = $state<ConversationRef | null>(null);
|
|
72
|
+
let deleting = $state<ConversationRef | null>(null);
|
|
73
|
+
let navEl = $state<HTMLElement | null>(null);
|
|
74
|
+
let returnFocus = $state<HTMLElement | null>(null);
|
|
75
|
+
/** Remember the row's menu trigger before a dialog takes focus from the closing menu. */
|
|
76
|
+
function open(id: string, set: () => void): void {
|
|
77
|
+
returnFocus =
|
|
78
|
+
navEl?.querySelector<HTMLElement>(
|
|
79
|
+
`[data-conversation-id="${CSS.escape(id)}"] [aria-haspopup="menu"]`,
|
|
80
|
+
) ?? null;
|
|
81
|
+
set();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let searchTimer: ReturnType<typeof setTimeout> | undefined;
|
|
85
|
+
function search(): void {
|
|
86
|
+
clearTimeout(searchTimer);
|
|
87
|
+
searchTimer = setTimeout(() => onsearch?.(query.trim()), searchDelay);
|
|
88
|
+
}
|
|
89
|
+
$effect(() => () => clearTimeout(searchTimer));
|
|
90
|
+
|
|
91
|
+
function rowItems(c: ConversationSummary) {
|
|
92
|
+
const ref = { conversationId: c.conversationId, title: c.title };
|
|
93
|
+
return [
|
|
94
|
+
...(onrename
|
|
95
|
+
? [
|
|
96
|
+
{
|
|
97
|
+
label: 'Rename',
|
|
98
|
+
icon: 'edit' as const,
|
|
99
|
+
onSelect: () => open(c.conversationId, () => (renaming = ref)),
|
|
100
|
+
},
|
|
101
|
+
]
|
|
102
|
+
: []),
|
|
103
|
+
...(ondelete
|
|
104
|
+
? [
|
|
105
|
+
{
|
|
106
|
+
label: 'Delete',
|
|
107
|
+
icon: 'trash' as const,
|
|
108
|
+
destructive: true,
|
|
109
|
+
onSelect: () => open(c.conversationId, () => (deleting = ref)),
|
|
110
|
+
},
|
|
111
|
+
]
|
|
112
|
+
: []),
|
|
113
|
+
];
|
|
55
114
|
}
|
|
56
115
|
</script>
|
|
57
116
|
|
|
58
|
-
<nav
|
|
59
|
-
|
|
117
|
+
<nav
|
|
118
|
+
bind:this={navEl}
|
|
119
|
+
class={cn('flex h-full w-72 flex-col border-r border-border bg-card', className)}
|
|
120
|
+
aria-label="Conversations"
|
|
121
|
+
>
|
|
122
|
+
<div class="grid gap-2 p-3">
|
|
60
123
|
<Button size="sm" onclick={() => onnew?.()}><Icon name="plus" size={14} /> New chat</Button>
|
|
61
|
-
<
|
|
62
|
-
class="agent-sidebar__search"
|
|
124
|
+
<Input
|
|
63
125
|
type="search"
|
|
64
|
-
|
|
126
|
+
class="h-9"
|
|
127
|
+
placeholder="Search conversations"
|
|
65
128
|
aria-label="Search conversations"
|
|
66
129
|
bind:value={query}
|
|
67
|
-
oninput={
|
|
130
|
+
oninput={search}
|
|
68
131
|
/>
|
|
69
132
|
</div>
|
|
70
|
-
<div class="
|
|
133
|
+
<div class="flex-1 overflow-y-auto px-2 pb-3">
|
|
134
|
+
{#if loading && conversations.length === 0}
|
|
135
|
+
<div class="grid gap-2 px-2 pt-3" aria-busy="true" aria-label="Loading conversations">
|
|
136
|
+
{#each [80, 65, 90, 55, 75] as width, i (i)}
|
|
137
|
+
<Skeleton variant="text" class="h-7" style={`width:${width}%`} />
|
|
138
|
+
{/each}
|
|
139
|
+
</div>
|
|
140
|
+
{:else if conversations.length === 0}
|
|
141
|
+
<EmptyState
|
|
142
|
+
size="sm"
|
|
143
|
+
icon={query.trim() ? 'search' : 'chat'}
|
|
144
|
+
headingLevel="p"
|
|
145
|
+
title={query.trim() ? 'No matching conversations' : 'No conversations yet'}
|
|
146
|
+
description={query.trim() ? 'Try another search.' : 'Start a chat to see it here.'}
|
|
147
|
+
class="py-8"
|
|
148
|
+
/>
|
|
149
|
+
{/if}
|
|
71
150
|
{#each groups as [label, items] (label)}
|
|
72
|
-
<div
|
|
151
|
+
<div
|
|
152
|
+
class="sticky top-0 z-[1] bg-card px-2 pb-1 pt-3 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground"
|
|
153
|
+
>
|
|
154
|
+
{label}
|
|
155
|
+
</div>
|
|
73
156
|
{#each items as c (c.conversationId)}
|
|
157
|
+
{@const active = c.conversationId === selectedId}
|
|
158
|
+
{@const menu = rowItems(c)}
|
|
74
159
|
<div
|
|
75
|
-
|
|
76
|
-
class
|
|
160
|
+
data-conversation-id={c.conversationId}
|
|
161
|
+
class={cn(
|
|
162
|
+
'group flex items-center gap-1 rounded-md pr-1 hover:bg-muted',
|
|
163
|
+
active && 'bg-muted font-medium',
|
|
164
|
+
)}
|
|
77
165
|
>
|
|
78
166
|
<button
|
|
79
167
|
type="button"
|
|
80
|
-
class="
|
|
168
|
+
class="flex min-w-0 flex-1 items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
169
|
+
aria-current={active ? 'page' : undefined}
|
|
81
170
|
onclick={() => onselect?.(c.conversationId)}
|
|
82
171
|
title={c.title}
|
|
83
172
|
>
|
|
84
|
-
{c.title}
|
|
173
|
+
<span class="truncate">{c.title}</span>
|
|
174
|
+
{#if c.activeRequestId}<Spinner
|
|
175
|
+
size="sm"
|
|
176
|
+
class="size-3 shrink-0"
|
|
177
|
+
label="Answering"
|
|
178
|
+
/>{/if}
|
|
85
179
|
</button>
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
180
|
+
{#if menu.length}
|
|
181
|
+
<DropdownMenu
|
|
182
|
+
items={menu}
|
|
183
|
+
align="end"
|
|
184
|
+
triggerVariant="ghost"
|
|
185
|
+
triggerSize="icon"
|
|
186
|
+
triggerLabel={`Options for ${c.title}`}
|
|
187
|
+
triggerClass={cn(
|
|
188
|
+
'size-7 shrink-0 text-muted-foreground opacity-0 focus-visible:opacity-100 group-focus-within:opacity-100 group-hover:opacity-100 data-[state=open]:opacity-100 [@media(hover:none)]:opacity-100',
|
|
189
|
+
active && 'opacity-100',
|
|
190
|
+
)}
|
|
191
|
+
showChevron={false}
|
|
192
|
+
>
|
|
193
|
+
{#snippet trigger()}<Icon name="more-horizontal" size={16} />{/snippet}
|
|
194
|
+
</DropdownMenu>
|
|
195
|
+
{/if}
|
|
96
196
|
</div>
|
|
97
197
|
{/each}
|
|
98
198
|
{/each}
|
|
99
|
-
{#if
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
199
|
+
{#if hasMore}<Button
|
|
200
|
+
variant="ghost"
|
|
201
|
+
size="sm"
|
|
202
|
+
class="w-full"
|
|
203
|
+
{loading}
|
|
204
|
+
onclick={() => onmore?.()}>Load more</Button
|
|
104
205
|
>{/if}
|
|
105
206
|
</div>
|
|
106
207
|
</nav>
|
|
107
208
|
|
|
108
|
-
<
|
|
109
|
-
.agent-sidebar {
|
|
110
|
-
display: flex;
|
|
111
|
-
flex-direction: column;
|
|
112
|
-
height: 100%;
|
|
113
|
-
border-right: 1px solid hsl(var(--border));
|
|
114
|
-
background: hsl(var(--card));
|
|
115
|
-
width: 16rem;
|
|
116
|
-
}
|
|
117
|
-
.agent-sidebar__top {
|
|
118
|
-
display: grid;
|
|
119
|
-
gap: 0.5rem;
|
|
120
|
-
padding: 0.75rem;
|
|
121
|
-
}
|
|
122
|
-
.agent-sidebar__search {
|
|
123
|
-
font: inherit;
|
|
124
|
-
font-size: 0.875rem;
|
|
125
|
-
padding: 0.375rem 0.625rem;
|
|
126
|
-
border: 1px solid hsl(var(--border));
|
|
127
|
-
border-radius: 0.5rem;
|
|
128
|
-
background: hsl(var(--background));
|
|
129
|
-
color: inherit;
|
|
130
|
-
}
|
|
131
|
-
.agent-sidebar__list {
|
|
132
|
-
flex: 1;
|
|
133
|
-
overflow-y: auto;
|
|
134
|
-
padding: 0 0.5rem 0.75rem;
|
|
135
|
-
}
|
|
136
|
-
.agent-sidebar__group {
|
|
137
|
-
font-size: 0.6875rem;
|
|
138
|
-
text-transform: uppercase;
|
|
139
|
-
letter-spacing: 0.06em;
|
|
140
|
-
color: hsl(var(--muted-foreground));
|
|
141
|
-
padding: 0.75rem 0.5rem 0.25rem;
|
|
142
|
-
}
|
|
143
|
-
.agent-sidebar__row {
|
|
144
|
-
display: flex;
|
|
145
|
-
align-items: center;
|
|
146
|
-
border-radius: 0.5rem;
|
|
147
|
-
}
|
|
148
|
-
.agent-sidebar__row:hover,
|
|
149
|
-
.agent-sidebar__row--active {
|
|
150
|
-
background: hsl(var(--muted));
|
|
151
|
-
}
|
|
152
|
-
.agent-sidebar__title {
|
|
153
|
-
flex: 1;
|
|
154
|
-
min-width: 0;
|
|
155
|
-
text-align: left;
|
|
156
|
-
background: none;
|
|
157
|
-
border: 0;
|
|
158
|
-
padding: 0.375rem 0.5rem;
|
|
159
|
-
font: inherit;
|
|
160
|
-
font-size: 0.875rem;
|
|
161
|
-
color: inherit;
|
|
162
|
-
cursor: pointer;
|
|
163
|
-
overflow: hidden;
|
|
164
|
-
text-overflow: ellipsis;
|
|
165
|
-
white-space: nowrap;
|
|
166
|
-
}
|
|
167
|
-
.agent-sidebar__actions {
|
|
168
|
-
display: none;
|
|
169
|
-
gap: 0.125rem;
|
|
170
|
-
padding-right: 0.25rem;
|
|
171
|
-
}
|
|
172
|
-
.agent-sidebar__row:hover .agent-sidebar__actions,
|
|
173
|
-
.agent-sidebar__row--active .agent-sidebar__actions {
|
|
174
|
-
display: flex;
|
|
175
|
-
}
|
|
176
|
-
.agent-sidebar__actions button {
|
|
177
|
-
border: 0;
|
|
178
|
-
background: none;
|
|
179
|
-
cursor: pointer;
|
|
180
|
-
color: hsl(var(--muted-foreground));
|
|
181
|
-
padding: 0.25rem;
|
|
182
|
-
}
|
|
183
|
-
.agent-sidebar__empty {
|
|
184
|
-
font-size: 0.8125rem;
|
|
185
|
-
color: hsl(var(--muted-foreground));
|
|
186
|
-
padding: 0.5rem;
|
|
187
|
-
}
|
|
188
|
-
</style>
|
|
209
|
+
<ConversationDialogs bind:renaming bind:deleting {onrename} {ondelete} {onerror} {returnFocus} />
|
|
@@ -7,10 +7,16 @@ interface Props {
|
|
|
7
7
|
query?: string;
|
|
8
8
|
onselect?: (conversationId: string) => void;
|
|
9
9
|
onnew?: () => void;
|
|
10
|
+
/** Called with the trimmed query once typing pauses for `searchDelay` ms. */
|
|
10
11
|
onsearch?: (query: string) => void;
|
|
12
|
+
searchDelay?: number;
|
|
11
13
|
onmore?: () => void;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
/** May return a promise; a rejection keeps the rename dialog open with its message. */
|
|
15
|
+
onrename?: (conversationId: string, title: string) => void | Promise<void>;
|
|
16
|
+
/** Called after the user confirms; a rejection is passed to `onerror`. */
|
|
17
|
+
ondelete?: (conversationId: string) => void | Promise<void>;
|
|
18
|
+
onerror?: (message: string) => void;
|
|
19
|
+
class?: string;
|
|
14
20
|
}
|
|
15
21
|
declare const ConversationSidebar: import("svelte").Component<Props, {}, "query">;
|
|
16
22
|
type ConversationSidebar = ReturnType<typeof ConversationSidebar>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConversationSidebar.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/ConversationSidebar.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ConversationSidebar.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/ConversationSidebar.svelte.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAIxE,UAAU,KAAK;IACb,aAAa,EAAE,mBAAmB,EAAE,CAAC;IACrC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IACnB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,uFAAuF;IACvF,QAAQ,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3E,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA0IH,QAAA,MAAM,mBAAmB,gDAAwC,CAAC;AAClE,KAAK,mBAAmB,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAClE,eAAe,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component
|
|
3
|
+
Renders sanitized markdown HTML from `parseMarkdown`, with the chat prose rules. The rules are
|
|
4
|
+
Tailwind arbitrary variants on the container rather than a stylesheet: the Classic App's
|
|
5
|
+
cascade-layers polyfill raises the specificity of its base styles above any plain component
|
|
6
|
+
CSS, while utilities still win. Values follow theme-docs `.markdown-content`, sized for chat.
|
|
7
|
+
-->
|
|
8
|
+
<script lang="ts" module>
|
|
9
|
+
const PROSE = [
|
|
10
|
+
'agent-markdown leading-[1.65] text-foreground [overflow-wrap:anywhere]',
|
|
11
|
+
'[&>:first-child]:mt-0 [&>:last-child]:mb-0',
|
|
12
|
+
'[&_p]:mb-3 [&_p]:mt-0 [&_p:empty]:hidden',
|
|
13
|
+
'[&_h2]:mb-2 [&_h2]:mt-5 [&_h2]:text-[1.25em] [&_h2]:font-semibold [&_h2]:leading-snug',
|
|
14
|
+
'[&_h3]:mb-2 [&_h3]:mt-5 [&_h3]:text-[1.125em] [&_h3]:font-semibold [&_h3]:leading-snug',
|
|
15
|
+
'[&_h4]:mb-2 [&_h4]:mt-4 [&_h4]:text-[1em] [&_h4]:font-semibold [&_h4]:leading-snug',
|
|
16
|
+
'[&_a]:text-primary [&_a]:underline [&_a]:underline-offset-2 [&_a:hover]:text-primary/80',
|
|
17
|
+
'[&_ul]:mb-3 [&_ul]:list-disc [&_ul]:pl-6 [&_ol]:mb-3 [&_ol]:list-decimal [&_ol]:pl-6',
|
|
18
|
+
'[&_li]:mb-1 [&_li>ul]:mb-0 [&_li>ul]:mt-1 [&_li>ol]:mb-0 [&_li>ol]:mt-1',
|
|
19
|
+
'[&_ul.agent-tasklist]:list-none [&_ul.agent-tasklist]:pl-1',
|
|
20
|
+
'[&_.agent-tasklist_input]:mr-1.5 [&_.agent-tasklist_input]:accent-primary',
|
|
21
|
+
'[&_code]:rounded [&_code]:bg-muted [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:font-mono [&_code]:text-[0.875em]',
|
|
22
|
+
'[&_pre]:mb-3 [&_pre]:overflow-x-auto [&_pre]:rounded-lg [&_pre]:border [&_pre]:border-border [&_pre]:bg-muted/60 [&_pre]:px-4 [&_pre]:py-3 [&_pre]:text-[13px] [&_pre]:leading-relaxed',
|
|
23
|
+
'[&_pre_code]:rounded-none [&_pre_code]:bg-transparent [&_pre_code]:p-0 [&_pre_code]:text-[length:inherit]',
|
|
24
|
+
'[&_blockquote]:mb-3 [&_blockquote]:border-l-4 [&_blockquote]:border-primary [&_blockquote]:bg-muted/50 [&_blockquote]:px-4 [&_blockquote]:py-2 [&_blockquote]:text-muted-foreground',
|
|
25
|
+
'[&_hr]:my-5 [&_hr]:border-border',
|
|
26
|
+
'[&_table]:mb-3 [&_table]:block [&_table]:max-w-full [&_table]:border-collapse [&_table]:overflow-x-auto [&_table]:text-[0.875em]',
|
|
27
|
+
'[&_th]:border [&_th]:border-border [&_th]:px-3 [&_th]:py-1.5 [&_th]:text-left [&_th]:align-top',
|
|
28
|
+
'[&_td]:border [&_td]:border-border [&_td]:px-3 [&_td]:py-1.5 [&_td]:text-left [&_td]:align-top',
|
|
29
|
+
'[&_th]:bg-muted [&_th]:font-semibold',
|
|
30
|
+
'[&_img]:max-w-full [&_img]:rounded-lg',
|
|
31
|
+
].join(' ');
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<script lang="ts">
|
|
35
|
+
import { cn } from '@classic-homes/theme-svelte';
|
|
36
|
+
|
|
37
|
+
interface Props {
|
|
38
|
+
/** Output of `parseMarkdown` (already sanitized by DOMPurify). */
|
|
39
|
+
html: string;
|
|
40
|
+
class?: string;
|
|
41
|
+
onclick?: (event: MouseEvent) => void;
|
|
42
|
+
}
|
|
43
|
+
let { html, class: className, onclick }: Props = $props();
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<!-- Clicks are delegated to internal links inside the HTML; the links themselves are focusable. -->
|
|
47
|
+
<!-- svelte-ignore a11y_no_static_element_interactions, a11y_click_events_have_key_events -->
|
|
48
|
+
<div class={className ? `${PROSE} ${cn(className)}` : PROSE} {onclick}>{@html html}</div>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/** Output of `parseMarkdown` (already sanitized by DOMPurify). */
|
|
3
|
+
html: string;
|
|
4
|
+
class?: string;
|
|
5
|
+
onclick?: (event: MouseEvent) => void;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Renders sanitized markdown HTML from `parseMarkdown`, with the chat prose rules. The rules are
|
|
9
|
+
* Tailwind arbitrary variants on the container rather than a stylesheet: the Classic App's
|
|
10
|
+
* cascade-layers polyfill raises the specificity of its base styles above any plain component
|
|
11
|
+
* CSS, while utilities still win. Values follow theme-docs `.markdown-content`, sized for chat.
|
|
12
|
+
*/
|
|
13
|
+
declare const Markdown: import("svelte").Component<Props, {}, "">;
|
|
14
|
+
type Markdown = ReturnType<typeof Markdown>;
|
|
15
|
+
export default Markdown;
|
|
16
|
+
//# sourceMappingURL=Markdown.svelte.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Markdown.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/Markdown.svelte.ts"],"names":[],"mappings":"AA+BE,UAAU,KAAK;IACb,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;CACvC;AAgBH;;;;;GAKG;AACH,QAAA,MAAM,QAAQ,2CAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { Button, Icon } from '@classic-homes/theme-svelte';
|
|
3
|
+
import Markdown from './Markdown.svelte';
|
|
3
4
|
import type { ChatMessage } from '../reducer.js';
|
|
4
5
|
import { errorTitle } from '../reducer.js';
|
|
5
6
|
import { parseMarkdown } from '../markdown.js';
|
|
@@ -78,14 +79,18 @@
|
|
|
78
79
|
</script>
|
|
79
80
|
|
|
80
81
|
{#if message.role === 'user'}
|
|
81
|
-
<div class="
|
|
82
|
-
{#if message.contextLabel}<div class="
|
|
82
|
+
<div class="ml-auto flex max-w-[85%] flex-col items-end gap-1 px-4 py-2">
|
|
83
|
+
{#if message.contextLabel}<div class="text-xs text-muted-foreground">
|
|
83
84
|
Context: {message.contextLabel}
|
|
84
85
|
</div>{/if}
|
|
85
|
-
<div
|
|
86
|
+
<div
|
|
87
|
+
class="whitespace-pre-wrap rounded-2xl rounded-br-md bg-primary px-3.5 py-2 text-primary-foreground [overflow-wrap:anywhere]"
|
|
88
|
+
>
|
|
89
|
+
{message.text}
|
|
90
|
+
</div>
|
|
86
91
|
{#if message.attachments.length}
|
|
87
|
-
<ul class="
|
|
88
|
-
{#each message.attachments as file (file.fileId)}<li>
|
|
92
|
+
<ul class="flex flex-wrap justify-end gap-2 text-xs text-muted-foreground">
|
|
93
|
+
{#each message.attachments as file (file.fileId)}<li class="inline-flex items-center gap-1">
|
|
89
94
|
<Icon name="file" size={12} />
|
|
90
95
|
{file.name}
|
|
91
96
|
</li>{/each}
|
|
@@ -93,15 +98,12 @@
|
|
|
93
98
|
{/if}
|
|
94
99
|
</div>
|
|
95
100
|
{:else}
|
|
96
|
-
<div class="
|
|
101
|
+
<div class="min-w-0 px-4 py-2" data-status={message.status}>
|
|
97
102
|
<ToolTimeline tools={message.tools} delegations={message.delegations} {streaming} />
|
|
98
|
-
|
|
99
|
-
<
|
|
100
|
-
{@html html}
|
|
101
|
-
{#if streaming && !message.text}<span class="agent-msg__thinking">Thinking…</span>{/if}
|
|
102
|
-
</div>
|
|
103
|
+
{#if html}<Markdown {html} onclick={handleClick} />{/if}
|
|
104
|
+
{#if streaming && !message.text}<span class="italic text-muted-foreground">Thinking…</span>{/if}
|
|
103
105
|
{#if message.artifacts.length}
|
|
104
|
-
<div class="
|
|
106
|
+
<div class="my-2 flex flex-wrap gap-1.5">
|
|
105
107
|
{#each message.artifacts as a (a.artifactId)}
|
|
106
108
|
<Button variant="outline" size="sm" onclick={() => onopenartifact?.(a.artifactId)}>
|
|
107
109
|
<Icon name="file-text" size={14} />
|
|
@@ -114,18 +116,21 @@
|
|
|
114
116
|
<UiFrame {resource} {sandboxOrigin} {onprompt} {ontool} {onnotify} />
|
|
115
117
|
{/each}
|
|
116
118
|
{#if message.files.length}
|
|
117
|
-
<ul class="
|
|
119
|
+
<ul class="mt-1 flex flex-wrap gap-2 text-sm">
|
|
118
120
|
{#each message.files as file (file.fileId)}
|
|
119
121
|
<li>
|
|
120
122
|
{#if file.mimeType.startsWith('image/')}
|
|
121
123
|
<img
|
|
122
|
-
class="
|
|
124
|
+
class="mb-1 block max-h-96 max-w-full rounded-lg"
|
|
123
125
|
src={fileUrl(file.fileId)}
|
|
124
126
|
alt={file.name}
|
|
125
127
|
loading="lazy"
|
|
126
128
|
/>
|
|
127
129
|
{/if}
|
|
128
|
-
<a
|
|
130
|
+
<a
|
|
131
|
+
class="inline-flex items-center gap-1 text-primary underline-offset-2 hover:underline"
|
|
132
|
+
href={fileUrl(file.fileId)}
|
|
133
|
+
download={file.name}
|
|
129
134
|
><Icon name="download" size={14} /> {file.name} ({formatBytes(file.bytes)})</a
|
|
130
135
|
>
|
|
131
136
|
</li>
|
|
@@ -133,14 +138,17 @@
|
|
|
133
138
|
</ul>
|
|
134
139
|
{/if}
|
|
135
140
|
{#if message.proposals.length}
|
|
136
|
-
<div class="
|
|
141
|
+
<div class="mt-1 text-xs text-muted-foreground">
|
|
137
142
|
Proposed {message.proposals.length} document edit{message.proposals.length === 1
|
|
138
143
|
? ''
|
|
139
144
|
: 's'}; apply them from the Excel or Word add-in.
|
|
140
145
|
</div>
|
|
141
146
|
{/if}
|
|
142
147
|
{#if message.error}
|
|
143
|
-
<div
|
|
148
|
+
<div
|
|
149
|
+
class="mt-2 rounded-lg bg-destructive/10 px-3 py-2 text-sm text-destructive"
|
|
150
|
+
role="alert"
|
|
151
|
+
>
|
|
144
152
|
<strong>{errorTitle(message.error.code)}.</strong>
|
|
145
153
|
{message.error.message}
|
|
146
154
|
{#if message.error.retryable && onretry}<Button variant="link" size="sm" onclick={onretry}
|
|
@@ -148,106 +156,9 @@
|
|
|
148
156
|
>{/if}
|
|
149
157
|
</div>
|
|
150
158
|
{:else if message.status === 'cancelled'}
|
|
151
|
-
<div class="
|
|
159
|
+
<div class="mt-1 text-xs text-muted-foreground">Stopped.</div>
|
|
152
160
|
{:else if message.status === 'incomplete'}
|
|
153
|
-
<div class="
|
|
161
|
+
<div class="mt-1 text-xs text-muted-foreground">The answer was cut short.</div>
|
|
154
162
|
{/if}
|
|
155
163
|
</div>
|
|
156
164
|
{/if}
|
|
157
|
-
|
|
158
|
-
<style>
|
|
159
|
-
.agent-msg {
|
|
160
|
-
padding: 0.5rem 1rem;
|
|
161
|
-
max-width: 48rem;
|
|
162
|
-
}
|
|
163
|
-
.agent-msg--user {
|
|
164
|
-
margin-left: auto;
|
|
165
|
-
display: flex;
|
|
166
|
-
flex-direction: column;
|
|
167
|
-
align-items: flex-end;
|
|
168
|
-
}
|
|
169
|
-
.agent-msg__bubble {
|
|
170
|
-
background: hsl(var(--primary));
|
|
171
|
-
color: hsl(var(--primary-foreground));
|
|
172
|
-
border-radius: 1rem 1rem 0.25rem 1rem;
|
|
173
|
-
padding: 0.5rem 0.875rem;
|
|
174
|
-
white-space: pre-wrap;
|
|
175
|
-
overflow-wrap: anywhere;
|
|
176
|
-
}
|
|
177
|
-
.agent-msg__context,
|
|
178
|
-
.agent-msg__attachments,
|
|
179
|
-
.agent-msg__note {
|
|
180
|
-
font-size: 0.75rem;
|
|
181
|
-
color: hsl(var(--muted-foreground));
|
|
182
|
-
}
|
|
183
|
-
.agent-msg__attachments,
|
|
184
|
-
.agent-msg__files {
|
|
185
|
-
list-style: none;
|
|
186
|
-
padding: 0;
|
|
187
|
-
margin: 0.25rem 0 0;
|
|
188
|
-
display: flex;
|
|
189
|
-
flex-wrap: wrap;
|
|
190
|
-
gap: 0.5rem;
|
|
191
|
-
}
|
|
192
|
-
.agent-msg__chips {
|
|
193
|
-
display: flex;
|
|
194
|
-
flex-wrap: wrap;
|
|
195
|
-
gap: 0.375rem;
|
|
196
|
-
margin: 0.5rem 0;
|
|
197
|
-
}
|
|
198
|
-
.agent-msg__image {
|
|
199
|
-
display: block;
|
|
200
|
-
max-width: 100%;
|
|
201
|
-
max-height: 24rem;
|
|
202
|
-
border-radius: 0.5rem;
|
|
203
|
-
margin-bottom: 0.25rem;
|
|
204
|
-
}
|
|
205
|
-
.agent-msg__thinking {
|
|
206
|
-
color: hsl(var(--muted-foreground));
|
|
207
|
-
font-style: italic;
|
|
208
|
-
}
|
|
209
|
-
.agent-msg__error {
|
|
210
|
-
margin-top: 0.5rem;
|
|
211
|
-
padding: 0.5rem 0.75rem;
|
|
212
|
-
border-radius: 0.5rem;
|
|
213
|
-
background: hsl(var(--destructive) / 0.1);
|
|
214
|
-
color: hsl(var(--destructive));
|
|
215
|
-
font-size: 0.875rem;
|
|
216
|
-
}
|
|
217
|
-
:global(.agent-markdown) {
|
|
218
|
-
overflow-wrap: anywhere;
|
|
219
|
-
line-height: 1.6;
|
|
220
|
-
}
|
|
221
|
-
:global(.agent-markdown pre) {
|
|
222
|
-
background: hsl(var(--muted));
|
|
223
|
-
padding: 0.75rem;
|
|
224
|
-
border-radius: 0.5rem;
|
|
225
|
-
overflow: auto;
|
|
226
|
-
font-size: 0.8125rem;
|
|
227
|
-
}
|
|
228
|
-
:global(.agent-markdown code) {
|
|
229
|
-
font-family: ui-monospace, monospace;
|
|
230
|
-
font-size: 0.875em;
|
|
231
|
-
}
|
|
232
|
-
:global(.agent-markdown table) {
|
|
233
|
-
border-collapse: collapse;
|
|
234
|
-
margin: 0.5rem 0;
|
|
235
|
-
font-size: 0.875rem;
|
|
236
|
-
}
|
|
237
|
-
:global(.agent-markdown th),
|
|
238
|
-
:global(.agent-markdown td) {
|
|
239
|
-
border: 1px solid hsl(var(--border));
|
|
240
|
-
padding: 0.25rem 0.5rem;
|
|
241
|
-
text-align: left;
|
|
242
|
-
}
|
|
243
|
-
:global(.agent-markdown blockquote) {
|
|
244
|
-
border-left: 3px solid hsl(var(--border));
|
|
245
|
-
margin: 0.5rem 0;
|
|
246
|
-
padding-left: 0.75rem;
|
|
247
|
-
color: hsl(var(--muted-foreground));
|
|
248
|
-
}
|
|
249
|
-
:global(.agent-markdown img) {
|
|
250
|
-
max-width: 100%;
|
|
251
|
-
border-radius: 0.5rem;
|
|
252
|
-
}
|
|
253
|
-
</style>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MessageItem.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/MessageItem.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"MessageItem.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/MessageItem.svelte.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAO/C,UAAU,KAAK;IACb,OAAO,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,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;CACtB;AA2IH,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
|