@budibase/frontend-core 3.42.0 → 3.44.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.
@@ -1,268 +0,0 @@
1
- import {
2
- ChatAgentRequest,
3
- ChatConversation,
4
- ChatConversationRequest,
5
- CreateChatConversationRequest,
6
- FetchChatAppAgentsResponse,
7
- ChatApp,
8
- ChatAppAgent,
9
- FetchAgentHistoryResponse,
10
- FetchPublishedChatAppsResponse,
11
- UpdateChatAppRequest,
12
- AgentMessageMetadata,
13
- FetchAgentFileUrlResponse,
14
- } from "@budibase/types"
15
- import { Header } from "@budibase/shared-core"
16
- import { BaseAPIClient } from "./types"
17
- import { readUIMessageStream, UIMessage, UIMessageChunk } from "ai"
18
- import { createSseToJsonTransformStream } from "../utils/utils"
19
-
20
- export interface ChatAppEndpoints {
21
- streamChatConversation: (
22
- chat: ChatConversationRequest,
23
- workspaceId: string
24
- ) => Promise<AsyncIterable<UIMessage<AgentMessageMetadata>>>
25
- deleteChatConversation: (
26
- chatConversationId: string,
27
- chatAppId: string,
28
- agentId?: string
29
- ) => Promise<void>
30
- fetchChatConversation: (
31
- chatAppId: string,
32
- chatConversationId: string,
33
- agentId?: string
34
- ) => Promise<ChatConversation>
35
- fetchChatAppAgents: (chatAppId: string) => Promise<FetchChatAppAgentsResponse>
36
- fetchChatHistory: (
37
- chatAppId: string,
38
- agentId?: string
39
- ) => Promise<FetchAgentHistoryResponse>
40
- fetchChatApp: (workspaceId?: string) => Promise<ChatApp | null>
41
- setChatAppAgent: (chatAppId: string, agentId: string) => Promise<ChatAppAgent>
42
- fetchChatAppAgentFileUrl: (
43
- chatAppId: string,
44
- agentId: string,
45
- fileId: string,
46
- operationId: string
47
- ) => Promise<FetchAgentFileUrlResponse>
48
- createChatConversation: (
49
- chat: CreateChatConversationRequest,
50
- workspaceId?: string
51
- ) => Promise<ChatConversation>
52
- updateChatApp: (chatApp: UpdateChatAppRequest) => Promise<ChatApp>
53
- getPublishedChatApps: () => Promise<
54
- FetchPublishedChatAppsResponse["chatApps"]
55
- >
56
- }
57
-
58
- const getBrowserTimezone = () => {
59
- try {
60
- return Intl.DateTimeFormat().resolvedOptions().timeZone
61
- } catch {
62
- return undefined
63
- }
64
- }
65
-
66
- const throwOnErrorChunk = () =>
67
- new TransformStream<UIMessageChunk, UIMessageChunk>({
68
- transform(chunk, controller) {
69
- if (chunk.type === "error") {
70
- throw new Error(chunk.errorText || "Agent action failed")
71
- }
72
- controller.enqueue(chunk)
73
- },
74
- })
75
-
76
- const withAgentIdQuery = (url: string, agentId?: string) => {
77
- if (!agentId) {
78
- return url
79
- }
80
-
81
- const query = new URLSearchParams({ agentId })
82
- return `${url}?${query.toString()}`
83
- }
84
-
85
- export const buildChatAppEndpoints = (
86
- API: BaseAPIClient
87
- ): ChatAppEndpoints => ({
88
- streamChatConversation: async (chat, workspaceId) => {
89
- if (!chat.chatAppId) {
90
- throw new Error("chatAppId is required to stream a chat conversation")
91
- }
92
-
93
- const body: ChatAgentRequest = {
94
- ...chat,
95
- timezone: chat.timezone ?? getBrowserTimezone(),
96
- }
97
- const conversationId = chat._id || "new"
98
-
99
- const response = await fetch(
100
- `/api/chatapps/${chat.chatAppId}/conversations/${conversationId}/stream`,
101
- {
102
- method: "POST",
103
- headers: {
104
- "Content-Type": "application/json",
105
- Accept: "application/json",
106
- [Header.WORKSPACE_ID]: workspaceId,
107
- },
108
- body: JSON.stringify(body),
109
- credentials: "same-origin",
110
- }
111
- )
112
-
113
- if (!response.ok) {
114
- const errorBody = await response.json()
115
- throw new Error(
116
- errorBody.message || `HTTP error! status: ${response.status}`
117
- )
118
- }
119
-
120
- if (!response.body) {
121
- throw new Error("Failed to get response body")
122
- }
123
-
124
- const chunkStream = response.body
125
- .pipeThrough(new TextDecoderStream())
126
- .pipeThrough(createSseToJsonTransformStream<UIMessageChunk>())
127
- .pipeThrough(throwOnErrorChunk())
128
-
129
- return readUIMessageStream({
130
- stream: chunkStream,
131
- terminateOnError: true,
132
- })
133
- },
134
-
135
- deleteChatConversation: async (
136
- chatConversationId: string,
137
- chatAppId: string,
138
- agentId?: string
139
- ) => {
140
- return await API.delete({
141
- url: withAgentIdQuery(
142
- `/api/chatapps/${chatAppId}/conversations/${chatConversationId}`,
143
- agentId
144
- ),
145
- })
146
- },
147
-
148
- fetchChatConversation: async (
149
- chatAppId: string,
150
- chatConversationId: string,
151
- agentId?: string
152
- ) => {
153
- return await API.get({
154
- url: withAgentIdQuery(
155
- `/api/chatapps/${chatAppId}/conversations/${chatConversationId}`,
156
- agentId
157
- ),
158
- })
159
- },
160
-
161
- fetchChatAppAgents: async (chatAppId: string) => {
162
- return await API.get({
163
- url: `/api/chatapps/${chatAppId}/agents`,
164
- })
165
- },
166
-
167
- fetchChatHistory: async (chatAppId: string, agentId?: string) => {
168
- return await API.get({
169
- url: withAgentIdQuery(
170
- `/api/chatapps/${chatAppId}/conversations`,
171
- agentId
172
- ),
173
- })
174
- },
175
-
176
- fetchChatApp: async (workspaceId?: string) => {
177
- const url = "/api/chatapps"
178
- const headers = workspaceId
179
- ? {
180
- [Header.WORKSPACE_ID]: workspaceId,
181
- }
182
- : undefined
183
- return await API.get({
184
- url,
185
- ...(headers && { headers }),
186
- })
187
- },
188
-
189
- setChatAppAgent: async (chatAppId: string, agentId: string) => {
190
- if (!chatAppId) {
191
- throw new Error("chatAppId is required to set chat app agent")
192
- }
193
- if (!agentId) {
194
- throw new Error("agentId is required to set chat app agent")
195
- }
196
- return await API.post({
197
- url: `/api/chatapps/${chatAppId}/agent`,
198
- body: { agentId } as any,
199
- })
200
- },
201
-
202
- fetchChatAppAgentFileUrl: async (
203
- chatAppId: string,
204
- agentId: string,
205
- fileId: string,
206
- operationId: string
207
- ) => {
208
- if (!operationId) {
209
- throw new Error("operationId is required to fetch a chat app agent file")
210
- }
211
- return await API.get<FetchAgentFileUrlResponse>({
212
- url: `/api/chatapps/${chatAppId}/agents/${agentId}/operations/${operationId}/files/${fileId}/url`,
213
- })
214
- },
215
-
216
- createChatConversation: async (
217
- chat: CreateChatConversationRequest,
218
- workspaceId?: string
219
- ) => {
220
- const resolvedWorkspaceId = workspaceId || API.getAppID()
221
- const { chatAppId } = chat
222
- if (!chatAppId) {
223
- throw new Error("chatAppId is required to create a chat conversation")
224
- }
225
-
226
- const headers: Record<string, string> = {
227
- "Content-Type": "application/json",
228
- Accept: "application/json",
229
- }
230
-
231
- if (resolvedWorkspaceId) {
232
- headers[Header.WORKSPACE_ID] = resolvedWorkspaceId
233
- }
234
-
235
- const response = await fetch(`/api/chatapps/${chatAppId}/conversations`, {
236
- method: "POST",
237
- headers,
238
- credentials: "same-origin",
239
- body: JSON.stringify(chat),
240
- })
241
-
242
- if (!response.ok) {
243
- const errorBody = await response.json().catch(() => null)
244
- throw new Error(
245
- errorBody?.message || `HTTP error! status: ${response.status}`
246
- )
247
- }
248
-
249
- return (await response.json()) as ChatConversation
250
- },
251
-
252
- updateChatApp: async (chatApp: UpdateChatAppRequest) => {
253
- if (!chatApp._id) {
254
- throw new Error("chatAppId is required to update a chat app")
255
- }
256
- return await API.put({
257
- url: `/api/chatapps/${chatApp._id}`,
258
- body: chatApp as any,
259
- })
260
- },
261
-
262
- getPublishedChatApps: async () => {
263
- const response = await API.get<FetchPublishedChatAppsResponse>({
264
- url: "/api/client/chatapps",
265
- })
266
- return response.chatApps
267
- },
268
- })
@@ -1,401 +0,0 @@
1
- <script lang="ts">
2
- import { createEventDispatcher, onMount } from "svelte"
3
- import { Body, Icon } from "@budibase/bbui"
4
- import type { ChatConversation, DraftChatConversation } from "@budibase/types"
5
- import Chatbox from "./index.svelte"
6
-
7
- type AgentAvailability =
8
- | "no_selection"
9
- | "deleted"
10
- | "offline"
11
- | "disabled"
12
- | "ready"
13
-
14
- type ChatConversationLike = ChatConversation | DraftChatConversation
15
-
16
- type EnabledAgentListItem = {
17
- agentId: string
18
- name?: string
19
- icon?: string
20
- iconColor?: string
21
- }
22
-
23
- export let selectedAgentId: string | null = null
24
- export let enabledAgentList: EnabledAgentListItem[] = []
25
- export let conversationStarters: { prompt: string }[] = []
26
- export let agentAvailability: AgentAvailability = "ready"
27
-
28
- export let chat: ChatConversationLike
29
- export let suppressAgentPicker: boolean = false
30
- export let workspaceId: string
31
- export let initialPrompt: string = ""
32
- export let userName: string = ""
33
-
34
- const dispatch = createEventDispatcher<{
35
- chatSaved: { chatId?: string; chat: ChatConversationLike }
36
- agentSelected: { agentId: string }
37
- startChat: { agentId: string; prompt: string }
38
- }>()
39
-
40
- const hasChatId = (value: ChatConversationLike) =>
41
- value && "_id" in value && Boolean(value._id)
42
-
43
- const buildGreeting = (name: string) => {
44
- const currentDate = new Date()
45
- const suffix = name ? `, ${name}` : ""
46
-
47
- if (currentDate.getDay() === 1) {
48
- return `Happy Monday${suffix}`
49
- }
50
-
51
- const isMorning = currentDate.getHours() < 12
52
- return `${isMorning ? "Good Morning" : "Good Afternoon"}${suffix}`
53
- }
54
-
55
- let readOnlyReason: "disabled" | "deleted" | "offline" | undefined
56
-
57
- let draftPrompt = ""
58
- let draftPromptInput: HTMLInputElement | null = null
59
-
60
- $: greetingText = buildGreeting(userName)
61
-
62
- $: visibleAgentList = enabledAgentList.slice(0, 3)
63
- $: hasEnabledAgents = enabledAgentList.length > 0
64
-
65
- const getReadOnlyReason = (
66
- availability: AgentAvailability
67
- ): "disabled" | "deleted" | "offline" | undefined => {
68
- switch (availability) {
69
- case "deleted":
70
- return "deleted"
71
- case "offline":
72
- return "offline"
73
- case "disabled":
74
- return "disabled"
75
- default:
76
- return undefined
77
- }
78
- }
79
-
80
- $: readOnlyReason = getReadOnlyReason(agentAvailability)
81
-
82
- const selectAgent = (agentId: string) => {
83
- dispatch("agentSelected", { agentId })
84
- }
85
-
86
- const startChat = () => {
87
- const prompt = draftPrompt.trim()
88
- if (!prompt) {
89
- return
90
- }
91
-
92
- const agentId = enabledAgentList[0]?.agentId
93
- if (!agentId) {
94
- return
95
- }
96
-
97
- dispatch("startChat", { agentId, prompt })
98
- draftPrompt = ""
99
- }
100
-
101
- const handlePromptKeyDown = (event: KeyboardEvent) => {
102
- if (event.key !== "Enter") {
103
- return
104
- }
105
-
106
- event.preventDefault()
107
- startChat()
108
- }
109
-
110
- onMount(() => {
111
- draftPromptInput?.focus()
112
- })
113
- </script>
114
-
115
- <div class="chat-wrapper">
116
- {#if selectedAgentId}
117
- {#if hasChatId(chat)}
118
- <div class="chat-header">
119
- <div class="chat-header-title">
120
- <Body size="S">{chat.title || "Untitled Chat"}</Body>
121
- </div>
122
- </div>
123
- {/if}
124
-
125
- <Chatbox
126
- bind:chat
127
- {workspaceId}
128
- {conversationStarters}
129
- {initialPrompt}
130
- readOnly={Boolean(readOnlyReason)}
131
- {readOnlyReason}
132
- onchatsaved={event => dispatch("chatSaved", event.detail)}
133
- />
134
- {:else if !suppressAgentPicker}
135
- <div class="chat-empty">
136
- <div class="chat-empty-greeting">
137
- <Body size="XL" weight="600">
138
- {greetingText}
139
- </Body>
140
- </div>
141
- <div class="chat-empty-input" role="presentation">
142
- <input
143
- class="chat-empty-input-field"
144
- type="text"
145
- placeholder="How can I help you today?"
146
- bind:this={draftPromptInput}
147
- bind:value={draftPrompt}
148
- on:keydown={handlePromptKeyDown}
149
- disabled={!hasEnabledAgents}
150
- />
151
- <button
152
- class="chat-empty-input-action"
153
- type="button"
154
- on:click={startChat}
155
- disabled={!hasEnabledAgents}
156
- aria-label="Start chat"
157
- >
158
- <Icon name="arrow-up" size="S" />
159
- </button>
160
- </div>
161
- <div class="chat-empty-grid">
162
- {#if visibleAgentList.length}
163
- {#each visibleAgentList as agent (agent.agentId)}
164
- <button
165
- class="chat-empty-card"
166
- class:chat-empty-card-single={visibleAgentList.length === 1}
167
- on:click={() => selectAgent(agent.agentId)}
168
- style={`--agent-icon-color:${
169
- agent.iconColor ||
170
- "var(--spectrum-semantic-cta-color-background-default)"
171
- };`}
172
- >
173
- <div class="chat-empty-card-head">
174
- <div class="chat-empty-card-icon">
175
- <Icon
176
- name={agent.icon || "SideKick"}
177
- size="S"
178
- color="var(--agent-icon-color)"
179
- />
180
- </div>
181
- <Body size="S" weight="500">
182
- {agent.name || "Unknown agent"}
183
- </Body>
184
- </div>
185
- <div class="chat-empty-card-subtitle">
186
- <Body size="XS" color="var(--spectrum-global-color-gray-600)">
187
- Start a chat with this agent.
188
- </Body>
189
- </div>
190
- </button>
191
- {/each}
192
- {:else}
193
- <Body size="S" color="var(--spectrum-global-color-gray-500)">
194
- No enabled agents
195
- </Body>
196
- {/if}
197
- </div>
198
- </div>
199
- {/if}
200
- </div>
201
-
202
- <style>
203
- .chat-wrapper {
204
- flex: 1 1 auto;
205
- display: flex;
206
- flex-direction: column;
207
- padding: 0 32px 32px 32px;
208
- box-sizing: border-box;
209
- min-width: 0;
210
- min-height: 0;
211
- }
212
-
213
- .chat-header {
214
- width: 100%;
215
- padding: var(--spacing-l) 0 var(--spacing-l);
216
- display: flex;
217
- align-items: center;
218
- justify-content: space-between;
219
- gap: var(--spacing-m);
220
- border-bottom: var(--border-light);
221
- }
222
-
223
- .chat-header-title {
224
- min-width: 0;
225
- flex: 1 1 auto;
226
- }
227
-
228
- .chat-header-title :global(p) {
229
- overflow: hidden;
230
- text-overflow: ellipsis;
231
- white-space: nowrap;
232
- font-size: 14px;
233
- line-height: 17px;
234
- letter-spacing: 0;
235
- font-weight: 400;
236
- color: var(--spectrum-alias-text-color);
237
- }
238
-
239
- .chat-empty {
240
- flex: 1 1 auto;
241
- display: flex;
242
- flex-direction: column;
243
- justify-content: center;
244
- align-items: center;
245
- gap: 32px;
246
- padding: var(--spacing-xxl);
247
- text-align: center;
248
- }
249
-
250
- .chat-empty-greeting :global(p) {
251
- color: var(--spectrum-alias-text-color);
252
- font-size: 28px;
253
- line-height: 34px;
254
- }
255
-
256
- .chat-empty-input {
257
- display: flex;
258
- align-items: center;
259
- gap: var(--spacing-m);
260
- width: 600px;
261
- padding: 10px;
262
- padding-left: 20px;
263
- border-radius: 999px;
264
- background: var(--spectrum-alias-background-color-secondary);
265
- color: var(--spectrum-alias-text-color);
266
- border: 1px solid var(--spectrum-alias-border-color);
267
- }
268
-
269
- .chat-empty-input-field {
270
- flex: 1;
271
- font-size: 16px;
272
- color: var(--spectrum-alias-text-color);
273
- background: transparent;
274
- border: none;
275
- outline: none;
276
- font: inherit;
277
- }
278
-
279
- .chat-empty-input-field::placeholder {
280
- color: var(--spectrum-alias-text-color-disabled);
281
- }
282
-
283
- .chat-empty-input-action {
284
- width: 32px;
285
- height: 32px;
286
- border-radius: 999px;
287
- background: var(--spectrum-semantic-cta-color-background-default);
288
- color: var(--spectrum-global-color-gray-50);
289
- display: inline-flex;
290
- align-items: center;
291
- justify-content: center;
292
- border: none;
293
- cursor: pointer;
294
- }
295
-
296
- .chat-empty-input-action:disabled,
297
- .chat-empty-input-field:disabled {
298
- opacity: 0.5;
299
- cursor: not-allowed;
300
- }
301
-
302
- .chat-empty-input-action:hover {
303
- background: var(--spectrum-semantic-cta-color-background-hover);
304
- }
305
-
306
- .chat-empty-grid {
307
- display: flex;
308
- flex-direction: row;
309
- gap: 16px;
310
- width: min(720px, 100%);
311
- align-items: center;
312
- justify-content: center;
313
- }
314
-
315
- .chat-empty-card {
316
- border: 1px solid var(--spectrum-alias-border-color);
317
- width: 240px;
318
- border-radius: 16px;
319
- padding: 0;
320
- background: var(--spectrum-alias-background-color-primary);
321
- color: var(--spectrum-alias-text-color);
322
- font: inherit;
323
- cursor: pointer;
324
- text-align: left;
325
- overflow: hidden;
326
- transform: translateY(var(--card-offset, 0px))
327
- rotate(var(--card-rotation, 0deg));
328
- transition:
329
- border-color 150ms ease,
330
- transform 150ms ease;
331
- }
332
-
333
- .chat-empty-card:hover {
334
- border-color: var(--spectrum-alias-border-color-hover);
335
- transform: translateY(calc(var(--card-offset, 0px) - 3px))
336
- rotate(var(--card-rotation, 0deg));
337
- }
338
-
339
- .chat-empty-card:first-child {
340
- --card-rotation: -6deg;
341
- --card-offset: 12px;
342
- }
343
-
344
- .chat-empty-card:last-child {
345
- --card-rotation: 6deg;
346
- --card-offset: 12px;
347
- }
348
-
349
- .chat-empty-card.chat-empty-card-single {
350
- --card-rotation: 0deg;
351
- --card-offset: 0px;
352
- }
353
-
354
- .chat-empty-card-head {
355
- display: flex;
356
- align-items: center;
357
- gap: var(--spacing-s);
358
- padding: var(--spacing-m);
359
- background-color: var(--spectrum-alias-background-color-secondary);
360
- color: var(--spectrum-alias-text-color);
361
- border-bottom: 1px solid var(--spectrum-alias-border-color);
362
- }
363
-
364
- .chat-empty-card-icon {
365
- width: 28px;
366
- height: 28px;
367
- border-radius: 8px;
368
- display: inline-flex;
369
- align-items: center;
370
- justify-content: center;
371
- background: transparent;
372
- }
373
-
374
- .chat-empty-card-subtitle {
375
- padding: var(--spacing-m);
376
- }
377
-
378
- @media (max-width: 1000px) {
379
- .chat-wrapper {
380
- padding: 0 var(--spacing-l) var(--spacing-l);
381
- }
382
-
383
- .chat-empty-input {
384
- width: min(600px, 100%);
385
- }
386
-
387
- .chat-empty-grid {
388
- flex-direction: column;
389
- }
390
-
391
- .chat-empty-card {
392
- width: min(360px, 100%);
393
- }
394
-
395
- .chat-empty-card:first-child,
396
- .chat-empty-card:last-child {
397
- --card-rotation: 0deg;
398
- --card-offset: 0px;
399
- }
400
- }
401
- </style>