@budibase/frontend-core 3.34.1 → 3.34.3
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/frontend-core",
|
|
3
|
-
"version": "3.34.
|
|
3
|
+
"version": "3.34.3",
|
|
4
4
|
"description": "Budibase frontend core libraries used in builder and client",
|
|
5
5
|
"author": "Budibase",
|
|
6
6
|
"license": "MPL-2.0",
|
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
"test:watch": "vitest"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@ai-sdk/svelte": "^4.0.
|
|
14
|
+
"@ai-sdk/svelte": "^4.0.116",
|
|
15
15
|
"@budibase/bbui": "*",
|
|
16
16
|
"@budibase/shared-core": "*",
|
|
17
17
|
"@budibase/types": "*",
|
|
18
|
-
"ai": "^6.0.
|
|
18
|
+
"ai": "^6.0.116",
|
|
19
19
|
"dayjs": "^1.10.8",
|
|
20
20
|
"lodash": "4.17.23",
|
|
21
21
|
"shortid": "2.2.15",
|
|
@@ -24,5 +24,5 @@
|
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"vitest": "^3.2.4"
|
|
26
26
|
},
|
|
27
|
-
"gitHead": "
|
|
27
|
+
"gitHead": "9c56751662ed542fe5398a29d207dd55198c9f83"
|
|
28
28
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { createEventDispatcher, onMount } from "svelte"
|
|
3
|
-
import { Body,
|
|
3
|
+
import { Body, Icon } from "@budibase/bbui"
|
|
4
4
|
import type { ChatConversation, DraftChatConversation } from "@budibase/types"
|
|
5
5
|
import Chatbox from "./index.svelte"
|
|
6
6
|
|
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export let selectedAgentId: string | null = null
|
|
24
|
-
export let selectedAgentName: string = ""
|
|
25
24
|
export let enabledAgentList: EnabledAgentListItem[] = []
|
|
26
25
|
export let conversationStarters: { prompt: string }[] = []
|
|
27
26
|
export let agentAvailability: AgentAvailability = "ready"
|
|
@@ -29,14 +28,12 @@
|
|
|
29
28
|
export let chat: ChatConversationLike
|
|
30
29
|
export let loading: boolean = false
|
|
31
30
|
export let suppressAgentPicker: boolean = false
|
|
32
|
-
export let deletingChat: boolean = false
|
|
33
31
|
export let workspaceId: string
|
|
34
32
|
export let initialPrompt: string = ""
|
|
35
33
|
export let userName: string = ""
|
|
36
34
|
|
|
37
35
|
const dispatch = createEventDispatcher<{
|
|
38
36
|
chatSaved: { chatId?: string; chat: ChatConversationLike }
|
|
39
|
-
deleteChat: undefined
|
|
40
37
|
agentSelected: { agentId: string }
|
|
41
38
|
startChat: { agentId: string; prompt: string }
|
|
42
39
|
}>()
|
|
@@ -83,10 +80,6 @@
|
|
|
83
80
|
|
|
84
81
|
$: readOnlyReason = getReadOnlyReason(agentAvailability)
|
|
85
82
|
|
|
86
|
-
const deleteChat = () => {
|
|
87
|
-
dispatch("deleteChat")
|
|
88
|
-
}
|
|
89
|
-
|
|
90
83
|
const selectAgent = (agentId: string) => {
|
|
91
84
|
dispatch("agentSelected", { agentId })
|
|
92
85
|
}
|
|
@@ -122,32 +115,13 @@
|
|
|
122
115
|
|
|
123
116
|
<div class="chat-wrapper">
|
|
124
117
|
{#if selectedAgentId}
|
|
125
|
-
|
|
126
|
-
<div class="chat-header
|
|
127
|
-
<
|
|
128
|
-
|
|
129
|
-
</
|
|
118
|
+
{#if hasChatId(chat)}
|
|
119
|
+
<div class="chat-header">
|
|
120
|
+
<div class="chat-header-title">
|
|
121
|
+
<Body size="S">{chat.title || "Untitled Chat"}</Body>
|
|
122
|
+
</div>
|
|
130
123
|
</div>
|
|
131
|
-
|
|
132
|
-
{#if hasChatId(chat)}
|
|
133
|
-
<Button
|
|
134
|
-
quiet
|
|
135
|
-
warning
|
|
136
|
-
disabled={deletingChat || loading}
|
|
137
|
-
on:click={deleteChat}
|
|
138
|
-
>
|
|
139
|
-
<span class="delete-button-content">
|
|
140
|
-
{#if deletingChat}
|
|
141
|
-
<ProgressCircle size="S" />
|
|
142
|
-
Deleting...
|
|
143
|
-
{:else}
|
|
144
|
-
<Icon name="trash" size="S" />
|
|
145
|
-
Delete chat
|
|
146
|
-
{/if}
|
|
147
|
-
</span>
|
|
148
|
-
</Button>
|
|
149
|
-
{/if}
|
|
150
|
-
</div>
|
|
124
|
+
{/if}
|
|
151
125
|
|
|
152
126
|
<Chatbox
|
|
153
127
|
bind:chat
|
|
@@ -161,7 +135,7 @@
|
|
|
161
135
|
{:else if !suppressAgentPicker}
|
|
162
136
|
<div class="chat-empty">
|
|
163
137
|
<div class="chat-empty-greeting">
|
|
164
|
-
<Body size="XL" weight="600"
|
|
138
|
+
<Body size="XL" weight="600">
|
|
165
139
|
{greetingText}
|
|
166
140
|
</Body>
|
|
167
141
|
</div>
|
|
@@ -247,12 +221,15 @@
|
|
|
247
221
|
border-bottom: var(--border-light);
|
|
248
222
|
}
|
|
249
223
|
|
|
250
|
-
.chat-header-
|
|
251
|
-
|
|
252
|
-
|
|
224
|
+
.chat-header-title {
|
|
225
|
+
min-width: 0;
|
|
226
|
+
flex: 1 1 auto;
|
|
253
227
|
}
|
|
254
228
|
|
|
255
|
-
.chat-header-
|
|
229
|
+
.chat-header-title :global(p) {
|
|
230
|
+
overflow: hidden;
|
|
231
|
+
text-overflow: ellipsis;
|
|
232
|
+
white-space: nowrap;
|
|
256
233
|
font-size: 14px;
|
|
257
234
|
line-height: 17px;
|
|
258
235
|
letter-spacing: 0;
|
|
@@ -260,12 +237,6 @@
|
|
|
260
237
|
color: var(--spectrum-alias-text-color);
|
|
261
238
|
}
|
|
262
239
|
|
|
263
|
-
.delete-button-content {
|
|
264
|
-
display: flex;
|
|
265
|
-
align-items: center;
|
|
266
|
-
gap: var(--spacing-xs);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
240
|
.chat-empty {
|
|
270
241
|
flex: 1 1 auto;
|
|
271
242
|
display: flex;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { Body, Icon } from "@budibase/bbui"
|
|
2
|
+
import { ActionMenu, Body, Icon, MenuItem } from "@budibase/bbui"
|
|
3
3
|
import { createEventDispatcher } from "svelte"
|
|
4
4
|
|
|
5
5
|
type EnabledAgentListItem = {
|
|
@@ -15,17 +15,29 @@
|
|
|
15
15
|
title?: string
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
type ConversationWithId = ConversationListItem & {
|
|
19
|
+
_id: string
|
|
20
|
+
}
|
|
21
|
+
|
|
18
22
|
export let enabledAgentList: EnabledAgentListItem[] = []
|
|
19
23
|
export let conversationHistory: ConversationListItem[] = []
|
|
20
24
|
export let selectedConversationId: string | undefined
|
|
25
|
+
export let selectedAgentName: string | undefined
|
|
21
26
|
export let hideAgents = false
|
|
27
|
+
export let deletingChat = false
|
|
22
28
|
|
|
23
29
|
$: defaultAgent =
|
|
24
30
|
enabledAgentList.find(agent => agent.isDefault) || enabledAgentList[0]
|
|
25
31
|
|
|
32
|
+
$: conversationsWithId = conversationHistory.filter(
|
|
33
|
+
(conversation): conversation is ConversationWithId =>
|
|
34
|
+
Boolean(conversation._id)
|
|
35
|
+
)
|
|
36
|
+
|
|
26
37
|
const dispatch = createEventDispatcher<{
|
|
27
38
|
agentSelected: { agentId: string }
|
|
28
39
|
conversationSelected: { conversationId: string }
|
|
40
|
+
conversationDeleted: { conversationId: string }
|
|
29
41
|
}>()
|
|
30
42
|
|
|
31
43
|
const selectAgent = (agentId: string) => {
|
|
@@ -35,10 +47,20 @@
|
|
|
35
47
|
const selectConversation = (conversationId: string) => {
|
|
36
48
|
dispatch("conversationSelected", { conversationId })
|
|
37
49
|
}
|
|
50
|
+
|
|
51
|
+
const deleteConversation = (conversationId: string) => {
|
|
52
|
+
dispatch("conversationDeleted", { conversationId })
|
|
53
|
+
}
|
|
38
54
|
</script>
|
|
39
55
|
|
|
40
56
|
<div class="chat-nav-shell">
|
|
41
57
|
<div class="chat-nav-content">
|
|
58
|
+
{#if selectedAgentName}
|
|
59
|
+
<div class="list-section current-agent-section">
|
|
60
|
+
<div class="current-agent-name">{selectedAgentName}</div>
|
|
61
|
+
</div>
|
|
62
|
+
{/if}
|
|
63
|
+
|
|
42
64
|
{#if defaultAgent?.agentId}
|
|
43
65
|
<div class="list-section">
|
|
44
66
|
<button
|
|
@@ -78,17 +100,47 @@
|
|
|
78
100
|
|
|
79
101
|
<div class="list-section">
|
|
80
102
|
<div class="list-title">Recent Chats</div>
|
|
81
|
-
{#if
|
|
82
|
-
{#each
|
|
83
|
-
|
|
103
|
+
{#if conversationsWithId.length}
|
|
104
|
+
{#each conversationsWithId as conversation (conversation._id)}
|
|
105
|
+
<div
|
|
106
|
+
class="conversation-row"
|
|
107
|
+
class:selected={selectedConversationId === conversation._id}
|
|
108
|
+
>
|
|
84
109
|
<button
|
|
85
|
-
class="list-item list-item-button"
|
|
86
|
-
|
|
87
|
-
on:click={() => selectConversation(conversation._id!)}
|
|
110
|
+
class="list-item list-item-button conversation-button"
|
|
111
|
+
on:click={() => selectConversation(conversation._id)}
|
|
88
112
|
>
|
|
89
|
-
|
|
113
|
+
<span class="conversation-title">
|
|
114
|
+
{conversation.title || "Untitled Chat"}
|
|
115
|
+
</span>
|
|
90
116
|
</button>
|
|
91
|
-
|
|
117
|
+
|
|
118
|
+
<ActionMenu align="right" disabled={deletingChat}>
|
|
119
|
+
<button
|
|
120
|
+
slot="control"
|
|
121
|
+
class="conversation-actions"
|
|
122
|
+
type="button"
|
|
123
|
+
aria-label={`Open actions for ${
|
|
124
|
+
conversation.title || "Untitled Chat"
|
|
125
|
+
}`}
|
|
126
|
+
>
|
|
127
|
+
<Icon size="S" name="dots-three" />
|
|
128
|
+
</button>
|
|
129
|
+
<MenuItem
|
|
130
|
+
on:click={() => selectConversation(conversation._id)}
|
|
131
|
+
icon="chat-circle"
|
|
132
|
+
>
|
|
133
|
+
View chat
|
|
134
|
+
</MenuItem>
|
|
135
|
+
<MenuItem
|
|
136
|
+
on:click={() => deleteConversation(conversation._id)}
|
|
137
|
+
icon="trash"
|
|
138
|
+
disabled={deletingChat}
|
|
139
|
+
>
|
|
140
|
+
{deletingChat ? "Deleting..." : "Delete chat"}
|
|
141
|
+
</MenuItem>
|
|
142
|
+
</ActionMenu>
|
|
143
|
+
</div>
|
|
92
144
|
{/each}
|
|
93
145
|
{:else}
|
|
94
146
|
<Body size="XS" color="var(--spectrum-global-color-gray-500)">
|
|
@@ -126,6 +178,17 @@
|
|
|
126
178
|
padding-top: 0;
|
|
127
179
|
}
|
|
128
180
|
|
|
181
|
+
.current-agent-section {
|
|
182
|
+
padding-bottom: var(--spacing-m);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.current-agent-name {
|
|
186
|
+
font-size: 14px;
|
|
187
|
+
line-height: 17px;
|
|
188
|
+
color: var(--spectrum-global-color-gray-800);
|
|
189
|
+
padding: var(--spacing-xs) 0;
|
|
190
|
+
}
|
|
191
|
+
|
|
129
192
|
.new-chat {
|
|
130
193
|
display: flex;
|
|
131
194
|
align-items: center;
|
|
@@ -176,6 +239,59 @@
|
|
|
176
239
|
text-overflow: ellipsis;
|
|
177
240
|
}
|
|
178
241
|
|
|
242
|
+
.conversation-row {
|
|
243
|
+
display: flex;
|
|
244
|
+
align-items: center;
|
|
245
|
+
gap: var(--spacing-xxs);
|
|
246
|
+
min-width: 0;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.conversation-button {
|
|
250
|
+
flex: 1 1 auto;
|
|
251
|
+
min-width: 0;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.conversation-title {
|
|
255
|
+
overflow: hidden;
|
|
256
|
+
text-overflow: ellipsis;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.conversation-actions {
|
|
260
|
+
display: inline-flex;
|
|
261
|
+
align-items: center;
|
|
262
|
+
justify-content: center;
|
|
263
|
+
width: 24px;
|
|
264
|
+
height: 24px;
|
|
265
|
+
padding: 0;
|
|
266
|
+
border: none;
|
|
267
|
+
background: transparent;
|
|
268
|
+
color: var(--spectrum-global-color-gray-600);
|
|
269
|
+
cursor: pointer;
|
|
270
|
+
flex: 0 0 auto;
|
|
271
|
+
opacity: 0;
|
|
272
|
+
pointer-events: none;
|
|
273
|
+
transition:
|
|
274
|
+
opacity 120ms ease,
|
|
275
|
+
color 120ms ease;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.conversation-row:hover .conversation-actions,
|
|
279
|
+
.conversation-row:focus-within .conversation-actions {
|
|
280
|
+
opacity: 1;
|
|
281
|
+
pointer-events: auto;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.conversation-actions:hover,
|
|
285
|
+
.conversation-actions:focus-visible {
|
|
286
|
+
color: var(--spectrum-global-color-gray-900);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.conversation-actions:disabled {
|
|
290
|
+
cursor: default;
|
|
291
|
+
opacity: 0.5;
|
|
292
|
+
pointer-events: none;
|
|
293
|
+
}
|
|
294
|
+
|
|
179
295
|
.list-item-icon {
|
|
180
296
|
display: inline-flex;
|
|
181
297
|
align-items: center;
|
|
@@ -190,11 +306,16 @@
|
|
|
190
306
|
color: var(--spectrum-global-color-gray-900);
|
|
191
307
|
}
|
|
192
308
|
|
|
193
|
-
.list-item.selected
|
|
309
|
+
.list-item.selected,
|
|
310
|
+
.conversation-row.selected .list-item {
|
|
194
311
|
color: var(--spectrum-global-color-gray-900);
|
|
195
312
|
font-weight: 600;
|
|
196
313
|
}
|
|
197
314
|
|
|
315
|
+
.conversation-row.selected .conversation-actions {
|
|
316
|
+
color: var(--spectrum-global-color-gray-900);
|
|
317
|
+
}
|
|
318
|
+
|
|
198
319
|
.list-title {
|
|
199
320
|
font-size: 14px;
|
|
200
321
|
line-height: 17px;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Icon } from "@budibase/bbui"
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
thinking?: boolean
|
|
6
|
+
label?: string
|
|
7
|
+
interactive?: boolean
|
|
8
|
+
expanded?: boolean
|
|
9
|
+
content?: string
|
|
10
|
+
ontoggle?: () => void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
thinking = false,
|
|
15
|
+
label = "Thought",
|
|
16
|
+
interactive = false,
|
|
17
|
+
expanded = false,
|
|
18
|
+
content = "",
|
|
19
|
+
ontoggle,
|
|
20
|
+
}: Props = $props()
|
|
21
|
+
|
|
22
|
+
const handleToggle = () => {
|
|
23
|
+
if (!interactive) {
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
ontoggle?.()
|
|
28
|
+
}
|
|
29
|
+
</script>
|
|
30
|
+
|
|
31
|
+
<div class="reasoning-part">
|
|
32
|
+
<button
|
|
33
|
+
class="reasoning-toggle"
|
|
34
|
+
class:reasoning-toggle-static={!interactive}
|
|
35
|
+
type="button"
|
|
36
|
+
onclick={handleToggle}
|
|
37
|
+
aria-disabled={!interactive}
|
|
38
|
+
tabindex={interactive ? undefined : -1}
|
|
39
|
+
>
|
|
40
|
+
<span class="reasoning-icon" class:shimmer={thinking}>
|
|
41
|
+
<Icon
|
|
42
|
+
name="brain"
|
|
43
|
+
size="M"
|
|
44
|
+
color="var(--spectrum-global-color-gray-600)"
|
|
45
|
+
/>
|
|
46
|
+
</span>
|
|
47
|
+
<span class="reasoning-label" class:shimmer={thinking}>{label}</span>
|
|
48
|
+
</button>
|
|
49
|
+
{#if expanded && content}
|
|
50
|
+
<div class="reasoning-content">{content}</div>
|
|
51
|
+
{/if}
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<style>
|
|
55
|
+
.reasoning-part {
|
|
56
|
+
display: flex;
|
|
57
|
+
flex-direction: column;
|
|
58
|
+
gap: 8px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.reasoning-toggle {
|
|
62
|
+
display: flex;
|
|
63
|
+
align-items: center;
|
|
64
|
+
gap: 6px;
|
|
65
|
+
padding: 0;
|
|
66
|
+
margin: 0;
|
|
67
|
+
background: none;
|
|
68
|
+
border: none;
|
|
69
|
+
cursor: pointer;
|
|
70
|
+
border-radius: 4px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.reasoning-toggle-static {
|
|
74
|
+
cursor: default;
|
|
75
|
+
pointer-events: none;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.reasoning-icon {
|
|
79
|
+
display: flex;
|
|
80
|
+
align-items: center;
|
|
81
|
+
justify-content: center;
|
|
82
|
+
flex-shrink: 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.reasoning-label {
|
|
86
|
+
font-size: 13px;
|
|
87
|
+
color: var(--spectrum-global-color-gray-600);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.reasoning-label.shimmer,
|
|
91
|
+
.reasoning-icon.shimmer {
|
|
92
|
+
animation: shimmer 2s ease-in-out infinite;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.reasoning-content {
|
|
96
|
+
font-size: 13px;
|
|
97
|
+
color: var(--spectrum-global-color-gray-600);
|
|
98
|
+
font-style: italic;
|
|
99
|
+
line-height: 1.4;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
@keyframes shimmer {
|
|
103
|
+
0%,
|
|
104
|
+
100% {
|
|
105
|
+
opacity: 0.6;
|
|
106
|
+
}
|
|
107
|
+
50% {
|
|
108
|
+
opacity: 1;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
</style>
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import { createAPIClient } from "@budibase/frontend-core"
|
|
18
18
|
import { Chat } from "@ai-sdk/svelte"
|
|
19
19
|
import { formatToolName } from "../../utils/aiTools"
|
|
20
|
+
import ReasoningStatus from "./ReasoningStatus.svelte"
|
|
20
21
|
import {
|
|
21
22
|
DefaultChatTransport,
|
|
22
23
|
isTextUIPart,
|
|
@@ -72,35 +73,12 @@
|
|
|
72
73
|
let expandedTools = $state<Record<string, boolean>>({})
|
|
73
74
|
let inputValue = $state("")
|
|
74
75
|
let lastInitialPrompt = $state("")
|
|
75
|
-
let reasoningTimers = $state<Record<string, number>>({})
|
|
76
76
|
let isPreparingResponse = $state(false)
|
|
77
|
-
let isHoldingFirstResponse = $state(false)
|
|
78
|
-
let firstResponseHoldTimer: ReturnType<typeof setTimeout> | undefined
|
|
79
|
-
|
|
80
|
-
const MIN_FIRST_RESPONSE_LOADING_MS = 1000
|
|
81
|
-
|
|
82
|
-
const clearFirstResponseHold = () => {
|
|
83
|
-
if (firstResponseHoldTimer) {
|
|
84
|
-
clearTimeout(firstResponseHoldTimer)
|
|
85
|
-
firstResponseHoldTimer = undefined
|
|
86
|
-
}
|
|
87
|
-
isHoldingFirstResponse = false
|
|
88
|
-
}
|
|
89
77
|
|
|
90
78
|
const resetPendingResponse = () => {
|
|
91
|
-
clearFirstResponseHold()
|
|
92
79
|
isPreparingResponse = false
|
|
93
80
|
}
|
|
94
81
|
|
|
95
|
-
const holdFirstResponse = () => {
|
|
96
|
-
clearFirstResponseHold()
|
|
97
|
-
isHoldingFirstResponse = true
|
|
98
|
-
firstResponseHoldTimer = setTimeout(() => {
|
|
99
|
-
isHoldingFirstResponse = false
|
|
100
|
-
firstResponseHoldTimer = undefined
|
|
101
|
-
}, MIN_FIRST_RESPONSE_LOADING_MS)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
82
|
const getReasoningText = (message: UIMessage<AgentMessageMetadata>) =>
|
|
105
83
|
(message.parts ?? [])
|
|
106
84
|
.filter(isReasoningUIPart)
|
|
@@ -112,6 +90,26 @@
|
|
|
112
90
|
part => isReasoningUIPart(part) && part.state === "streaming"
|
|
113
91
|
)
|
|
114
92
|
|
|
93
|
+
const hasVisibleAssistantContent = (
|
|
94
|
+
message: UIMessage<AgentMessageMetadata>
|
|
95
|
+
) => {
|
|
96
|
+
if (getReasoningText(message).trim()) {
|
|
97
|
+
return true
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (
|
|
101
|
+
(message.parts ?? []).some(
|
|
102
|
+
part =>
|
|
103
|
+
(isTextUIPart(part) && part.text.trim().length > 0) ||
|
|
104
|
+
isToolUIPart(part)
|
|
105
|
+
)
|
|
106
|
+
) {
|
|
107
|
+
return true
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return Boolean(message.metadata?.ragSources?.length)
|
|
111
|
+
}
|
|
112
|
+
|
|
115
113
|
const hasToolError = (message: UIMessage<AgentMessageMetadata>) =>
|
|
116
114
|
(message.parts ?? []).some(
|
|
117
115
|
part => isToolUIPart(part) && part.state === "output-error"
|
|
@@ -138,54 +136,6 @@
|
|
|
138
136
|
return displayName
|
|
139
137
|
}
|
|
140
138
|
|
|
141
|
-
$effect(() => {
|
|
142
|
-
const interval = setInterval(() => {
|
|
143
|
-
let updated = false
|
|
144
|
-
const newTimers = { ...reasoningTimers }
|
|
145
|
-
|
|
146
|
-
for (const message of messages) {
|
|
147
|
-
if (message.role !== "assistant") continue
|
|
148
|
-
const createdAt = message.metadata?.createdAt
|
|
149
|
-
const completedAt = message.metadata?.completedAt
|
|
150
|
-
const id = `${message.id}-reasoning`
|
|
151
|
-
|
|
152
|
-
if (!createdAt) continue
|
|
153
|
-
|
|
154
|
-
if (completedAt) {
|
|
155
|
-
const finalElapsed = (completedAt - createdAt) / 1000
|
|
156
|
-
if (newTimers[id] !== finalElapsed) {
|
|
157
|
-
newTimers[id] = finalElapsed
|
|
158
|
-
updated = true
|
|
159
|
-
}
|
|
160
|
-
continue
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const toolError = hasToolError(message)
|
|
164
|
-
if (toolError) {
|
|
165
|
-
if (newTimers[id] == null) {
|
|
166
|
-
newTimers[id] = (Date.now() - createdAt) / 1000
|
|
167
|
-
updated = true
|
|
168
|
-
}
|
|
169
|
-
continue
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (isReasoningStreaming(message)) {
|
|
173
|
-
const newElapsed = (Date.now() - createdAt) / 1000
|
|
174
|
-
if (newTimers[id] !== newElapsed) {
|
|
175
|
-
newTimers[id] = newElapsed
|
|
176
|
-
updated = true
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (updated) {
|
|
182
|
-
reasoningTimers = newTimers
|
|
183
|
-
}
|
|
184
|
-
}, 100)
|
|
185
|
-
|
|
186
|
-
return () => clearInterval(interval)
|
|
187
|
-
})
|
|
188
|
-
|
|
189
139
|
const PREVIEW_CHAT_APP_ID = "agent-preview"
|
|
190
140
|
|
|
191
141
|
let resolvedChatAppId = $state<string | undefined>()
|
|
@@ -281,28 +231,19 @@
|
|
|
281
231
|
})
|
|
282
232
|
|
|
283
233
|
let messages = $derived(chatInstance.messages)
|
|
284
|
-
let
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
: messages[messages.length - 1]
|
|
234
|
+
let lastMessage = $derived(messages[messages.length - 1])
|
|
235
|
+
let lastAssistantMessage = $derived(
|
|
236
|
+
messages.findLast(message => message.role === "assistant")
|
|
288
237
|
)
|
|
289
238
|
let isBusy = $derived(
|
|
290
239
|
chatInstance.status === "streaming" || chatInstance.status === "submitted"
|
|
291
240
|
)
|
|
292
|
-
let isRequestPending = $derived(
|
|
293
|
-
isPreparingResponse || isHoldingFirstResponse || isBusy
|
|
294
|
-
)
|
|
241
|
+
let isRequestPending = $derived(isPreparingResponse || isBusy)
|
|
295
242
|
let showPendingAssistantState = $derived(
|
|
296
|
-
isPreparingResponse ||
|
|
297
|
-
((isBusy || isHoldingFirstResponse) &&
|
|
298
|
-
lastVisibleMessage?.role === "user")
|
|
243
|
+
isPreparingResponse || (isBusy && lastMessage?.role === "user")
|
|
299
244
|
)
|
|
300
245
|
let canStart = $derived(inputValue.trim().length > 0)
|
|
301
|
-
let hasMessages = $derived(
|
|
302
|
-
messages.some(
|
|
303
|
-
message => !isHoldingFirstResponse || message.role !== "assistant"
|
|
304
|
-
)
|
|
305
|
-
)
|
|
246
|
+
let hasMessages = $derived(messages.length > 0)
|
|
306
247
|
let showConversationStarters = $derived(
|
|
307
248
|
!isRequestPending &&
|
|
308
249
|
!hasMessages &&
|
|
@@ -415,11 +356,7 @@
|
|
|
415
356
|
notifications.error(message)
|
|
416
357
|
}
|
|
417
358
|
|
|
418
|
-
const isFirstMessage = !messages.length
|
|
419
359
|
isPreparingResponse = true
|
|
420
|
-
if (isFirstMessage) {
|
|
421
|
-
holdFirstResponse()
|
|
422
|
-
}
|
|
423
360
|
|
|
424
361
|
const chatAppIdFromEnsure = await ensureChatApp()
|
|
425
362
|
|
|
@@ -575,186 +512,165 @@
|
|
|
575
512
|
<div class="message user">
|
|
576
513
|
<MarkdownViewer value={getUserMessageText(message)} />
|
|
577
514
|
</div>
|
|
578
|
-
{:else if message.role === "assistant"
|
|
515
|
+
{:else if message.role === "assistant"}
|
|
579
516
|
{@const reasoningText = getReasoningText(message)}
|
|
580
517
|
{@const reasoningId = `${message.id}-reasoning`}
|
|
518
|
+
{@const pendingAssistant =
|
|
519
|
+
isBusy &&
|
|
520
|
+
lastAssistantMessage?.id === message.id &&
|
|
521
|
+
!hasVisibleAssistantContent(message)}
|
|
581
522
|
{@const toolError = hasToolError(message)}
|
|
582
523
|
{@const messageError = getMessageError(message)}
|
|
583
524
|
{@const reasoningStreaming = isReasoningStreaming(message)}
|
|
584
525
|
{@const isThinking =
|
|
585
|
-
reasoningStreaming &&
|
|
526
|
+
(reasoningStreaming || pendingAssistant) &&
|
|
586
527
|
!toolError &&
|
|
587
528
|
!messageError &&
|
|
588
529
|
!message.metadata?.completedAt}
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
<
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
530
|
+
{#if hasVisibleAssistantContent(message) || pendingAssistant}
|
|
531
|
+
<div class="message assistant">
|
|
532
|
+
{#if reasoningText || pendingAssistant}
|
|
533
|
+
<ReasoningStatus
|
|
534
|
+
thinking={isThinking}
|
|
535
|
+
label={isThinking ? "Thinking" : "Thought"}
|
|
536
|
+
interactive={!!reasoningText}
|
|
537
|
+
expanded={Boolean(expandedTools[reasoningId])}
|
|
538
|
+
content={reasoningText}
|
|
539
|
+
ontoggle={() =>
|
|
596
540
|
(expandedTools = {
|
|
597
541
|
...expandedTools,
|
|
598
542
|
[reasoningId]: !expandedTools[reasoningId],
|
|
599
543
|
})}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
{#if isTextUIPart(part)}
|
|
624
|
-
<MarkdownViewer value={part.text} />
|
|
625
|
-
{:else if isToolUIPart(part)}
|
|
626
|
-
{@const rawToolName = getToolName(part)}
|
|
627
|
-
{@const displayToolName = formatToolName(
|
|
628
|
-
rawToolName,
|
|
629
|
-
getToolDisplayName(message, rawToolName)
|
|
630
|
-
)}
|
|
631
|
-
{@const toolId = `${message.id}-${rawToolName}-${partIndex}`}
|
|
632
|
-
{@const isRunning =
|
|
633
|
-
part.state === "input-streaming" ||
|
|
634
|
-
part.state === "input-available"}
|
|
635
|
-
{@const isSuccess = part.state === "output-available"}
|
|
636
|
-
{@const isError = part.state === "output-error"}
|
|
637
|
-
<div class="tool-part" class:tool-running={isRunning}>
|
|
638
|
-
<button
|
|
639
|
-
class="tool-header"
|
|
640
|
-
class:tool-header-expanded={expandedTools[toolId]}
|
|
641
|
-
type="button"
|
|
642
|
-
onclick={() => toggleTool(toolId)}
|
|
643
|
-
>
|
|
644
|
-
<span
|
|
645
|
-
class="tool-chevron"
|
|
646
|
-
class:expanded={expandedTools[toolId]}
|
|
544
|
+
/>
|
|
545
|
+
{/if}
|
|
546
|
+
{#each message.parts ?? [] as part, partIndex}
|
|
547
|
+
{#if isTextUIPart(part)}
|
|
548
|
+
<MarkdownViewer value={part.text} />
|
|
549
|
+
{:else if isToolUIPart(part)}
|
|
550
|
+
{@const rawToolName = getToolName(part)}
|
|
551
|
+
{@const displayToolName = formatToolName(
|
|
552
|
+
rawToolName,
|
|
553
|
+
getToolDisplayName(message, rawToolName)
|
|
554
|
+
)}
|
|
555
|
+
{@const toolId = `${message.id}-${rawToolName}-${partIndex}`}
|
|
556
|
+
{@const isRunning =
|
|
557
|
+
part.state === "input-streaming" ||
|
|
558
|
+
part.state === "input-available"}
|
|
559
|
+
{@const isSuccess = part.state === "output-available"}
|
|
560
|
+
{@const isError = part.state === "output-error"}
|
|
561
|
+
<div class="tool-part" class:tool-running={isRunning}>
|
|
562
|
+
<button
|
|
563
|
+
class="tool-header"
|
|
564
|
+
class:tool-header-expanded={expandedTools[toolId]}
|
|
565
|
+
type="button"
|
|
566
|
+
onclick={() => toggleTool(toolId)}
|
|
647
567
|
>
|
|
648
|
-
<span
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
size="M"
|
|
652
|
-
weight="regular"
|
|
653
|
-
color="var(--spectrum-global-color-gray-600)"
|
|
654
|
-
/>
|
|
655
|
-
</span>
|
|
656
|
-
<span class="tool-chevron-icon tool-chevron-icon-expanded">
|
|
657
|
-
<Icon
|
|
658
|
-
name="minus"
|
|
659
|
-
size="M"
|
|
660
|
-
weight="regular"
|
|
661
|
-
color="var(--spectrum-global-color-gray-600)"
|
|
662
|
-
/>
|
|
663
|
-
</span>
|
|
664
|
-
</span>
|
|
665
|
-
<span class="tool-call-label">Tool call</span>
|
|
666
|
-
<div class="tool-name-wrapper">
|
|
667
|
-
<span class="tool-name-primary"
|
|
668
|
-
>{displayToolName.primary}</span
|
|
568
|
+
<span
|
|
569
|
+
class="tool-chevron"
|
|
570
|
+
class:expanded={expandedTools[toolId]}
|
|
669
571
|
>
|
|
670
|
-
|
|
671
|
-
{#if isRunning || isError || isSuccess}
|
|
672
|
-
<span class="tool-status">
|
|
673
|
-
{#if isRunning}
|
|
674
|
-
<ProgressCircle size="S" />
|
|
675
|
-
{:else if isError}
|
|
572
|
+
<span class="tool-chevron-icon tool-chevron-icon-default">
|
|
676
573
|
<Icon
|
|
677
|
-
name="
|
|
678
|
-
size="
|
|
679
|
-
|
|
574
|
+
name="wrench"
|
|
575
|
+
size="M"
|
|
576
|
+
weight="regular"
|
|
577
|
+
color="var(--spectrum-global-color-gray-600)"
|
|
680
578
|
/>
|
|
681
|
-
|
|
579
|
+
</span>
|
|
580
|
+
<span
|
|
581
|
+
class="tool-chevron-icon tool-chevron-icon-expanded"
|
|
582
|
+
>
|
|
682
583
|
<Icon
|
|
683
|
-
name="
|
|
684
|
-
size="
|
|
685
|
-
|
|
584
|
+
name="minus"
|
|
585
|
+
size="M"
|
|
586
|
+
weight="regular"
|
|
587
|
+
color="var(--spectrum-global-color-gray-600)"
|
|
686
588
|
/>
|
|
687
|
-
|
|
589
|
+
</span>
|
|
688
590
|
</span>
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
</div>
|
|
591
|
+
<span class="tool-call-label">Tool call</span>
|
|
592
|
+
<div class="tool-name-wrapper">
|
|
593
|
+
<span class="tool-name-primary"
|
|
594
|
+
>{displayToolName.primary}</span
|
|
595
|
+
>
|
|
596
|
+
</div>
|
|
597
|
+
{#if isRunning || isError || isSuccess}
|
|
598
|
+
<span class="tool-status">
|
|
599
|
+
{#if isRunning}
|
|
600
|
+
<ProgressCircle size="S" />
|
|
601
|
+
{:else if isError}
|
|
602
|
+
<Icon
|
|
603
|
+
name="x"
|
|
604
|
+
size="S"
|
|
605
|
+
color="var(--spectrum-global-color-red-600)"
|
|
606
|
+
/>
|
|
607
|
+
{:else if isSuccess}
|
|
608
|
+
<Icon
|
|
609
|
+
name="check"
|
|
610
|
+
size="S"
|
|
611
|
+
color="var(--spectrum-global-color-green-600)"
|
|
612
|
+
/>
|
|
613
|
+
{/if}
|
|
614
|
+
</span>
|
|
714
615
|
{/if}
|
|
715
|
-
</
|
|
716
|
-
|
|
616
|
+
</button>
|
|
617
|
+
{#if expandedTools[toolId]}
|
|
618
|
+
<div class="tool-details">
|
|
619
|
+
{#if part.input}
|
|
620
|
+
<div class="tool-section">
|
|
621
|
+
<div class="tool-section-label">Input</div>
|
|
622
|
+
<pre class="tool-section-content">{formatToolOutput(
|
|
623
|
+
part.input
|
|
624
|
+
)}</pre>
|
|
625
|
+
</div>
|
|
626
|
+
{/if}
|
|
627
|
+
{#if isSuccess && part.output}
|
|
628
|
+
<div class="tool-section">
|
|
629
|
+
<div class="tool-section-label">Output</div>
|
|
630
|
+
<pre class="tool-section-content">{formatToolOutput(
|
|
631
|
+
part.output
|
|
632
|
+
)}</pre>
|
|
633
|
+
</div>
|
|
634
|
+
{:else if isError && part.errorText}
|
|
635
|
+
<div class="tool-section tool-error">
|
|
636
|
+
<div class="tool-section-label">Error</div>
|
|
637
|
+
<pre
|
|
638
|
+
class="tool-section-content error-content">{part.errorText}</pre>
|
|
639
|
+
</div>
|
|
640
|
+
{/if}
|
|
641
|
+
</div>
|
|
642
|
+
{/if}
|
|
643
|
+
</div>
|
|
644
|
+
{/if}
|
|
645
|
+
{/each}
|
|
646
|
+
{#if message.metadata?.ragSources?.length}
|
|
647
|
+
<div class="sources">
|
|
648
|
+
<div class="sources-title">Sources</div>
|
|
649
|
+
<ul>
|
|
650
|
+
{#each message.metadata.ragSources as source (source.sourceId)}
|
|
651
|
+
<li class="source-item">
|
|
652
|
+
<span class="source-name"
|
|
653
|
+
>{source.filename || source.sourceId}</span
|
|
654
|
+
>
|
|
655
|
+
{#if source.chunkCount > 0}
|
|
656
|
+
<span class="source-count"
|
|
657
|
+
>({source.chunkCount} chunk{source.chunkCount === 1
|
|
658
|
+
? ""
|
|
659
|
+
: "s"})</span
|
|
660
|
+
>
|
|
661
|
+
{/if}
|
|
662
|
+
</li>
|
|
663
|
+
{/each}
|
|
664
|
+
</ul>
|
|
717
665
|
</div>
|
|
718
666
|
{/if}
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
<div class="sources">
|
|
722
|
-
<div class="sources-title">Sources</div>
|
|
723
|
-
<ul>
|
|
724
|
-
{#each message.metadata.ragSources as source (source.sourceId)}
|
|
725
|
-
<li class="source-item">
|
|
726
|
-
<span class="source-name"
|
|
727
|
-
>{source.filename || source.sourceId}</span
|
|
728
|
-
>
|
|
729
|
-
{#if source.chunkCount > 0}
|
|
730
|
-
<span class="source-count"
|
|
731
|
-
>({source.chunkCount} chunk{source.chunkCount === 1
|
|
732
|
-
? ""
|
|
733
|
-
: "s"})</span
|
|
734
|
-
>
|
|
735
|
-
{/if}
|
|
736
|
-
</li>
|
|
737
|
-
{/each}
|
|
738
|
-
</ul>
|
|
739
|
-
</div>
|
|
740
|
-
{/if}
|
|
741
|
-
</div>
|
|
667
|
+
</div>
|
|
668
|
+
{/if}
|
|
742
669
|
{/if}
|
|
743
670
|
{/each}
|
|
744
671
|
{#if showPendingAssistantState}
|
|
745
672
|
<div class="message assistant assistant-loading" aria-live="polite">
|
|
746
|
-
<
|
|
747
|
-
<button class="reasoning-toggle" type="button" disabled>
|
|
748
|
-
<span class="reasoning-icon shimmer">
|
|
749
|
-
<Icon
|
|
750
|
-
name="brain"
|
|
751
|
-
size="M"
|
|
752
|
-
color="var(--spectrum-global-color-gray-600)"
|
|
753
|
-
/>
|
|
754
|
-
</span>
|
|
755
|
-
<span class="reasoning-label shimmer">Thinking</span>
|
|
756
|
-
</button>
|
|
757
|
-
</div>
|
|
673
|
+
<ReasoningStatus thinking={true} label="Thinking" />
|
|
758
674
|
</div>
|
|
759
675
|
{/if}
|
|
760
676
|
</div>
|
|
@@ -806,6 +722,14 @@
|
|
|
806
722
|
flex-direction: column;
|
|
807
723
|
overflow-y: auto;
|
|
808
724
|
min-height: 0;
|
|
725
|
+
font-family: var(--chat-font-sans, var(--font-sans));
|
|
726
|
+
--font-serif: var(--chat-font-sans, var(--font-sans));
|
|
727
|
+
--font-accent: var(--chat-font-sans, var(--font-sans));
|
|
728
|
+
--spectrum-alias-body-text-font-family: var(
|
|
729
|
+
--chat-font-sans,
|
|
730
|
+
var(--font-sans)
|
|
731
|
+
);
|
|
732
|
+
--spectrum-global-font-family-base: var(--chat-font-sans, var(--font-sans));
|
|
809
733
|
}
|
|
810
734
|
.chatbox {
|
|
811
735
|
display: flex;
|
|
@@ -904,14 +828,6 @@
|
|
|
904
828
|
max-width: 100%;
|
|
905
829
|
}
|
|
906
830
|
|
|
907
|
-
.assistant-loading {
|
|
908
|
-
min-height: 24px;
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
.assistant-loading .reasoning-toggle {
|
|
912
|
-
cursor: default;
|
|
913
|
-
}
|
|
914
|
-
|
|
915
831
|
.input-wrapper {
|
|
916
832
|
position: sticky;
|
|
917
833
|
bottom: 0;
|
|
@@ -1182,65 +1098,6 @@
|
|
|
1182
1098
|
color: var(--spectrum-global-color-red-700);
|
|
1183
1099
|
}
|
|
1184
1100
|
|
|
1185
|
-
/* Reasoning parts styling */
|
|
1186
|
-
.reasoning-part {
|
|
1187
|
-
display: flex;
|
|
1188
|
-
flex-direction: column;
|
|
1189
|
-
gap: 8px;
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1192
|
-
.reasoning-toggle {
|
|
1193
|
-
display: flex;
|
|
1194
|
-
align-items: center;
|
|
1195
|
-
gap: 6px;
|
|
1196
|
-
padding: 0;
|
|
1197
|
-
margin: 0;
|
|
1198
|
-
background: none;
|
|
1199
|
-
border: none;
|
|
1200
|
-
cursor: pointer;
|
|
1201
|
-
border-radius: 4px;
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
.reasoning-icon {
|
|
1205
|
-
display: flex;
|
|
1206
|
-
align-items: center;
|
|
1207
|
-
justify-content: center;
|
|
1208
|
-
flex-shrink: 0;
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
.reasoning-label {
|
|
1212
|
-
font-size: 13px;
|
|
1213
|
-
color: var(--spectrum-global-color-gray-600);
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
|
-
.reasoning-timer {
|
|
1217
|
-
font-size: 12px;
|
|
1218
|
-
color: var(--spectrum-global-color-gray-600);
|
|
1219
|
-
font-weight: 400;
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
.reasoning-label.shimmer,
|
|
1223
|
-
.reasoning-icon.shimmer {
|
|
1224
|
-
animation: shimmer 2s ease-in-out infinite;
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1227
|
-
.reasoning-content {
|
|
1228
|
-
font-size: 13px;
|
|
1229
|
-
color: var(--spectrum-global-color-gray-600);
|
|
1230
|
-
font-style: italic;
|
|
1231
|
-
line-height: 1.4;
|
|
1232
|
-
}
|
|
1233
|
-
|
|
1234
|
-
@keyframes shimmer {
|
|
1235
|
-
0%,
|
|
1236
|
-
100% {
|
|
1237
|
-
opacity: 0.6;
|
|
1238
|
-
}
|
|
1239
|
-
50% {
|
|
1240
|
-
opacity: 1;
|
|
1241
|
-
}
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
1101
|
.sources {
|
|
1245
1102
|
margin-top: var(--spacing-m);
|
|
1246
1103
|
padding-top: var(--spacing-s);
|