@budibase/frontend-core 3.32.5 → 3.33.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/package.json +2 -2
- package/src/components/Chatbox/index.svelte +136 -25
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/frontend-core",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.33.0",
|
|
4
4
|
"description": "Budibase frontend core libraries used in builder and client",
|
|
5
5
|
"author": "Budibase",
|
|
6
6
|
"license": "MPL-2.0",
|
|
@@ -24,5 +24,5 @@
|
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"vitest": "^3.2.4"
|
|
26
26
|
},
|
|
27
|
-
"gitHead": "
|
|
27
|
+
"gitHead": "0b4329bdd0778ad3748dfdbd42fa82370b084a6c"
|
|
28
28
|
}
|
|
@@ -27,7 +27,6 @@
|
|
|
27
27
|
} from "ai"
|
|
28
28
|
|
|
29
29
|
type ChatConversationLike = ChatConversation | DraftChatConversation
|
|
30
|
-
|
|
31
30
|
interface Props {
|
|
32
31
|
workspaceId: string
|
|
33
32
|
chat: ChatConversationLike
|
|
@@ -74,6 +73,33 @@
|
|
|
74
73
|
let inputValue = $state("")
|
|
75
74
|
let lastInitialPrompt = $state("")
|
|
76
75
|
let reasoningTimers = $state<Record<string, number>>({})
|
|
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
|
+
|
|
90
|
+
const resetPendingResponse = () => {
|
|
91
|
+
clearFirstResponseHold()
|
|
92
|
+
isPreparingResponse = false
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const holdFirstResponse = () => {
|
|
96
|
+
clearFirstResponseHold()
|
|
97
|
+
isHoldingFirstResponse = true
|
|
98
|
+
firstResponseHoldTimer = setTimeout(() => {
|
|
99
|
+
isHoldingFirstResponse = false
|
|
100
|
+
firstResponseHoldTimer = undefined
|
|
101
|
+
}, MIN_FIRST_RESPONSE_LOADING_MS)
|
|
102
|
+
}
|
|
77
103
|
|
|
78
104
|
const getReasoningText = (message: UIMessage<AgentMessageMetadata>) =>
|
|
79
105
|
(message.parts ?? [])
|
|
@@ -94,6 +120,24 @@
|
|
|
94
120
|
const getMessageError = (message: UIMessage<AgentMessageMetadata>) =>
|
|
95
121
|
message.metadata?.error
|
|
96
122
|
|
|
123
|
+
const getToolDisplayName = (
|
|
124
|
+
message: UIMessage<AgentMessageMetadata>,
|
|
125
|
+
rawToolName: string
|
|
126
|
+
) => {
|
|
127
|
+
const { metadata } = message
|
|
128
|
+
if (!metadata) {
|
|
129
|
+
return undefined
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const toolDisplayNames = Reflect.get(metadata, "toolDisplayNames")
|
|
133
|
+
const displayName =
|
|
134
|
+
toolDisplayNames !== undefined
|
|
135
|
+
? Reflect.get(toolDisplayNames, rawToolName)
|
|
136
|
+
: undefined
|
|
137
|
+
|
|
138
|
+
return displayName
|
|
139
|
+
}
|
|
140
|
+
|
|
97
141
|
$effect(() => {
|
|
98
142
|
const interval = setInterval(() => {
|
|
99
143
|
let updated = false
|
|
@@ -153,7 +197,6 @@
|
|
|
153
197
|
}
|
|
154
198
|
inputValue = starterPrompt
|
|
155
199
|
await sendMessage()
|
|
156
|
-
tick().then(() => textareaElement?.focus())
|
|
157
200
|
}
|
|
158
201
|
|
|
159
202
|
$effect(() => {
|
|
@@ -193,6 +236,8 @@
|
|
|
193
236
|
}),
|
|
194
237
|
messages: chat?.messages || [],
|
|
195
238
|
onFinish: async () => {
|
|
239
|
+
isPreparingResponse = false
|
|
240
|
+
|
|
196
241
|
if (persistConversation && !chat._id && chat.chatAppId) {
|
|
197
242
|
try {
|
|
198
243
|
const history = await API.fetchChatHistory(
|
|
@@ -217,11 +262,10 @@
|
|
|
217
262
|
|
|
218
263
|
chat = { ...chat, messages: chatInstance.messages }
|
|
219
264
|
onchatsaved?.({ detail: { chatId: chat._id, chat } })
|
|
220
|
-
|
|
221
|
-
await tick()
|
|
222
|
-
textareaElement?.focus()
|
|
223
265
|
},
|
|
224
266
|
onError: error => {
|
|
267
|
+
resetPendingResponse()
|
|
268
|
+
|
|
225
269
|
console.error(error)
|
|
226
270
|
let message = error.message || "Failed to send message"
|
|
227
271
|
try {
|
|
@@ -237,13 +281,30 @@
|
|
|
237
281
|
})
|
|
238
282
|
|
|
239
283
|
let messages = $derived(chatInstance.messages)
|
|
284
|
+
let lastVisibleMessage = $derived(
|
|
285
|
+
isHoldingFirstResponse
|
|
286
|
+
? messages.findLast(message => message.role !== "assistant")
|
|
287
|
+
: messages[messages.length - 1]
|
|
288
|
+
)
|
|
240
289
|
let isBusy = $derived(
|
|
241
290
|
chatInstance.status === "streaming" || chatInstance.status === "submitted"
|
|
242
291
|
)
|
|
292
|
+
let isRequestPending = $derived(
|
|
293
|
+
isPreparingResponse || isHoldingFirstResponse || isBusy
|
|
294
|
+
)
|
|
295
|
+
let showPendingAssistantState = $derived(
|
|
296
|
+
isPreparingResponse ||
|
|
297
|
+
((isBusy || isHoldingFirstResponse) &&
|
|
298
|
+
lastVisibleMessage?.role === "user")
|
|
299
|
+
)
|
|
243
300
|
let canStart = $derived(inputValue.trim().length > 0)
|
|
244
|
-
let hasMessages = $derived(
|
|
301
|
+
let hasMessages = $derived(
|
|
302
|
+
messages.some(
|
|
303
|
+
message => !isHoldingFirstResponse || message.role !== "assistant"
|
|
304
|
+
)
|
|
305
|
+
)
|
|
245
306
|
let showConversationStarters = $derived(
|
|
246
|
-
!
|
|
307
|
+
!isRequestPending &&
|
|
247
308
|
!hasMessages &&
|
|
248
309
|
conversationStarters.length > 0 &&
|
|
249
310
|
!isAgentPreviewChat &&
|
|
@@ -262,6 +323,9 @@
|
|
|
262
323
|
if (chat?._id !== lastChatId) {
|
|
263
324
|
lastChatId = chat?._id
|
|
264
325
|
stableSessionId = createStableSessionId()
|
|
326
|
+
if (!isPreparingResponse) {
|
|
327
|
+
resetPendingResponse()
|
|
328
|
+
}
|
|
265
329
|
chatInstance.messages = chat?.messages || []
|
|
266
330
|
expandedTools = {}
|
|
267
331
|
}
|
|
@@ -338,6 +402,25 @@
|
|
|
338
402
|
return
|
|
339
403
|
}
|
|
340
404
|
|
|
405
|
+
const text = inputValue.trim()
|
|
406
|
+
if (!text) {
|
|
407
|
+
return
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const failToStartResponse = (message: string, error?: unknown) => {
|
|
411
|
+
resetPendingResponse()
|
|
412
|
+
if (error) {
|
|
413
|
+
console.error(error)
|
|
414
|
+
}
|
|
415
|
+
notifications.error(message)
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const isFirstMessage = !messages.length
|
|
419
|
+
isPreparingResponse = true
|
|
420
|
+
if (isFirstMessage) {
|
|
421
|
+
holdFirstResponse()
|
|
422
|
+
}
|
|
423
|
+
|
|
341
424
|
const chatAppIdFromEnsure = await ensureChatApp()
|
|
342
425
|
|
|
343
426
|
if (!chat) {
|
|
@@ -348,12 +431,12 @@
|
|
|
348
431
|
const agentId = chat.agentId
|
|
349
432
|
|
|
350
433
|
if (!chatAppId) {
|
|
351
|
-
|
|
434
|
+
failToStartResponse("Chat app could not be created")
|
|
352
435
|
return
|
|
353
436
|
}
|
|
354
437
|
|
|
355
438
|
if (!agentId) {
|
|
356
|
-
|
|
439
|
+
failToStartResponse("Agent is required to start a chat")
|
|
357
440
|
return
|
|
358
441
|
}
|
|
359
442
|
|
|
@@ -378,19 +461,16 @@
|
|
|
378
461
|
err instanceof Error
|
|
379
462
|
? err.message
|
|
380
463
|
: "Could not start a new chat conversation"
|
|
381
|
-
|
|
382
|
-
notifications.error(errorMessage)
|
|
464
|
+
failToStartResponse(errorMessage, err)
|
|
383
465
|
return
|
|
384
466
|
}
|
|
385
467
|
} else if (chat._id) {
|
|
386
468
|
resolvedConversationId = chat._id
|
|
387
469
|
}
|
|
388
470
|
|
|
389
|
-
const text = inputValue.trim()
|
|
390
|
-
if (!text) return
|
|
391
|
-
|
|
392
471
|
inputValue = ""
|
|
393
472
|
chatInstance.sendMessage({ text })
|
|
473
|
+
isPreparingResponse = false
|
|
394
474
|
}
|
|
395
475
|
|
|
396
476
|
const handlePromptAction = async () => {
|
|
@@ -429,14 +509,19 @@
|
|
|
429
509
|
if (!mounted) {
|
|
430
510
|
mounted = true
|
|
431
511
|
ensureChatApp()
|
|
432
|
-
tick().then(() => {
|
|
433
|
-
if (!readOnly) {
|
|
434
|
-
textareaElement?.focus()
|
|
435
|
-
}
|
|
436
|
-
})
|
|
437
512
|
}
|
|
438
513
|
})
|
|
439
514
|
|
|
515
|
+
$effect(() => {
|
|
516
|
+
if (readOnly || isRequestPending) {
|
|
517
|
+
return
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
tick().then(() => {
|
|
521
|
+
textareaElement?.focus()
|
|
522
|
+
})
|
|
523
|
+
})
|
|
524
|
+
|
|
440
525
|
$effect(() => {
|
|
441
526
|
if (!chatAreaElement) return
|
|
442
527
|
|
|
@@ -470,7 +555,7 @@
|
|
|
470
555
|
{/each}
|
|
471
556
|
</div>
|
|
472
557
|
</div>
|
|
473
|
-
{:else if !hasMessages}
|
|
558
|
+
{:else if !hasMessages && !isRequestPending}
|
|
474
559
|
<div class="empty-state">
|
|
475
560
|
<div class="empty-state-icon">
|
|
476
561
|
<Icon
|
|
@@ -490,7 +575,7 @@
|
|
|
490
575
|
<div class="message user">
|
|
491
576
|
<MarkdownViewer value={getUserMessageText(message)} />
|
|
492
577
|
</div>
|
|
493
|
-
{:else if message.role === "assistant"}
|
|
578
|
+
{:else if message.role === "assistant" && !isHoldingFirstResponse}
|
|
494
579
|
{@const reasoningText = getReasoningText(message)}
|
|
495
580
|
{@const reasoningId = `${message.id}-reasoning`}
|
|
496
581
|
{@const toolError = hasToolError(message)}
|
|
@@ -541,7 +626,7 @@
|
|
|
541
626
|
{@const rawToolName = getToolName(part)}
|
|
542
627
|
{@const displayToolName = formatToolName(
|
|
543
628
|
rawToolName,
|
|
544
|
-
message
|
|
629
|
+
getToolDisplayName(message, rawToolName)
|
|
545
630
|
)}
|
|
546
631
|
{@const toolId = `${message.id}-${rawToolName}-${partIndex}`}
|
|
547
632
|
{@const isRunning =
|
|
@@ -661,6 +746,22 @@
|
|
|
661
746
|
</div>
|
|
662
747
|
{/if}
|
|
663
748
|
{/each}
|
|
749
|
+
{#if showPendingAssistantState}
|
|
750
|
+
<div class="message assistant assistant-loading" aria-live="polite">
|
|
751
|
+
<div class="reasoning-part">
|
|
752
|
+
<button class="reasoning-toggle" type="button" disabled>
|
|
753
|
+
<span class="reasoning-icon shimmer">
|
|
754
|
+
<Icon
|
|
755
|
+
name="brain"
|
|
756
|
+
size="M"
|
|
757
|
+
color="var(--spectrum-global-color-gray-600)"
|
|
758
|
+
/>
|
|
759
|
+
</span>
|
|
760
|
+
<span class="reasoning-label shimmer">Thinking</span>
|
|
761
|
+
</button>
|
|
762
|
+
</div>
|
|
763
|
+
</div>
|
|
764
|
+
{/if}
|
|
664
765
|
</div>
|
|
665
766
|
|
|
666
767
|
{#if readOnly}
|
|
@@ -680,18 +781,20 @@
|
|
|
680
781
|
class="input spectrum-Textfield-input"
|
|
681
782
|
onkeydown={handleKeyDown}
|
|
682
783
|
placeholder="Ask..."
|
|
683
|
-
disabled={
|
|
784
|
+
disabled={isRequestPending}
|
|
684
785
|
></textarea>
|
|
685
786
|
<button
|
|
686
787
|
type="button"
|
|
687
788
|
class="prompt-action"
|
|
688
|
-
class:running={
|
|
789
|
+
class:running={isRequestPending}
|
|
689
790
|
onclick={handlePromptAction}
|
|
690
791
|
aria-label={isBusy ? "Pause response" : "Start response"}
|
|
691
|
-
disabled={!isBusy && !canStart}
|
|
792
|
+
disabled={isPreparingResponse || (!isBusy && !canStart)}
|
|
692
793
|
>
|
|
693
794
|
{#if isBusy}
|
|
694
795
|
<Icon name="stop" size="M" weight="fill" color="#ffffff" />
|
|
796
|
+
{:else if isPreparingResponse}
|
|
797
|
+
<ProgressCircle size="S" />
|
|
695
798
|
{:else}
|
|
696
799
|
<Icon name="arrow-up" size="M" weight="bold" color="#111111" />
|
|
697
800
|
{/if}
|
|
@@ -806,6 +909,14 @@
|
|
|
806
909
|
max-width: 100%;
|
|
807
910
|
}
|
|
808
911
|
|
|
912
|
+
.assistant-loading {
|
|
913
|
+
min-height: 24px;
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
.assistant-loading .reasoning-toggle {
|
|
917
|
+
cursor: default;
|
|
918
|
+
}
|
|
919
|
+
|
|
809
920
|
.input-wrapper {
|
|
810
921
|
position: sticky;
|
|
811
922
|
bottom: 0;
|