@budibase/frontend-core 3.23.26 → 3.23.28
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 +3 -2
- package/src/api/agents.ts +5 -5
- package/src/components/Chatbox/index.svelte +185 -90
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/frontend-core",
|
|
3
|
-
"version": "3.23.
|
|
3
|
+
"version": "3.23.28",
|
|
4
4
|
"description": "Budibase frontend core libraries used in builder and client",
|
|
5
5
|
"author": "Budibase",
|
|
6
6
|
"license": "MPL-2.0",
|
|
@@ -12,10 +12,11 @@
|
|
|
12
12
|
"@budibase/bbui": "*",
|
|
13
13
|
"@budibase/shared-core": "*",
|
|
14
14
|
"@budibase/types": "*",
|
|
15
|
+
"ai": "^5.0.93",
|
|
15
16
|
"dayjs": "^1.10.8",
|
|
16
17
|
"lodash": "4.17.21",
|
|
17
18
|
"shortid": "2.2.15",
|
|
18
19
|
"socket.io-client": "^4.7.5"
|
|
19
20
|
},
|
|
20
|
-
"gitHead": "
|
|
21
|
+
"gitHead": "bf96741b576cc61b4aae9af4ff390dbcf1eb34e4"
|
|
21
22
|
}
|
package/src/api/agents.ts
CHANGED
|
@@ -8,19 +8,19 @@ import {
|
|
|
8
8
|
CreateToolSourceRequest,
|
|
9
9
|
FetchAgentHistoryResponse,
|
|
10
10
|
FetchAgentsResponse,
|
|
11
|
-
LLMStreamChunk,
|
|
12
11
|
UpdateAgentRequest,
|
|
13
12
|
UpdateAgentResponse,
|
|
14
13
|
} from "@budibase/types"
|
|
15
14
|
|
|
16
15
|
import { Header } from "@budibase/shared-core"
|
|
17
16
|
import { BaseAPIClient } from "./types"
|
|
17
|
+
import { UIMessageChunk } from "ai"
|
|
18
18
|
|
|
19
19
|
export interface AgentEndpoints {
|
|
20
20
|
agentChatStream: (
|
|
21
21
|
chat: AgentChat,
|
|
22
22
|
workspaceId: string,
|
|
23
|
-
onChunk: (chunk:
|
|
23
|
+
onChunk: (chunk: UIMessageChunk) => void,
|
|
24
24
|
onError?: (error: Error) => void
|
|
25
25
|
) => Promise<void>
|
|
26
26
|
|
|
@@ -44,7 +44,6 @@ export const buildAgentEndpoints = (API: BaseAPIClient): AgentEndpoints => ({
|
|
|
44
44
|
const body: ChatAgentRequest = chat
|
|
45
45
|
|
|
46
46
|
try {
|
|
47
|
-
// TODO: add support for streaming into the frontend-core API object
|
|
48
47
|
const response = await fetch("/api/agent/chat/stream", {
|
|
49
48
|
method: "POST",
|
|
50
49
|
headers: {
|
|
@@ -88,8 +87,9 @@ export const buildAgentEndpoints = (API: BaseAPIClient): AgentEndpoints => ({
|
|
|
88
87
|
if (line.startsWith("data: ")) {
|
|
89
88
|
try {
|
|
90
89
|
const data = line.slice(6) // Remove 'data: ' prefix
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
const trimmedData = data.trim()
|
|
91
|
+
if (trimmedData && trimmedData !== "[DONE]") {
|
|
92
|
+
const chunk: UIMessageChunk = JSON.parse(data)
|
|
93
93
|
onChunk(chunk)
|
|
94
94
|
}
|
|
95
95
|
} catch (parseError) {
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { MarkdownViewer, notifications } from "@budibase/bbui"
|
|
3
|
-
import type {
|
|
2
|
+
import { Helpers, MarkdownViewer, notifications } from "@budibase/bbui"
|
|
3
|
+
import type { AgentChat } from "@budibase/types"
|
|
4
4
|
import BBAI from "../../icons/BBAI.svelte"
|
|
5
5
|
import { tick } from "svelte"
|
|
6
6
|
import { onDestroy } from "svelte"
|
|
7
7
|
import { onMount } from "svelte"
|
|
8
|
-
import { createAPIClient } from "@budibase/frontend-core"
|
|
9
8
|
import { createEventDispatcher } from "svelte"
|
|
9
|
+
import { createAPIClient } from "@budibase/frontend-core"
|
|
10
|
+
import type { UIMessage, UIMessageChunk } from "ai"
|
|
11
|
+
import { v4 as uuidv4 } from "uuid"
|
|
10
12
|
|
|
11
13
|
export let API = createAPIClient()
|
|
12
14
|
|
|
@@ -44,7 +46,11 @@
|
|
|
44
46
|
chat = { title: "", messages: [], agentId: "" }
|
|
45
47
|
}
|
|
46
48
|
|
|
47
|
-
const userMessage:
|
|
49
|
+
const userMessage: UIMessage = {
|
|
50
|
+
id: uuidv4(),
|
|
51
|
+
role: "user",
|
|
52
|
+
parts: [{ type: "text", text: inputValue }],
|
|
53
|
+
}
|
|
48
54
|
|
|
49
55
|
const updatedChat = {
|
|
50
56
|
...chat,
|
|
@@ -60,93 +66,60 @@
|
|
|
60
66
|
inputValue = ""
|
|
61
67
|
loading = true
|
|
62
68
|
|
|
63
|
-
let
|
|
64
|
-
let
|
|
65
|
-
let
|
|
69
|
+
let streamingText = ""
|
|
70
|
+
let assistantIndex = -1
|
|
71
|
+
let streamCompleted = false
|
|
66
72
|
|
|
67
73
|
try {
|
|
68
74
|
await API.agentChatStream(
|
|
69
75
|
updatedChat,
|
|
70
76
|
workspaceId,
|
|
71
|
-
chunk => {
|
|
72
|
-
if (chunk.type === "
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const updatedMessages = [...updatedChat.messages]
|
|
78
|
-
|
|
79
|
-
// Find or create assistant message
|
|
80
|
-
const lastMessage = updatedMessages[updatedMessages.length - 1]
|
|
81
|
-
if (lastMessage?.role === "assistant") {
|
|
82
|
-
lastMessage.content =
|
|
83
|
-
streamingContent + (isToolCall ? toolCallInfo : "")
|
|
84
|
-
} else {
|
|
85
|
-
updatedMessages.push({
|
|
86
|
-
role: "assistant",
|
|
87
|
-
content: streamingContent + (isToolCall ? toolCallInfo : ""),
|
|
88
|
-
})
|
|
77
|
+
(chunk: UIMessageChunk) => {
|
|
78
|
+
if (chunk.type === "text-start") {
|
|
79
|
+
const assistantMessage: UIMessage = {
|
|
80
|
+
id: Helpers.uuid(),
|
|
81
|
+
role: "assistant",
|
|
82
|
+
parts: [{ type: "text", text: "", state: "streaming" }],
|
|
89
83
|
}
|
|
90
|
-
|
|
91
84
|
chat = {
|
|
92
85
|
...chat,
|
|
93
|
-
messages:
|
|
86
|
+
messages: [...updatedChat.messages, assistantMessage],
|
|
94
87
|
}
|
|
95
|
-
|
|
96
|
-
// Auto-scroll as content streams
|
|
88
|
+
assistantIndex = chat.messages.length - 1
|
|
97
89
|
scrollToBottom()
|
|
98
|
-
} else if (chunk.type === "
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
chat = {
|
|
114
|
-
...chat,
|
|
115
|
-
messages: updatedMessages,
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
scrollToBottom()
|
|
119
|
-
} else if (chunk.type === "tool_call_result") {
|
|
120
|
-
const resultInfo = chunk.toolResult?.error
|
|
121
|
-
? `\n**❌ Tool Error:** ${chunk.toolResult.error}`
|
|
122
|
-
: `\n**✅ Tool Result:** Complete`
|
|
123
|
-
|
|
124
|
-
toolCallInfo += resultInfo
|
|
125
|
-
|
|
126
|
-
const updatedMessages = [...updatedChat.messages]
|
|
127
|
-
const lastMessage = updatedMessages[updatedMessages.length - 1]
|
|
128
|
-
if (lastMessage?.role === "assistant") {
|
|
129
|
-
lastMessage.content = streamingContent + toolCallInfo
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
chat = {
|
|
133
|
-
...chat,
|
|
134
|
-
messages: updatedMessages,
|
|
90
|
+
} else if (chunk.type === "text-delta") {
|
|
91
|
+
streamingText += chunk.delta || ""
|
|
92
|
+
if (assistantIndex >= 0) {
|
|
93
|
+
const messages = [...chat.messages]
|
|
94
|
+
const assistant = { ...messages[assistantIndex] }
|
|
95
|
+
const parts = [...assistant.parts]
|
|
96
|
+
const textPart = parts.find(p => p.type === "text")
|
|
97
|
+
if (textPart) {
|
|
98
|
+
textPart.text = streamingText
|
|
99
|
+
}
|
|
100
|
+
assistant.parts = parts
|
|
101
|
+
messages[assistantIndex] = assistant
|
|
102
|
+
chat = { ...chat, messages }
|
|
135
103
|
}
|
|
136
|
-
|
|
137
104
|
scrollToBottom()
|
|
138
|
-
} else if (chunk.type === "
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
105
|
+
} else if (chunk.type === "text-end") {
|
|
106
|
+
loading = false
|
|
107
|
+
streamCompleted = true
|
|
108
|
+
if (assistantIndex >= 0) {
|
|
109
|
+
const messages = [...chat.messages]
|
|
110
|
+
const assistant = { ...messages[assistantIndex] }
|
|
111
|
+
const parts = [...assistant.parts]
|
|
112
|
+
const textPart = parts.find(p => p.type === "text")
|
|
113
|
+
if (textPart) {
|
|
114
|
+
textPart.state = "done"
|
|
143
115
|
}
|
|
116
|
+
assistant.parts = parts
|
|
117
|
+
messages[assistantIndex] = assistant
|
|
118
|
+
chat = { ...chat, messages }
|
|
144
119
|
}
|
|
145
|
-
} else if (chunk.type === "done") {
|
|
146
|
-
loading = false
|
|
147
120
|
scrollToBottom()
|
|
148
121
|
} else if (chunk.type === "error") {
|
|
149
|
-
notifications.error(chunk.
|
|
122
|
+
notifications.error(chunk.errorText || "An error occurred")
|
|
150
123
|
loading = false
|
|
151
124
|
}
|
|
152
125
|
},
|
|
@@ -156,6 +129,13 @@
|
|
|
156
129
|
loading = false
|
|
157
130
|
}
|
|
158
131
|
)
|
|
132
|
+
|
|
133
|
+
if (streamCompleted && chat) {
|
|
134
|
+
setTimeout(() => {
|
|
135
|
+
const chatId = chat._id || ""
|
|
136
|
+
dispatch("chatSaved", { chatId })
|
|
137
|
+
}, 500)
|
|
138
|
+
}
|
|
159
139
|
} catch (err: any) {
|
|
160
140
|
console.error(err)
|
|
161
141
|
notifications.error(err.message)
|
|
@@ -170,8 +150,6 @@
|
|
|
170
150
|
}
|
|
171
151
|
|
|
172
152
|
onMount(async () => {
|
|
173
|
-
chat = { title: "", messages: [], agentId: chat.agentId }
|
|
174
|
-
|
|
175
153
|
// Ensure we always autoscroll to reveal new messages
|
|
176
154
|
observer = new MutationObserver(async () => {
|
|
177
155
|
await tick()
|
|
@@ -205,22 +183,57 @@
|
|
|
205
183
|
{#if message.role === "user"}
|
|
206
184
|
<div class="message user">
|
|
207
185
|
<MarkdownViewer
|
|
208
|
-
value={
|
|
209
|
-
? message.
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
? part.text
|
|
215
|
-
: `${part.type} content not supported`
|
|
216
|
-
)
|
|
217
|
-
.join("")
|
|
218
|
-
: "[Empty message]"}
|
|
186
|
+
value={message.parts && message.parts.length > 0
|
|
187
|
+
? message.parts
|
|
188
|
+
.filter(part => part.type === "text")
|
|
189
|
+
.map(part => (part.type === "text" ? part.text : ""))
|
|
190
|
+
.join("")
|
|
191
|
+
: "[Empty message]"}
|
|
219
192
|
/>
|
|
220
193
|
</div>
|
|
221
|
-
{:else if message.role === "assistant"
|
|
194
|
+
{:else if message.role === "assistant"}
|
|
222
195
|
<div class="message assistant">
|
|
223
|
-
|
|
196
|
+
{#each message.parts || [] as part}
|
|
197
|
+
{#if part.type === "text"}
|
|
198
|
+
<MarkdownViewer value={part.text || ""} />
|
|
199
|
+
{:else if part.type === "reasoning"}
|
|
200
|
+
<div class="reasoning-part">
|
|
201
|
+
<div class="reasoning-label">Reasoning</div>
|
|
202
|
+
<div class="reasoning-content">{part.text || ""}</div>
|
|
203
|
+
</div>
|
|
204
|
+
{:else if part.type?.startsWith("tool-") || part.type === "dynamic-tool"}
|
|
205
|
+
{@const toolPart = part}
|
|
206
|
+
<div class="tool-part">
|
|
207
|
+
<div class="tool-header">
|
|
208
|
+
<span class="tool-icon">🔧</span>
|
|
209
|
+
<span class="tool-name"
|
|
210
|
+
>{("toolName" in toolPart && toolPart.toolName) ||
|
|
211
|
+
"Tool"}</span
|
|
212
|
+
>
|
|
213
|
+
{#if "state" in toolPart}
|
|
214
|
+
{#if toolPart.state === "output-available"}
|
|
215
|
+
<span class="tool-status success">✓</span>
|
|
216
|
+
{:else if toolPart.state === "output-error"}
|
|
217
|
+
<span class="tool-status error">✗</span>
|
|
218
|
+
{:else if toolPart.state === "input-streaming"}
|
|
219
|
+
<span class="tool-status pending">...</span>
|
|
220
|
+
{:else if toolPart.state === "input-available"}
|
|
221
|
+
<span class="tool-status pending">...</span>
|
|
222
|
+
{/if}
|
|
223
|
+
{/if}
|
|
224
|
+
</div>
|
|
225
|
+
{#if "state" in toolPart && toolPart.state === "output-available" && "output" in toolPart && toolPart.output}
|
|
226
|
+
<div class="tool-output">
|
|
227
|
+
<div class="tool-output-label">Output:</div>
|
|
228
|
+
<pre class="tool-output-content">{typeof toolPart.output ===
|
|
229
|
+
"string"
|
|
230
|
+
? toolPart.output
|
|
231
|
+
: JSON.stringify(toolPart.output, null, 2)}</pre>
|
|
232
|
+
</div>
|
|
233
|
+
{/if}
|
|
234
|
+
</div>
|
|
235
|
+
{/if}
|
|
236
|
+
{/each}
|
|
224
237
|
</div>
|
|
225
238
|
{/if}
|
|
226
239
|
{/each}
|
|
@@ -334,4 +347,86 @@
|
|
|
334
347
|
border: 1px solid var(--grey-3);
|
|
335
348
|
border-radius: 4px;
|
|
336
349
|
}
|
|
350
|
+
|
|
351
|
+
/* Tool parts styling */
|
|
352
|
+
.tool-part {
|
|
353
|
+
margin: var(--spacing-m) 0;
|
|
354
|
+
padding: var(--spacing-m);
|
|
355
|
+
background-color: var(--grey-2);
|
|
356
|
+
border: 1px solid var(--grey-3);
|
|
357
|
+
border-radius: 8px;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.tool-header {
|
|
361
|
+
display: flex;
|
|
362
|
+
align-items: center;
|
|
363
|
+
gap: var(--spacing-s);
|
|
364
|
+
margin-bottom: var(--spacing-s);
|
|
365
|
+
font-weight: 600;
|
|
366
|
+
font-size: 14px;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
.tool-icon {
|
|
370
|
+
font-size: 16px;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.tool-name {
|
|
374
|
+
color: var(--spectrum-global-color-gray-900);
|
|
375
|
+
font-family: var(--font-mono), monospace;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
.tool-status {
|
|
379
|
+
margin-left: auto;
|
|
380
|
+
font-size: 12px;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
.tool-status.success {
|
|
384
|
+
color: var(--spectrum-global-color-green-600);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.tool-status.error {
|
|
388
|
+
color: var(--spectrum-global-color-red-600);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.tool-status.pending {
|
|
392
|
+
color: var(--spectrum-global-color-gray-600);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.tool-output,
|
|
396
|
+
.tool-output-label,
|
|
397
|
+
.tool-output-content {
|
|
398
|
+
background-color: var(--background);
|
|
399
|
+
border: 1px solid var(--grey-3);
|
|
400
|
+
border-radius: 4px;
|
|
401
|
+
padding: var(--spacing-s);
|
|
402
|
+
font-size: 12px;
|
|
403
|
+
font-family: var(--font-mono), monospace;
|
|
404
|
+
overflow-x: auto;
|
|
405
|
+
white-space: pre-wrap;
|
|
406
|
+
word-break: break-word;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/* Reasoning parts styling */
|
|
410
|
+
.reasoning-part {
|
|
411
|
+
margin: var(--spacing-m) 0;
|
|
412
|
+
padding: var(--spacing-m);
|
|
413
|
+
background-color: var(--grey-1);
|
|
414
|
+
border-left: 3px solid var(--spectrum-global-color-static-seafoam-700);
|
|
415
|
+
border-radius: 4px;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
.reasoning-label {
|
|
419
|
+
font-size: 12px;
|
|
420
|
+
font-weight: 600;
|
|
421
|
+
color: var(--spectrum-global-color-static-seafoam-700);
|
|
422
|
+
margin-bottom: 4px;
|
|
423
|
+
text-transform: uppercase;
|
|
424
|
+
letter-spacing: 0.5px;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
.reasoning-content {
|
|
428
|
+
font-size: 13px;
|
|
429
|
+
color: var(--spectrum-global-color-gray-800);
|
|
430
|
+
font-style: italic;
|
|
431
|
+
}
|
|
337
432
|
</style>
|