@budibase/frontend-core 3.32.6 → 3.33.1
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 -38
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/frontend-core",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.33.1",
|
|
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": "7e37f4dca78aaab158a4fa07ba631720003da6b3"
|
|
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 =
|
|
@@ -582,11 +667,6 @@
|
|
|
582
667
|
<span class="tool-name-primary"
|
|
583
668
|
>{displayToolName.primary}</span
|
|
584
669
|
>
|
|
585
|
-
{#if displayToolName.secondary}
|
|
586
|
-
<span class="tool-name-secondary">
|
|
587
|
-
{displayToolName.secondary}
|
|
588
|
-
</span>
|
|
589
|
-
{/if}
|
|
590
670
|
</div>
|
|
591
671
|
{#if isRunning || isError || isSuccess}
|
|
592
672
|
<span class="tool-status">
|
|
@@ -661,6 +741,22 @@
|
|
|
661
741
|
</div>
|
|
662
742
|
{/if}
|
|
663
743
|
{/each}
|
|
744
|
+
{#if showPendingAssistantState}
|
|
745
|
+
<div class="message assistant assistant-loading" aria-live="polite">
|
|
746
|
+
<div class="reasoning-part">
|
|
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>
|
|
758
|
+
</div>
|
|
759
|
+
{/if}
|
|
664
760
|
</div>
|
|
665
761
|
|
|
666
762
|
{#if readOnly}
|
|
@@ -680,18 +776,20 @@
|
|
|
680
776
|
class="input spectrum-Textfield-input"
|
|
681
777
|
onkeydown={handleKeyDown}
|
|
682
778
|
placeholder="Ask..."
|
|
683
|
-
disabled={
|
|
779
|
+
disabled={isRequestPending}
|
|
684
780
|
></textarea>
|
|
685
781
|
<button
|
|
686
782
|
type="button"
|
|
687
783
|
class="prompt-action"
|
|
688
|
-
class:running={
|
|
784
|
+
class:running={isRequestPending}
|
|
689
785
|
onclick={handlePromptAction}
|
|
690
786
|
aria-label={isBusy ? "Pause response" : "Start response"}
|
|
691
|
-
disabled={!isBusy && !canStart}
|
|
787
|
+
disabled={isPreparingResponse || (!isBusy && !canStart)}
|
|
692
788
|
>
|
|
693
789
|
{#if isBusy}
|
|
694
790
|
<Icon name="stop" size="M" weight="fill" color="#ffffff" />
|
|
791
|
+
{:else if isPreparingResponse}
|
|
792
|
+
<ProgressCircle size="S" />
|
|
695
793
|
{:else}
|
|
696
794
|
<Icon name="arrow-up" size="M" weight="bold" color="#111111" />
|
|
697
795
|
{/if}
|
|
@@ -806,6 +904,14 @@
|
|
|
806
904
|
max-width: 100%;
|
|
807
905
|
}
|
|
808
906
|
|
|
907
|
+
.assistant-loading {
|
|
908
|
+
min-height: 24px;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
.assistant-loading .reasoning-toggle {
|
|
912
|
+
cursor: default;
|
|
913
|
+
}
|
|
914
|
+
|
|
809
915
|
.input-wrapper {
|
|
810
916
|
position: sticky;
|
|
811
917
|
bottom: 0;
|
|
@@ -995,9 +1101,7 @@
|
|
|
995
1101
|
|
|
996
1102
|
.tool-name-wrapper {
|
|
997
1103
|
display: flex;
|
|
998
|
-
flex-direction: column;
|
|
999
1104
|
align-items: flex-start;
|
|
1000
|
-
gap: 2px;
|
|
1001
1105
|
padding: 6px 8px;
|
|
1002
1106
|
background-color: var(--spectrum-global-color-gray-200);
|
|
1003
1107
|
border-radius: 6px;
|
|
@@ -1010,12 +1114,6 @@
|
|
|
1010
1114
|
line-height: 1.2;
|
|
1011
1115
|
}
|
|
1012
1116
|
|
|
1013
|
-
.tool-name-secondary {
|
|
1014
|
-
font-size: 11px;
|
|
1015
|
-
color: var(--spectrum-global-color-gray-600);
|
|
1016
|
-
line-height: 1.2;
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
1117
|
.tool-status {
|
|
1020
1118
|
margin-left: auto;
|
|
1021
1119
|
display: flex;
|