@budibase/frontend-core 3.23.4 → 3.23.6

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.23.4",
3
+ "version": "3.23.6",
4
4
  "description": "Budibase frontend core libraries used in builder and client",
5
5
  "author": "Budibase",
6
6
  "license": "MPL-2.0",
@@ -17,5 +17,5 @@
17
17
  "shortid": "2.2.15",
18
18
  "socket.io-client": "^4.7.5"
19
19
  },
20
- "gitHead": "de7e273c24ea446e0639e606fb3808a3fe46caae"
20
+ "gitHead": "993c44f2265cdca05008c355c1225967e2eb10c2"
21
21
  }
@@ -0,0 +1,337 @@
1
+ <script lang="ts">
2
+ import { MarkdownViewer, notifications } from "@budibase/bbui"
3
+ import type { UserMessage, AgentChat } from "@budibase/types"
4
+ import BBAI from "../../icons/BBAI.svelte"
5
+ import { tick } from "svelte"
6
+ import { onDestroy } from "svelte"
7
+ import { onMount } from "svelte"
8
+ import { createAPIClient } from "@budibase/frontend-core"
9
+ import { createEventDispatcher } from "svelte"
10
+
11
+ export let API = createAPIClient()
12
+
13
+ export let workspaceId: string
14
+ export let chat: AgentChat
15
+
16
+ const dispatch = createEventDispatcher<{ chatSaved: { chatId: string } }>()
17
+
18
+ let inputValue = ""
19
+ let loading: boolean = false
20
+ let chatAreaElement: HTMLDivElement
21
+ let observer: MutationObserver
22
+ let textareaElement: HTMLTextAreaElement
23
+
24
+ $: if (chat.messages.length) {
25
+ scrollToBottom()
26
+ }
27
+
28
+ async function scrollToBottom() {
29
+ await tick()
30
+ if (chatAreaElement) {
31
+ chatAreaElement.scrollTop = chatAreaElement.scrollHeight
32
+ }
33
+ }
34
+
35
+ async function handleKeyDown(event: any) {
36
+ if (event.key === "Enter" && !event.shiftKey) {
37
+ event.preventDefault()
38
+ await prompt()
39
+ }
40
+ }
41
+
42
+ async function prompt() {
43
+ if (!chat) {
44
+ chat = { title: "", messages: [] }
45
+ }
46
+
47
+ const userMessage: UserMessage = { role: "user", content: inputValue }
48
+
49
+ const updatedChat = {
50
+ ...chat,
51
+ messages: [...chat.messages, userMessage],
52
+ }
53
+
54
+ // Update local display immediately with user message
55
+ chat = updatedChat
56
+
57
+ // Ensure we scroll to the new message
58
+ await scrollToBottom()
59
+
60
+ inputValue = ""
61
+ loading = true
62
+
63
+ let streamingContent = ""
64
+ let isToolCall = false
65
+ let toolCallInfo: string = ""
66
+
67
+ try {
68
+ await API.agentChatStream(
69
+ updatedChat,
70
+ workspaceId,
71
+ chunk => {
72
+ if (chunk.type === "content") {
73
+ // Accumulate streaming content
74
+ streamingContent += chunk.content || ""
75
+
76
+ // Update chat with partial content
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
+ })
89
+ }
90
+
91
+ chat = {
92
+ ...chat,
93
+ messages: updatedMessages,
94
+ }
95
+
96
+ // Auto-scroll as content streams
97
+ scrollToBottom()
98
+ } else if (chunk.type === "tool_call_start") {
99
+ isToolCall = true
100
+ toolCallInfo = `\n\n**🔧 Executing Tool:** ${chunk.toolCall?.name}\n**Parameters:**\n\`\`\`json\n${chunk.toolCall?.arguments}\n\`\`\`\n`
101
+
102
+ const updatedMessages = [...updatedChat.messages]
103
+ const lastMessage = updatedMessages[updatedMessages.length - 1]
104
+ if (lastMessage?.role === "assistant") {
105
+ lastMessage.content = streamingContent + toolCallInfo
106
+ } else {
107
+ updatedMessages.push({
108
+ role: "assistant",
109
+ content: streamingContent + toolCallInfo,
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,
135
+ }
136
+
137
+ scrollToBottom()
138
+ } else if (chunk.type === "chat_saved") {
139
+ if (chunk.chat) {
140
+ chat = chunk.chat
141
+ if (chunk.chat._id) {
142
+ dispatch("chatSaved", { chatId: chunk.chat._id })
143
+ }
144
+ }
145
+ } else if (chunk.type === "done") {
146
+ loading = false
147
+ scrollToBottom()
148
+ } else if (chunk.type === "error") {
149
+ notifications.error(chunk.content || "An error occurred")
150
+ loading = false
151
+ }
152
+ },
153
+ error => {
154
+ console.error("Streaming error:", error)
155
+ notifications.error(error.message)
156
+ loading = false
157
+ }
158
+ )
159
+ } catch (err: any) {
160
+ console.error(err)
161
+ notifications.error(err.message)
162
+ loading = false
163
+ }
164
+
165
+ // Return focus to textarea after the response
166
+ await tick()
167
+ if (textareaElement) {
168
+ textareaElement.focus()
169
+ }
170
+ }
171
+
172
+ onMount(async () => {
173
+ chat = { title: "", messages: [] }
174
+
175
+ // Ensure we always autoscroll to reveal new messages
176
+ observer = new MutationObserver(async () => {
177
+ await tick()
178
+ if (chatAreaElement) {
179
+ chatAreaElement.scrollTop = chatAreaElement.scrollHeight
180
+ }
181
+ })
182
+
183
+ if (chatAreaElement) {
184
+ observer.observe(chatAreaElement, {
185
+ childList: true,
186
+ subtree: true,
187
+ attributes: true,
188
+ })
189
+ }
190
+
191
+ await tick()
192
+ if (textareaElement) {
193
+ textareaElement.focus()
194
+ }
195
+ })
196
+
197
+ onDestroy(() => {
198
+ observer.disconnect()
199
+ })
200
+ </script>
201
+
202
+ <div class="chat-area" bind:this={chatAreaElement}>
203
+ <div class="chatbox">
204
+ {#each chat.messages as message}
205
+ {#if message.role === "user"}
206
+ <div class="message user">
207
+ <MarkdownViewer
208
+ value={typeof message.content === "string"
209
+ ? message.content
210
+ : message.content.length > 0
211
+ ? message.content
212
+ .map(part =>
213
+ part.type === "text"
214
+ ? part.text
215
+ : `${part.type} content not supported`
216
+ )
217
+ .join("")
218
+ : "[Empty message]"}
219
+ />
220
+ </div>
221
+ {:else if message.role === "assistant" && message.content}
222
+ <div class="message assistant">
223
+ <MarkdownViewer value={message.content} />
224
+ </div>
225
+ {/if}
226
+ {/each}
227
+ {#if loading}
228
+ <div class="message system">
229
+ <BBAI size="48px" animate />
230
+ </div>
231
+ {/if}
232
+ </div>
233
+
234
+ <div class="input-wrapper">
235
+ <textarea
236
+ bind:value={inputValue}
237
+ bind:this={textareaElement}
238
+ class="input spectrum-Textfield-input"
239
+ on:keydown={handleKeyDown}
240
+ placeholder="Ask anything"
241
+ disabled={loading}
242
+ />
243
+ </div>
244
+ </div>
245
+
246
+ <style>
247
+ .chat-area {
248
+ flex: 1 1 auto;
249
+ display: flex;
250
+ flex-direction: column;
251
+ overflow-y: auto;
252
+ height: 0;
253
+ }
254
+ .chatbox {
255
+ display: flex;
256
+ flex-direction: column;
257
+ gap: 24px;
258
+ width: 600px;
259
+ margin: 0 auto;
260
+ flex: 1 1 auto;
261
+ padding: 48px 0 24px 0;
262
+ }
263
+
264
+ .message {
265
+ display: flex;
266
+ flex-direction: column;
267
+ max-width: 80%;
268
+ padding: var(--spacing-l);
269
+ border-radius: 20px;
270
+ font-size: 16px;
271
+ color: var(--spectrum-global-color-gray-900);
272
+ }
273
+
274
+ .message.user {
275
+ align-self: flex-end;
276
+ background-color: var(--grey-3);
277
+ }
278
+
279
+ .message.assistant {
280
+ align-self: flex-start;
281
+ background-color: var(--grey-1);
282
+ border: 1px solid var(--grey-3);
283
+ }
284
+
285
+ .message.system {
286
+ align-self: flex-start;
287
+ background: none;
288
+ padding-left: 0;
289
+ }
290
+
291
+ .input-wrapper {
292
+ position: sticky;
293
+ bottom: 0;
294
+ width: 600px;
295
+ margin: 0 auto;
296
+ background: var(--background-alt);
297
+ padding-bottom: 32px;
298
+ display: flex;
299
+ flex-direction: column;
300
+ }
301
+
302
+ .input {
303
+ width: 100%;
304
+ height: 100px;
305
+ top: 0;
306
+ resize: none;
307
+ padding: 20px;
308
+ font-size: 16px;
309
+ background-color: var(--grey-3);
310
+ color: var(--grey-9);
311
+ border-radius: 16px;
312
+ border: none;
313
+ outline: none;
314
+ min-height: 100px;
315
+ margin-bottom: 8px;
316
+ }
317
+
318
+ .input::placeholder {
319
+ color: var(--spectrum-global-color-gray-600);
320
+ }
321
+
322
+ /* Style the markdown tool sections in assistant messages */
323
+ :global(.assistant strong) {
324
+ color: var(--spectrum-global-color-static-seafoam-700);
325
+ }
326
+
327
+ :global(.assistant h3) {
328
+ margin-top: var(--spacing-m);
329
+ color: var(--spectrum-global-color-static-seafoam-700);
330
+ }
331
+
332
+ :global(.assistant pre) {
333
+ background-color: var(--grey-2);
334
+ border: 1px solid var(--grey-3);
335
+ border-radius: 4px;
336
+ }
337
+ </style>
@@ -10,3 +10,4 @@ export { default as FilterUsers } from "./FilterUsers.svelte"
10
10
  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
+ export { default as Chatbox } from "./Chatbox/index.svelte"
@@ -0,0 +1,96 @@
1
+ <script lang="ts">
2
+ export let size = "64px"
3
+ export let animate: boolean = false
4
+ </script>
5
+
6
+ <svg
7
+ viewBox="0 0 150 150"
8
+ width={size}
9
+ fill="none"
10
+ xmlns="http://www.w3.org/2000/svg"
11
+ >
12
+ <g id="bbai">
13
+ <g id="Group 48096461">
14
+ {#if animate}
15
+ <animateTransform
16
+ attributeName="transform"
17
+ attributeType="XML"
18
+ type="rotate"
19
+ from="0 75 75"
20
+ to="360 75 75"
21
+ dur="2.5s"
22
+ repeatCount="indefinite"
23
+ calcMode="spline"
24
+ keyTimes="0;1"
25
+ keySplines="0.3 0 0.5 1"
26
+ />
27
+ {/if}
28
+ <path
29
+ id="Vector"
30
+ d="M22.3457 95.0839L51.9032 124.642C53.199 125.937 55.2988 125.937 56.5946 124.642L69.4653 111.771C70.6236 110.612 71.171 108.976 70.9436 107.352L67.4872 82.9246C67.2334 81.1475 65.8371 79.7512 64.0626 79.4999L39.6354 76.0436C38.0143 75.8135 36.3774 76.3609 35.2164 77.5218L22.3457 90.3926C21.0499 91.6884 21.0499 93.7881 22.3457 95.0839Z"
31
+ fill="url(#paint0_linear_605_124)"
32
+ />
33
+ <path
34
+ id="Vector_2"
35
+ d="M92.0381 124.643L121.596 95.0851C122.891 93.7893 122.891 91.6895 121.596 90.3937L108.725 77.5229C107.567 76.3647 105.93 75.8172 104.306 76.0447L79.8787 79.501C78.1016 79.7549 76.7053 81.1512 76.454 82.9257L72.9977 107.353C72.7676 108.974 73.315 110.611 74.4759 111.772L87.3467 124.643C88.6425 125.938 90.7422 125.938 92.0381 124.643Z"
36
+ fill="url(#paint1_linear_605_124)"
37
+ />
38
+ <path
39
+ id="Vector_3"
40
+ d="M121.654 54.92L92.0968 25.3624C90.801 24.0666 88.7012 24.0666 87.4054 25.3624L74.5347 38.2332C73.3764 39.3915 72.829 41.0284 73.0564 42.6521L76.5128 67.0793C76.7666 68.8565 78.1629 70.2527 79.9374 70.504L104.365 73.9603C105.986 74.1904 107.623 73.643 108.784 72.4821L121.654 59.6113C122.95 58.3155 122.95 56.2158 121.654 54.92Z"
41
+ fill="url(#paint2_linear_605_124)"
42
+ />
43
+ <path
44
+ id="Vector_4"
45
+ d="M51.9619 25.3613L22.4044 54.9188C21.1086 56.2146 21.1086 58.3144 22.4044 59.6102L35.2752 72.481C36.4334 73.6393 38.0704 74.1867 39.6941 73.9592L64.1213 70.5029C65.8984 70.249 67.2947 68.8527 67.546 67.0782L71.0023 42.651C71.2324 41.0299 70.685 39.393 69.5241 38.2321L56.6533 25.3613C55.3575 24.0655 53.2578 24.0655 51.9619 25.3613Z"
46
+ fill="url(#paint3_linear_605_124)"
47
+ />
48
+ </g>
49
+ </g>
50
+ <defs>
51
+ <linearGradient
52
+ id="paint0_linear_605_124"
53
+ x1="52.1505"
54
+ y1="121.517"
55
+ x2="68.292"
56
+ y2="84.222"
57
+ gradientUnits="userSpaceOnUse"
58
+ >
59
+ <stop stop-color="#6E56FF" />
60
+ <stop offset="1" stop-color="#9F8FFF" />
61
+ </linearGradient>
62
+ <linearGradient
63
+ id="paint1_linear_605_124"
64
+ x1="118.471"
65
+ y1="94.8378"
66
+ x2="81.1761"
67
+ y2="78.6963"
68
+ gradientUnits="userSpaceOnUse"
69
+ >
70
+ <stop stop-color="#6E56FF" />
71
+ <stop offset="1" stop-color="#9F8FFF" />
72
+ </linearGradient>
73
+ <linearGradient
74
+ id="paint2_linear_605_124"
75
+ x1="91.8495"
76
+ y1="28.4869"
77
+ x2="75.708"
78
+ y2="65.7819"
79
+ gradientUnits="userSpaceOnUse"
80
+ >
81
+ <stop stop-color="#6E56FF" />
82
+ <stop offset="1" stop-color="#9F8FFF" />
83
+ </linearGradient>
84
+ <linearGradient
85
+ id="paint3_linear_605_124"
86
+ x1="25.5289"
87
+ y1="55.1661"
88
+ x2="62.8239"
89
+ y2="71.3076"
90
+ gradientUnits="userSpaceOnUse"
91
+ >
92
+ <stop stop-color="#6E56FF" />
93
+ <stop offset="1" stop-color="#9F8FFF" />
94
+ </linearGradient>
95
+ </defs>
96
+ </svg>