@budibase/frontend-core 3.43.0 → 3.44.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 +4 -4
- package/src/api/agents.ts +26 -57
- package/src/api/ai.ts +26 -0
- package/src/api/chatLinks.ts +26 -6
- package/src/api/escalations.ts +6 -2
- package/src/api/index.ts +15 -5
- package/src/api/types.ts +1 -2
- package/src/api/workspaceHome.ts +2 -2
- package/src/components/Chatbox/index.svelte +69 -334
- package/src/components/index.ts +0 -2
- package/src/utils/download.test.js +34 -0
- package/src/api/chatApps.ts +0 -268
- package/src/components/Chatbox/ChatConversationPanel.svelte +0 -401
- package/src/components/Chatbox/ChatNavigationPanel.svelte +0 -326
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
EscalationContextDoc,
|
|
15
15
|
EscalationRespondResult,
|
|
16
16
|
} from "@budibase/types"
|
|
17
|
-
import {
|
|
17
|
+
import { EscalateToolResultStatus } from "@budibase/types"
|
|
18
18
|
import { Header } from "@budibase/shared-core"
|
|
19
19
|
import { tick, untrack } from "svelte"
|
|
20
20
|
import { createAPIClient } from "@budibase/frontend-core"
|
|
@@ -37,12 +37,6 @@
|
|
|
37
37
|
interface Props {
|
|
38
38
|
workspaceId: string
|
|
39
39
|
chat: ChatConversationLike
|
|
40
|
-
persistConversation?: boolean
|
|
41
|
-
conversationStarters?: { prompt: string }[]
|
|
42
|
-
initialPrompt?: string
|
|
43
|
-
onchatsaved?: (event: {
|
|
44
|
-
detail: { chatId?: string; chat: ChatConversationLike }
|
|
45
|
-
}) => void
|
|
46
40
|
// Fired when an escalation parks; the consumer polls the outcome and
|
|
47
41
|
// injects it via appendAssistantMessage.
|
|
48
42
|
onEscalationPending?: (detail: { escalationId: string }) => void
|
|
@@ -57,10 +51,7 @@
|
|
|
57
51
|
escalationId: string,
|
|
58
52
|
accepted: boolean
|
|
59
53
|
) => Promise<EscalationRespondResult | undefined>
|
|
60
|
-
isAgentPreviewChat?: boolean
|
|
61
54
|
previewRoleId?: string
|
|
62
|
-
readOnly?: boolean
|
|
63
|
-
readOnlyReason?: "disabled" | "deleted" | "offline"
|
|
64
55
|
promptHistory?: string[]
|
|
65
56
|
onpromptsubmitted?: (prompt: string) => void
|
|
66
57
|
}
|
|
@@ -68,18 +59,11 @@
|
|
|
68
59
|
let {
|
|
69
60
|
workspaceId,
|
|
70
61
|
chat = $bindable(),
|
|
71
|
-
persistConversation = true,
|
|
72
|
-
conversationStarters = [],
|
|
73
|
-
initialPrompt = "",
|
|
74
|
-
onchatsaved,
|
|
75
62
|
onEscalationPending,
|
|
76
63
|
escalationState,
|
|
77
64
|
showInlineApproval = false,
|
|
78
65
|
onResolve,
|
|
79
|
-
isAgentPreviewChat = false,
|
|
80
66
|
previewRoleId,
|
|
81
|
-
readOnly = false,
|
|
82
|
-
readOnlyReason,
|
|
83
67
|
promptHistory = [],
|
|
84
68
|
onpromptsubmitted,
|
|
85
69
|
}: Props = $props()
|
|
@@ -108,13 +92,15 @@
|
|
|
108
92
|
// The escalate part's input/output are loosely typed by the AI SDK, so the
|
|
109
93
|
// casts live here rather than cluttering the template.
|
|
110
94
|
const escalationCardProps = (part: { input?: unknown; output?: unknown }) => {
|
|
111
|
-
const output = part.output as
|
|
95
|
+
const output = part.output as
|
|
96
|
+
| { escalationId?: string; title?: string; summary?: string }
|
|
97
|
+
| undefined
|
|
112
98
|
const input = part.input as { title?: string; summary?: string } | undefined
|
|
113
99
|
const escalationId = output?.escalationId
|
|
114
100
|
return {
|
|
115
101
|
escalationId,
|
|
116
|
-
title: input?.title,
|
|
117
|
-
summary: input?.summary,
|
|
102
|
+
title: output?.title ?? input?.title,
|
|
103
|
+
summary: output?.summary ?? input?.summary,
|
|
118
104
|
resolution:
|
|
119
105
|
(escalationId && escalationState?.[escalationId]?.resolution) ||
|
|
120
106
|
"pending",
|
|
@@ -131,8 +117,7 @@
|
|
|
131
117
|
})
|
|
132
118
|
)
|
|
133
119
|
|
|
134
|
-
const createStableSessionId = () =>
|
|
135
|
-
isAgentPreviewChat ? `chat-preview:${Helpers.uuid()}` : Helpers.uuid()
|
|
120
|
+
const createStableSessionId = () => `chat-preview:${Helpers.uuid()}`
|
|
136
121
|
|
|
137
122
|
let stableSessionId = $state(createStableSessionId())
|
|
138
123
|
let chatAreaElement = $state<HTMLDivElement>()
|
|
@@ -141,7 +126,6 @@
|
|
|
141
126
|
let reasoningTextByMessageId = $state<Record<string, string>>({})
|
|
142
127
|
let inputValue = $state("")
|
|
143
128
|
let promptHistoryIndex = $state<number | undefined>()
|
|
144
|
-
let lastInitialPrompt = $state("")
|
|
145
129
|
let isPreparingResponse = $state(false)
|
|
146
130
|
const resetPendingResponse = () => {
|
|
147
131
|
isPreparingResponse = false
|
|
@@ -165,27 +149,15 @@
|
|
|
165
149
|
|
|
166
150
|
try {
|
|
167
151
|
const resolvedUrl =
|
|
168
|
-
|
|
169
|
-
chat?.chatAppId &&
|
|
170
|
-
chat?.agentId &&
|
|
171
|
-
selectedOperationId
|
|
152
|
+
chat?.agentId && selectedOperationId
|
|
172
153
|
? (
|
|
173
|
-
await API.
|
|
174
|
-
chat.chatAppId,
|
|
154
|
+
await API.fetchOperationFileUrl(
|
|
175
155
|
chat.agentId,
|
|
176
|
-
|
|
177
|
-
|
|
156
|
+
selectedOperationId,
|
|
157
|
+
source.fileId
|
|
178
158
|
)
|
|
179
159
|
).url
|
|
180
|
-
:
|
|
181
|
-
? (
|
|
182
|
-
await API.fetchOperationFileUrl(
|
|
183
|
-
chat.agentId,
|
|
184
|
-
selectedOperationId,
|
|
185
|
-
source.fileId
|
|
186
|
-
)
|
|
187
|
-
).url
|
|
188
|
-
: undefined
|
|
160
|
+
: undefined
|
|
189
161
|
if (!resolvedUrl) {
|
|
190
162
|
notifications.error("Could not resolve source file URL")
|
|
191
163
|
return
|
|
@@ -292,48 +264,21 @@
|
|
|
292
264
|
return displayName
|
|
293
265
|
}
|
|
294
266
|
|
|
295
|
-
const PREVIEW_CHAT_APP_ID = "agent-preview"
|
|
296
|
-
|
|
297
|
-
let resolvedChatAppId = $state<string | undefined>()
|
|
298
267
|
let resolvedConversationId = $state<string | undefined>()
|
|
299
268
|
|
|
300
|
-
const applyConversationStarter = async (starterPrompt: string) => {
|
|
301
|
-
if (isBusy) {
|
|
302
|
-
return
|
|
303
|
-
}
|
|
304
|
-
inputValue = starterPrompt
|
|
305
|
-
await sendMessage()
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
$effect(() => {
|
|
309
|
-
if (!initialPrompt) {
|
|
310
|
-
lastInitialPrompt = ""
|
|
311
|
-
return
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
if (initialPrompt === lastInitialPrompt) {
|
|
315
|
-
return
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
lastInitialPrompt = initialPrompt
|
|
319
|
-
applyConversationStarter(initialPrompt)
|
|
320
|
-
})
|
|
321
|
-
|
|
322
269
|
const chatInstance = new Chat<UIMessage<AgentMessageMetadata>>({
|
|
323
270
|
transport: new DefaultChatTransport({
|
|
324
271
|
headers: () => ({ [Header.WORKSPACE_ID]: workspaceId }),
|
|
325
272
|
prepareSendMessagesRequest: ({ messages }) => {
|
|
326
|
-
const chatAppId = resolvedChatAppId || chat?.chatAppId
|
|
327
273
|
const conversationId = resolvedConversationId || chat?._id || "new"
|
|
274
|
+
const agentId = chat?.agentId
|
|
328
275
|
return {
|
|
329
|
-
api: `/api/
|
|
276
|
+
api: `/api/agents/${agentId}/conversations/${conversationId}/stream`,
|
|
330
277
|
body: {
|
|
331
278
|
_id: resolvedConversationId || chat?._id,
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
isPreview: isAgentPreviewChat,
|
|
336
|
-
previewRoleId: isAgentPreviewChat ? previewRoleId : undefined,
|
|
279
|
+
agentId,
|
|
280
|
+
isPreview: true,
|
|
281
|
+
previewRoleId,
|
|
337
282
|
sessionId: stableSessionId,
|
|
338
283
|
title: chat?.title,
|
|
339
284
|
messages,
|
|
@@ -344,31 +289,7 @@
|
|
|
344
289
|
messages: chat?.messages || [],
|
|
345
290
|
onFinish: async () => {
|
|
346
291
|
isPreparingResponse = false
|
|
347
|
-
|
|
348
|
-
if (persistConversation && !chat._id && chat.chatAppId) {
|
|
349
|
-
try {
|
|
350
|
-
const history = await API.fetchChatHistory(
|
|
351
|
-
chat.chatAppId,
|
|
352
|
-
chat.agentId
|
|
353
|
-
)
|
|
354
|
-
const msgs = chatInstance.messages
|
|
355
|
-
const lastMessageId = msgs[msgs.length - 1]?.id
|
|
356
|
-
const savedConversation =
|
|
357
|
-
history?.find(convo =>
|
|
358
|
-
convo.messages.some(message => message.id === lastMessageId)
|
|
359
|
-
) || history?.[0]
|
|
360
|
-
|
|
361
|
-
if (savedConversation) {
|
|
362
|
-
chat = { ...chat, ...savedConversation }
|
|
363
|
-
resolvedConversationId = savedConversation._id
|
|
364
|
-
}
|
|
365
|
-
} catch (historyError) {
|
|
366
|
-
console.error(historyError)
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
292
|
chat = { ...chat, messages: chatInstance.messages }
|
|
371
|
-
onchatsaved?.({ detail: { chatId: chat._id, chat } })
|
|
372
293
|
},
|
|
373
294
|
onError: error => {
|
|
374
295
|
resetPendingResponse()
|
|
@@ -398,11 +319,7 @@
|
|
|
398
319
|
const ids: string[] = []
|
|
399
320
|
for (const message of messages) {
|
|
400
321
|
for (const part of message.parts ?? []) {
|
|
401
|
-
if (
|
|
402
|
-
!isToolUIPart(part) ||
|
|
403
|
-
getToolName(part) !== ESCALATE_TOOL_NAME ||
|
|
404
|
-
part.state !== "output-available"
|
|
405
|
-
) {
|
|
322
|
+
if (!isToolUIPart(part) || part.state !== "output-available") {
|
|
406
323
|
continue
|
|
407
324
|
}
|
|
408
325
|
const output = part.output as
|
|
@@ -451,20 +368,6 @@
|
|
|
451
368
|
)
|
|
452
369
|
let canStart = $derived(inputValue.trim().length > 0)
|
|
453
370
|
let hasMessages = $derived(messages.length > 0)
|
|
454
|
-
let showConversationStarters = $derived(
|
|
455
|
-
!isRequestPending &&
|
|
456
|
-
!hasMessages &&
|
|
457
|
-
conversationStarters.length > 0 &&
|
|
458
|
-
!isAgentPreviewChat &&
|
|
459
|
-
!readOnly
|
|
460
|
-
)
|
|
461
|
-
let readOnlyMessage = $derived(
|
|
462
|
-
readOnlyReason === "deleted"
|
|
463
|
-
? "This agent was deleted. Select another agent to resume chatting."
|
|
464
|
-
: readOnlyReason === "offline"
|
|
465
|
-
? "This agent is no longer live. Make it live in Settings to resume chatting."
|
|
466
|
-
: "This agent is disabled. Enable it in Settings to resume chatting."
|
|
467
|
-
)
|
|
468
371
|
|
|
469
372
|
let lastChatId = $state<string | undefined>(chat?._id)
|
|
470
373
|
$effect(() => {
|
|
@@ -522,70 +425,22 @@
|
|
|
522
425
|
}
|
|
523
426
|
})
|
|
524
427
|
|
|
525
|
-
const ensureChatApp = async (): Promise<string | undefined> => {
|
|
526
|
-
if (chat?.chatAppId) {
|
|
527
|
-
resolvedChatAppId = chat.chatAppId
|
|
528
|
-
return chat.chatAppId
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
if (isAgentPreviewChat) {
|
|
532
|
-
resolvedChatAppId = PREVIEW_CHAT_APP_ID
|
|
533
|
-
if (chat) {
|
|
534
|
-
chat = { ...chat, chatAppId: PREVIEW_CHAT_APP_ID }
|
|
535
|
-
}
|
|
536
|
-
return PREVIEW_CHAT_APP_ID
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
try {
|
|
540
|
-
const chatApp = await API.fetchChatApp(workspaceId)
|
|
541
|
-
if (chatApp?._id) {
|
|
542
|
-
const baseChat = chat || {
|
|
543
|
-
title: "",
|
|
544
|
-
messages: [],
|
|
545
|
-
chatAppId: "",
|
|
546
|
-
agentId: "",
|
|
547
|
-
}
|
|
548
|
-
const fallbackAgentId =
|
|
549
|
-
chatApp.agents?.find(agent => agent.isEnabled && agent.isDefault)
|
|
550
|
-
?.agentId || chatApp.agents?.find(agent => agent.isEnabled)?.agentId
|
|
551
|
-
chat = {
|
|
552
|
-
...baseChat,
|
|
553
|
-
chatAppId: chatApp._id,
|
|
554
|
-
...(fallbackAgentId && !baseChat.agentId
|
|
555
|
-
? { agentId: fallbackAgentId }
|
|
556
|
-
: {}),
|
|
557
|
-
}
|
|
558
|
-
resolvedChatAppId = chatApp._id
|
|
559
|
-
return chatApp._id
|
|
560
|
-
}
|
|
561
|
-
} catch (err) {
|
|
562
|
-
console.error(err)
|
|
563
|
-
}
|
|
564
|
-
return undefined
|
|
565
|
-
}
|
|
566
|
-
|
|
567
428
|
const handleKeyDown = async (event: KeyboardEvent) => {
|
|
568
|
-
|
|
429
|
+
const navigationState = navigatePromptHistory({
|
|
430
|
+
key: event.key,
|
|
431
|
+
history: promptHistory,
|
|
432
|
+
inputValue,
|
|
433
|
+
index: promptHistoryIndex,
|
|
434
|
+
})
|
|
435
|
+
if (navigationState) {
|
|
436
|
+
event.preventDefault()
|
|
437
|
+
inputValue = navigationState.inputValue
|
|
438
|
+
promptHistoryIndex = navigationState.index
|
|
439
|
+
await tick()
|
|
440
|
+
textareaElement?.setSelectionRange(inputValue.length, inputValue.length)
|
|
569
441
|
return
|
|
570
442
|
}
|
|
571
443
|
|
|
572
|
-
if (isAgentPreviewChat) {
|
|
573
|
-
const navigationState = navigatePromptHistory({
|
|
574
|
-
key: event.key,
|
|
575
|
-
history: promptHistory,
|
|
576
|
-
inputValue,
|
|
577
|
-
index: promptHistoryIndex,
|
|
578
|
-
})
|
|
579
|
-
if (navigationState) {
|
|
580
|
-
event.preventDefault()
|
|
581
|
-
inputValue = navigationState.inputValue
|
|
582
|
-
promptHistoryIndex = navigationState.index
|
|
583
|
-
await tick()
|
|
584
|
-
textareaElement?.setSelectionRange(inputValue.length, inputValue.length)
|
|
585
|
-
return
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
|
|
589
444
|
if (event.key === "Enter" && !event.shiftKey) {
|
|
590
445
|
event.preventDefault()
|
|
591
446
|
await sendMessage()
|
|
@@ -593,10 +448,6 @@
|
|
|
593
448
|
}
|
|
594
449
|
|
|
595
450
|
const sendMessage = async () => {
|
|
596
|
-
if (readOnly) {
|
|
597
|
-
return
|
|
598
|
-
}
|
|
599
|
-
|
|
600
451
|
const text = inputValue.trim()
|
|
601
452
|
if (!text) {
|
|
602
453
|
return
|
|
@@ -612,52 +463,22 @@
|
|
|
612
463
|
|
|
613
464
|
isPreparingResponse = true
|
|
614
465
|
|
|
615
|
-
const chatAppIdFromEnsure = await ensureChatApp()
|
|
616
|
-
|
|
617
466
|
if (!chat) {
|
|
618
|
-
chat = {
|
|
467
|
+
chat = {
|
|
468
|
+
title: "",
|
|
469
|
+
messages: [],
|
|
470
|
+
agentId: "",
|
|
471
|
+
}
|
|
619
472
|
}
|
|
620
473
|
|
|
621
|
-
const chatAppId = chat.chatAppId || chatAppIdFromEnsure
|
|
622
474
|
const agentId = chat.agentId
|
|
623
475
|
|
|
624
|
-
if (!chatAppId) {
|
|
625
|
-
failToStartResponse("Chat app could not be created")
|
|
626
|
-
return
|
|
627
|
-
}
|
|
628
|
-
|
|
629
476
|
if (!agentId) {
|
|
630
477
|
failToStartResponse("Agent is required to start a chat")
|
|
631
478
|
return
|
|
632
479
|
}
|
|
633
480
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
if (isAgentPreviewChat) {
|
|
637
|
-
resolvedConversationId = chat._id
|
|
638
|
-
} else if (
|
|
639
|
-
persistConversation &&
|
|
640
|
-
!chat._id &&
|
|
641
|
-
(!chat.messages || chat.messages.length === 0)
|
|
642
|
-
) {
|
|
643
|
-
try {
|
|
644
|
-
const newChat = await API.createChatConversation(
|
|
645
|
-
{ chatAppId, agentId, title: chat.title },
|
|
646
|
-
workspaceId
|
|
647
|
-
)
|
|
648
|
-
chat = { ...chat, ...newChat, chatAppId }
|
|
649
|
-
resolvedConversationId = newChat._id
|
|
650
|
-
} catch (err: unknown) {
|
|
651
|
-
const errorMessage =
|
|
652
|
-
err instanceof Error
|
|
653
|
-
? err.message
|
|
654
|
-
: "Could not start a new chat conversation"
|
|
655
|
-
failToStartResponse(errorMessage, err)
|
|
656
|
-
return
|
|
657
|
-
}
|
|
658
|
-
} else if (chat._id) {
|
|
659
|
-
resolvedConversationId = chat._id
|
|
660
|
-
}
|
|
481
|
+
resolvedConversationId = chat._id
|
|
661
482
|
|
|
662
483
|
inputValue = ""
|
|
663
484
|
promptHistoryIndex = undefined
|
|
@@ -689,8 +510,6 @@
|
|
|
689
510
|
.map(p => p.text)
|
|
690
511
|
.join("") || "[Empty message]"
|
|
691
512
|
|
|
692
|
-
let mounted = $state(false)
|
|
693
|
-
|
|
694
513
|
$effect(() => {
|
|
695
514
|
const currentChatId = chat?._id
|
|
696
515
|
if (currentChatId) {
|
|
@@ -699,14 +518,7 @@
|
|
|
699
518
|
})
|
|
700
519
|
|
|
701
520
|
$effect(() => {
|
|
702
|
-
if (
|
|
703
|
-
mounted = true
|
|
704
|
-
ensureChatApp()
|
|
705
|
-
}
|
|
706
|
-
})
|
|
707
|
-
|
|
708
|
-
$effect(() => {
|
|
709
|
-
if (readOnly || isRequestPending) {
|
|
521
|
+
if (isRequestPending) {
|
|
710
522
|
return
|
|
711
523
|
}
|
|
712
524
|
|
|
@@ -733,22 +545,7 @@
|
|
|
733
545
|
|
|
734
546
|
<div class="chat-area" bind:this={chatAreaElement}>
|
|
735
547
|
<div class="chatbox">
|
|
736
|
-
{#if
|
|
737
|
-
<div class="starter-section">
|
|
738
|
-
<div class="starter-title">Conversation starters</div>
|
|
739
|
-
<div class="starter-grid">
|
|
740
|
-
{#each conversationStarters as starter, index (index)}
|
|
741
|
-
<button
|
|
742
|
-
type="button"
|
|
743
|
-
class="starter-card"
|
|
744
|
-
onclick={() => applyConversationStarter(starter.prompt)}
|
|
745
|
-
>
|
|
746
|
-
{starter.prompt}
|
|
747
|
-
</button>
|
|
748
|
-
{/each}
|
|
749
|
-
</div>
|
|
750
|
-
</div>
|
|
751
|
-
{:else if !hasMessages && !isRequestPending}
|
|
548
|
+
{#if !hasMessages && !isRequestPending}
|
|
752
549
|
<div class="empty-state">
|
|
753
550
|
<div class="empty-state-icon">
|
|
754
551
|
<Icon
|
|
@@ -810,7 +607,7 @@
|
|
|
810
607
|
{#each message.parts ?? [] as part, partIndex}
|
|
811
608
|
{#if isTextUIPart(part)}
|
|
812
609
|
<MarkdownViewer value={part.text} />
|
|
813
|
-
{:else if isToolUIPart(part) &&
|
|
610
|
+
{:else if isToolUIPart(part) && isRaisedEscalation(part.output)}
|
|
814
611
|
{@const card = escalationCardProps(part)}
|
|
815
612
|
<EscalationCard
|
|
816
613
|
title={card.title}
|
|
@@ -967,48 +764,38 @@
|
|
|
967
764
|
{/if}
|
|
968
765
|
</div>
|
|
969
766
|
|
|
970
|
-
|
|
971
|
-
<div class="input-
|
|
972
|
-
<
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
767
|
+
<div class="input-wrapper">
|
|
768
|
+
<div class="input-container">
|
|
769
|
+
<textarea
|
|
770
|
+
bind:value={inputValue}
|
|
771
|
+
bind:this={textareaElement}
|
|
772
|
+
class="input spectrum-Textfield-input"
|
|
773
|
+
onkeydown={handleKeyDown}
|
|
774
|
+
oninput={() => (promptHistoryIndex = undefined)}
|
|
775
|
+
placeholder="Ask..."
|
|
776
|
+
disabled={isRequestPending}
|
|
777
|
+
></textarea>
|
|
778
|
+
<button
|
|
779
|
+
type="button"
|
|
780
|
+
class="prompt-action"
|
|
781
|
+
class:running={isRequestPending}
|
|
782
|
+
onclick={handlePromptAction}
|
|
783
|
+
aria-label={isBusy ? "Pause response" : "Start response"}
|
|
784
|
+
disabled={isPreparingResponse || (!isBusy && !canStart)}
|
|
785
|
+
>
|
|
786
|
+
{#if isBusy}
|
|
787
|
+
<Icon name="stop" size="M" weight="fill" color="#ffffff" />
|
|
788
|
+
{:else if isPreparingResponse}
|
|
789
|
+
<ProgressCircle size="S" />
|
|
790
|
+
{:else}
|
|
791
|
+
<Icon name="arrow-up" size="M" weight="bold" color="#111111" />
|
|
792
|
+
{/if}
|
|
793
|
+
</button>
|
|
977
794
|
</div>
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
<div class="input-container">
|
|
981
|
-
<textarea
|
|
982
|
-
bind:value={inputValue}
|
|
983
|
-
bind:this={textareaElement}
|
|
984
|
-
class="input spectrum-Textfield-input"
|
|
985
|
-
onkeydown={handleKeyDown}
|
|
986
|
-
oninput={() => (promptHistoryIndex = undefined)}
|
|
987
|
-
placeholder="Ask..."
|
|
988
|
-
disabled={isRequestPending}
|
|
989
|
-
></textarea>
|
|
990
|
-
<button
|
|
991
|
-
type="button"
|
|
992
|
-
class="prompt-action"
|
|
993
|
-
class:running={isRequestPending}
|
|
994
|
-
onclick={handlePromptAction}
|
|
995
|
-
aria-label={isBusy ? "Pause response" : "Start response"}
|
|
996
|
-
disabled={isPreparingResponse || (!isBusy && !canStart)}
|
|
997
|
-
>
|
|
998
|
-
{#if isBusy}
|
|
999
|
-
<Icon name="stop" size="M" weight="fill" color="#ffffff" />
|
|
1000
|
-
{:else if isPreparingResponse}
|
|
1001
|
-
<ProgressCircle size="S" />
|
|
1002
|
-
{:else}
|
|
1003
|
-
<Icon name="arrow-up" size="M" weight="bold" color="#111111" />
|
|
1004
|
-
{/if}
|
|
1005
|
-
</button>
|
|
1006
|
-
</div>
|
|
1007
|
-
<div class="input-footer">
|
|
1008
|
-
<ContextUsage usage={lastAssistantUsage} />
|
|
1009
|
-
</div>
|
|
795
|
+
<div class="input-footer">
|
|
796
|
+
<ContextUsage usage={lastAssistantUsage} />
|
|
1010
797
|
</div>
|
|
1011
|
-
|
|
798
|
+
</div>
|
|
1012
799
|
</div>
|
|
1013
800
|
|
|
1014
801
|
<style>
|
|
@@ -1050,50 +837,6 @@
|
|
|
1050
837
|
.empty-state-icon {
|
|
1051
838
|
--size: 24px;
|
|
1052
839
|
}
|
|
1053
|
-
.starter-section {
|
|
1054
|
-
display: flex;
|
|
1055
|
-
flex-direction: column;
|
|
1056
|
-
align-items: center;
|
|
1057
|
-
gap: var(--spacing-xl);
|
|
1058
|
-
margin: auto 0;
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
.starter-title {
|
|
1062
|
-
font-size: 14px;
|
|
1063
|
-
letter-spacing: 0;
|
|
1064
|
-
color: var(--spectrum-global-color-gray-700);
|
|
1065
|
-
text-align: center;
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
.starter-grid {
|
|
1069
|
-
display: grid;
|
|
1070
|
-
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
1071
|
-
gap: var(--spacing-m);
|
|
1072
|
-
width: min(520px, 100%);
|
|
1073
|
-
margin: 0 auto;
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
.starter-card {
|
|
1077
|
-
border: 1px solid var(--spectrum-global-color-gray-200);
|
|
1078
|
-
border-radius: 12px;
|
|
1079
|
-
padding: var(--spacing-m);
|
|
1080
|
-
background: var(--spectrum-global-color-gray-50);
|
|
1081
|
-
color: var(--spectrum-global-color-gray-800);
|
|
1082
|
-
font: inherit;
|
|
1083
|
-
font-size: 14px;
|
|
1084
|
-
line-height: 1.4;
|
|
1085
|
-
text-align: center;
|
|
1086
|
-
cursor: pointer;
|
|
1087
|
-
display: flex;
|
|
1088
|
-
align-items: center;
|
|
1089
|
-
justify-content: center;
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
.starter-card:hover {
|
|
1093
|
-
border-color: var(--spectrum-global-color-gray-300);
|
|
1094
|
-
background: var(--spectrum-global-color-gray-100);
|
|
1095
|
-
}
|
|
1096
|
-
|
|
1097
840
|
.message {
|
|
1098
841
|
display: flex;
|
|
1099
842
|
flex-direction: column;
|
|
@@ -1141,14 +884,6 @@
|
|
|
1141
884
|
padding: 0 4px;
|
|
1142
885
|
}
|
|
1143
886
|
|
|
1144
|
-
.read-only-notice {
|
|
1145
|
-
border: 1px solid var(--spectrum-global-color-gray-200);
|
|
1146
|
-
border-radius: 10px;
|
|
1147
|
-
padding: var(--spacing-m);
|
|
1148
|
-
background-color: var(--spectrum-global-color-gray-50);
|
|
1149
|
-
text-align: center;
|
|
1150
|
-
}
|
|
1151
|
-
|
|
1152
887
|
.input-container {
|
|
1153
888
|
position: relative;
|
|
1154
889
|
width: 100%;
|
package/src/components/index.ts
CHANGED
|
@@ -11,5 +11,3 @@ export { default as ChangePasswordModal } from "./ChangePasswordModal.svelte"
|
|
|
11
11
|
export { default as ProfileModal } from "./ProfileModal.svelte"
|
|
12
12
|
export { default as PasswordRepeatInput } from "./PasswordRepeatInput.svelte"
|
|
13
13
|
export { default as Chatbox } from "./Chatbox/index.svelte"
|
|
14
|
-
export { default as ChatNavigationPanel } from "./Chatbox/ChatNavigationPanel.svelte"
|
|
15
|
-
export { default as ChatConversationPanel } from "./Chatbox/ChatConversationPanel.svelte"
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest"
|
|
2
|
+
import { downloadStream } from "./download.js"
|
|
3
|
+
|
|
4
|
+
describe("downloadStream", () => {
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
vi.unstubAllGlobals()
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
it("uses the filename from the Content-Disposition header", async () => {
|
|
10
|
+
const link = {
|
|
11
|
+
click: vi.fn(),
|
|
12
|
+
download: "",
|
|
13
|
+
href: "",
|
|
14
|
+
}
|
|
15
|
+
const createObjectURL = vi.fn(() => "blob:package")
|
|
16
|
+
const revokeObjectURL = vi.fn()
|
|
17
|
+
vi.stubGlobal("document", {
|
|
18
|
+
createElement: vi.fn(() => link),
|
|
19
|
+
})
|
|
20
|
+
vi.stubGlobal("URL", { createObjectURL, revokeObjectURL })
|
|
21
|
+
|
|
22
|
+
await downloadStream({
|
|
23
|
+
blob: vi.fn().mockResolvedValue(new Blob(["package"])),
|
|
24
|
+
headers: new Headers({
|
|
25
|
+
"Content-Disposition":
|
|
26
|
+
'attachment; filename="budibase-teams-support-agent-package.zip"',
|
|
27
|
+
}),
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
expect(link.download).toBe("budibase-teams-support-agent-package.zip")
|
|
31
|
+
expect(link.click).toHaveBeenCalledOnce()
|
|
32
|
+
expect(revokeObjectURL).toHaveBeenCalledWith("blob:package")
|
|
33
|
+
})
|
|
34
|
+
})
|