@budibase/frontend-core 3.23.47 → 3.23.48

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.47",
3
+ "version": "3.23.48",
4
4
  "description": "Budibase frontend core libraries used in builder and client",
5
5
  "author": "Budibase",
6
6
  "license": "MPL-2.0",
@@ -12,11 +12,11 @@
12
12
  "@budibase/bbui": "*",
13
13
  "@budibase/shared-core": "*",
14
14
  "@budibase/types": "*",
15
- "ai": "^5.0.93",
15
+ "ai": "^6.0.3",
16
16
  "dayjs": "^1.10.8",
17
17
  "lodash": "4.17.21",
18
18
  "shortid": "2.2.15",
19
19
  "socket.io-client": "^4.7.5"
20
20
  },
21
- "gitHead": "534845c38ed0fe209406ebe45fd87f3de79f1da5"
21
+ "gitHead": "0a78faa2adbb8fb3b81de2b9b00352e60178f12f"
22
22
  }
package/src/api/agents.ts CHANGED
@@ -1,29 +1,15 @@
1
1
  import {
2
- AgentChat,
3
- ChatAgentRequest,
4
2
  CreateAgentRequest,
5
3
  CreateAgentResponse,
6
- FetchAgentHistoryResponse,
7
4
  FetchAgentsResponse,
8
5
  ToolMetadata,
9
6
  UpdateAgentRequest,
10
7
  UpdateAgentResponse,
11
8
  } from "@budibase/types"
12
9
 
13
- import { Header } from "@budibase/shared-core"
14
10
  import { BaseAPIClient } from "./types"
15
- import { readUIMessageStream, UIMessage, UIMessageChunk } from "ai"
16
- import { createSseToJsonTransformStream } from "../utils/utils"
17
11
 
18
12
  export interface AgentEndpoints {
19
- agentChatStream: (
20
- chat: AgentChat,
21
- workspaceId: string
22
- ) => Promise<AsyncIterable<UIMessage>>
23
-
24
- removeChat: (chatId: string) => Promise<void>
25
- fetchChats: (agentId: string) => Promise<FetchAgentHistoryResponse>
26
-
27
13
  fetchTools: () => Promise<ToolMetadata[]>
28
14
  fetchAgents: () => Promise<FetchAgentsResponse>
29
15
  createAgent: (agent: CreateAgentRequest) => Promise<CreateAgentResponse>
@@ -31,71 +17,7 @@ export interface AgentEndpoints {
31
17
  deleteAgent: (agentId: string) => Promise<{ deleted: true }>
32
18
  }
33
19
 
34
- const throwOnErrorChunk = () =>
35
- new TransformStream<UIMessageChunk, UIMessageChunk>({
36
- transform(chunk, controller) {
37
- if (chunk.type === "error") {
38
- throw new Error(chunk.errorText || "Agent action failed")
39
- }
40
- controller.enqueue(chunk)
41
- },
42
- })
43
-
44
20
  export const buildAgentEndpoints = (API: BaseAPIClient): AgentEndpoints => ({
45
- agentChatStream: async (chat, workspaceId) => {
46
- const body: ChatAgentRequest = chat
47
-
48
- const response = await fetch("/api/agent/chat/stream", {
49
- method: "POST",
50
- headers: {
51
- "Content-Type": "application/json",
52
- Accept: "application/json",
53
- [Header.APP_ID]: workspaceId,
54
- },
55
- body: JSON.stringify(body),
56
- credentials: "same-origin",
57
- })
58
-
59
- if (!response.ok) {
60
- const errorBody = await response.json()
61
- throw new Error(
62
- errorBody.message || `HTTP error! status: ${response.status}`
63
- )
64
- }
65
-
66
- if (!response.body) {
67
- throw new Error("Failed to get response body")
68
- }
69
-
70
- const chunkStream = response.body
71
- .pipeThrough(new TextDecoderStream())
72
- .pipeThrough(createSseToJsonTransformStream<UIMessageChunk>())
73
- .pipeThrough(throwOnErrorChunk())
74
-
75
- return readUIMessageStream({
76
- stream: chunkStream,
77
- terminateOnError: true,
78
- })
79
- },
80
-
81
- removeChat: async (chatId: string) => {
82
- return await API.delete({
83
- url: `/api/agent/chats/${chatId}`,
84
- })
85
- },
86
-
87
- fetchChats: async (agentId: string) => {
88
- return await API.get({
89
- url: `/api/agent/${agentId}/chats`,
90
- })
91
- },
92
-
93
- fetchTools: async () => {
94
- return await API.get({
95
- url: `/api/agent/tools`,
96
- })
97
- },
98
-
99
21
  fetchAgents: async () => {
100
22
  return await API.get({
101
23
  url: "/api/agent",
@@ -121,4 +43,10 @@ export const buildAgentEndpoints = (API: BaseAPIClient): AgentEndpoints => ({
121
43
  url: `/api/agent/${agentId}`,
122
44
  })
123
45
  },
46
+
47
+ fetchTools: async () => {
48
+ return await API.get({
49
+ url: `/api/agent/tools`,
50
+ })
51
+ },
124
52
  })
@@ -0,0 +1,190 @@
1
+ import {
2
+ ChatAgentRequest,
3
+ ChatConversation,
4
+ ChatConversationRequest,
5
+ CreateChatConversationRequest,
6
+ ChatApp,
7
+ FetchAgentHistoryResponse,
8
+ UpdateChatAppRequest,
9
+ } from "@budibase/types"
10
+ import { Header } from "@budibase/shared-core"
11
+ import { BaseAPIClient } from "./types"
12
+ import { readUIMessageStream, UIMessage, UIMessageChunk } from "ai"
13
+ import { createSseToJsonTransformStream } from "../utils/utils"
14
+
15
+ export interface ChatAppEndpoints {
16
+ streamChatConversation: (
17
+ chat: ChatConversationRequest,
18
+ workspaceId: string
19
+ ) => Promise<AsyncIterable<UIMessage>>
20
+ deleteChatConversation: (
21
+ chatConversationId: string,
22
+ chatAppId: string
23
+ ) => Promise<void>
24
+ fetchChatConversation: (
25
+ chatAppId: string,
26
+ chatConversationId: string
27
+ ) => Promise<ChatConversation>
28
+ fetchChatHistory: (chatAppId: string) => Promise<FetchAgentHistoryResponse>
29
+ fetchChatApp: (workspaceId?: string) => Promise<ChatApp | null>
30
+ setChatAppAgent: (chatAppId: string, agentId: string) => Promise<ChatApp>
31
+ createChatConversation: (
32
+ chat: CreateChatConversationRequest,
33
+ workspaceId?: string
34
+ ) => Promise<ChatConversation>
35
+ updateChatApp: (chatApp: UpdateChatAppRequest) => Promise<ChatApp>
36
+ }
37
+
38
+ const throwOnErrorChunk = () =>
39
+ new TransformStream<UIMessageChunk, UIMessageChunk>({
40
+ transform(chunk, controller) {
41
+ if (chunk.type === "error") {
42
+ throw new Error(chunk.errorText || "Agent action failed")
43
+ }
44
+ controller.enqueue(chunk)
45
+ },
46
+ })
47
+
48
+ export const buildChatAppEndpoints = (
49
+ API: BaseAPIClient
50
+ ): ChatAppEndpoints => ({
51
+ streamChatConversation: async (chat, workspaceId) => {
52
+ if (!chat.chatAppId) {
53
+ throw new Error("chatAppId is required to stream a chat conversation")
54
+ }
55
+
56
+ const body: ChatAgentRequest = chat
57
+ const conversationId = chat._id || "new"
58
+
59
+ const response = await fetch(
60
+ `/api/chatapps/${chat.chatAppId}/conversations/${conversationId}/stream`,
61
+ {
62
+ method: "POST",
63
+ headers: {
64
+ "Content-Type": "application/json",
65
+ Accept: "application/json",
66
+ [Header.APP_ID]: workspaceId,
67
+ },
68
+ body: JSON.stringify(body),
69
+ credentials: "same-origin",
70
+ }
71
+ )
72
+
73
+ if (!response.ok) {
74
+ const errorBody = await response.json()
75
+ throw new Error(
76
+ errorBody.message || `HTTP error! status: ${response.status}`
77
+ )
78
+ }
79
+
80
+ if (!response.body) {
81
+ throw new Error("Failed to get response body")
82
+ }
83
+
84
+ const chunkStream = response.body
85
+ .pipeThrough(new TextDecoderStream())
86
+ .pipeThrough(createSseToJsonTransformStream<UIMessageChunk>())
87
+ .pipeThrough(throwOnErrorChunk())
88
+
89
+ return readUIMessageStream({
90
+ stream: chunkStream,
91
+ terminateOnError: true,
92
+ })
93
+ },
94
+
95
+ deleteChatConversation: async (
96
+ chatConversationId: string,
97
+ chatAppId: string
98
+ ) => {
99
+ return await API.delete({
100
+ url: `/api/chatapps/${chatAppId}/conversations/${chatConversationId}`,
101
+ })
102
+ },
103
+
104
+ fetchChatConversation: async (
105
+ chatAppId: string,
106
+ chatConversationId: string
107
+ ) => {
108
+ return await API.get({
109
+ url: `/api/chatapps/${chatAppId}/conversations/${chatConversationId}`,
110
+ })
111
+ },
112
+
113
+ fetchChatHistory: async (chatAppId: string) => {
114
+ return await API.get({
115
+ url: `/api/chatapps/${chatAppId}/conversations`,
116
+ })
117
+ },
118
+
119
+ fetchChatApp: async (workspaceId?: string) => {
120
+ const url = "/api/chatapps"
121
+ const headers = workspaceId
122
+ ? {
123
+ [Header.APP_ID]: workspaceId,
124
+ }
125
+ : undefined
126
+ return await API.get({
127
+ url,
128
+ ...(headers && { headers }),
129
+ })
130
+ },
131
+
132
+ setChatAppAgent: async (chatAppId: string, agentId: string) => {
133
+ if (!chatAppId) {
134
+ throw new Error("chatAppId is required to set chat app agent")
135
+ }
136
+ if (!agentId) {
137
+ throw new Error("agentId is required to set chat app agent")
138
+ }
139
+ return await API.post({
140
+ url: `/api/chatapps/${chatAppId}/agent`,
141
+ body: { agentId } as any,
142
+ })
143
+ },
144
+
145
+ createChatConversation: async (
146
+ chat: CreateChatConversationRequest,
147
+ workspaceId?: string
148
+ ) => {
149
+ const resolvedWorkspaceId = workspaceId || API.getAppID()
150
+ const { chatAppId } = chat
151
+ if (!chatAppId) {
152
+ throw new Error("chatAppId is required to create a chat conversation")
153
+ }
154
+
155
+ const headers: Record<string, string> = {
156
+ "Content-Type": "application/json",
157
+ Accept: "application/json",
158
+ }
159
+
160
+ if (resolvedWorkspaceId) {
161
+ headers[Header.APP_ID] = resolvedWorkspaceId
162
+ }
163
+
164
+ const response = await fetch(`/api/chatapps/${chatAppId}/conversations`, {
165
+ method: "POST",
166
+ headers,
167
+ credentials: "same-origin",
168
+ body: JSON.stringify(chat),
169
+ })
170
+
171
+ if (!response.ok) {
172
+ const errorBody = await response.json().catch(() => null)
173
+ throw new Error(
174
+ errorBody?.message || `HTTP error! status: ${response.status}`
175
+ )
176
+ }
177
+
178
+ return (await response.json()) as ChatConversation
179
+ },
180
+
181
+ updateChatApp: async (chatApp: UpdateChatAppRequest) => {
182
+ if (!chatApp._id) {
183
+ throw new Error("chatAppId is required to update a chat app")
184
+ }
185
+ return await API.put({
186
+ url: `/api/chatapps/${chatApp._id}`,
187
+ body: chatApp as any,
188
+ })
189
+ },
190
+ })
package/src/api/index.ts CHANGED
@@ -47,6 +47,7 @@ import { buildMigrationEndpoints } from "./migrations"
47
47
  import { buildRowActionEndpoints } from "./rowActions"
48
48
  import { buildOAuth2Endpoints } from "./oauth2"
49
49
  import { buildAgentEndpoints } from "./agents"
50
+ import { buildChatAppEndpoints } from "./chatApps"
50
51
  import { buildFeatureFlagEndpoints } from "./features"
51
52
  import { buildNavigationEndpoints } from "./navigation"
52
53
  import { buildWorkspaceAppEndpoints } from "./workspaceApps"
@@ -313,6 +314,7 @@ export const createAPIClient = (config: APIClientConfig = {}): APIClient => {
313
314
  ...buildLogsEndpoints(API),
314
315
  ...buildMigrationEndpoints(API),
315
316
  ...buildAgentEndpoints(API),
317
+ ...buildChatAppEndpoints(API),
316
318
  ...buildFeatureFlagEndpoints(API),
317
319
  deployment: buildDeploymentEndpoints(API),
318
320
  viewV2: buildViewV2Endpoints(API),
package/src/api/types.ts CHANGED
@@ -35,6 +35,7 @@ import { UserEndpoints } from "./user"
35
35
  import { ViewEndpoints } from "./views"
36
36
  import { ViewV2Endpoints } from "./viewsV2"
37
37
  import { AgentEndpoints } from "./agents"
38
+ import { ChatAppEndpoints } from "./chatApps"
38
39
  import { NavigationEndpoints } from "./navigation"
39
40
  import { WorkspaceAppEndpoints } from "./workspaceApps"
40
41
  import { ResourceEndpoints } from "./resource"
@@ -113,6 +114,7 @@ export type APIError = {
113
114
  export type APIClient = BaseAPIClient &
114
115
  AIEndpoints &
115
116
  AgentEndpoints &
117
+ ChatAppEndpoints &
116
118
  AnalyticsEndpoints &
117
119
  AppEndpoints &
118
120
  AttachmentEndpoints &
@@ -1,6 +1,10 @@
1
1
  <script lang="ts">
2
2
  import { MarkdownViewer, notifications } from "@budibase/bbui"
3
- import type { AgentChat } from "@budibase/types"
3
+ import type {
4
+ ChatConversation,
5
+ ChatConversationRequest,
6
+ } from "@budibase/types"
7
+ import { Header } from "@budibase/shared-core"
4
8
  import BBAI from "../../icons/BBAI.svelte"
5
9
  import { tick } from "svelte"
6
10
  import { onDestroy } from "svelte"
@@ -10,14 +14,21 @@
10
14
  import type { UIMessage } from "ai"
11
15
  import { v4 as uuidv4 } from "uuid"
12
16
 
13
- export let API = createAPIClient()
14
-
15
17
  export let workspaceId: string
16
- export let chat: AgentChat
18
+ export let API = createAPIClient({
19
+ attachHeaders: headers => {
20
+ if (workspaceId) {
21
+ headers[Header.APP_ID] = workspaceId
22
+ }
23
+ },
24
+ })
25
+ type ChatConversationLike = ChatConversation | ChatConversationRequest
26
+
27
+ export let chat: ChatConversationLike
17
28
  export let loading: boolean = false
18
29
 
19
30
  const dispatch = createEventDispatcher<{
20
- chatSaved: { chatId?: string; chat: AgentChat }
31
+ chatSaved: { chatId?: string; chat: ChatConversationLike }
21
32
  }>()
22
33
 
23
34
  let inputValue = ""
@@ -25,12 +36,32 @@
25
36
  let observer: MutationObserver
26
37
  let textareaElement: HTMLTextAreaElement
27
38
  let lastFocusedChatId: string | undefined
28
- let lastFocusedNewChat: AgentChat | undefined
39
+ let lastFocusedNewChat: ChatConversationLike | undefined
29
40
 
30
41
  $: if (chat?.messages?.length) {
31
42
  scrollToBottom()
32
43
  }
33
44
 
45
+ const ensureChatApp = async (): Promise<string | undefined> => {
46
+ if (chat?.chatAppId) {
47
+ return chat.chatAppId
48
+ }
49
+ try {
50
+ const chatApp = await API.fetchChatApp(workspaceId)
51
+ if (chatApp?._id) {
52
+ const baseChat = chat || { title: "", messages: [], chatAppId: "" }
53
+ chat = {
54
+ ...baseChat,
55
+ chatAppId: chatApp._id,
56
+ }
57
+ return chatApp._id
58
+ }
59
+ } catch (err) {
60
+ console.error(err)
61
+ }
62
+ return undefined
63
+ }
64
+
34
65
  async function scrollToBottom() {
35
66
  await tick()
36
67
  if (chatAreaElement) {
@@ -46,8 +77,41 @@
46
77
  }
47
78
 
48
79
  async function prompt() {
80
+ const resolvedChatAppId = await ensureChatApp()
81
+
49
82
  if (!chat) {
50
- chat = { title: "", messages: [] }
83
+ chat = { title: "", messages: [], chatAppId: "" }
84
+ }
85
+
86
+ const chatAppId = chat.chatAppId || resolvedChatAppId
87
+
88
+ if (!chatAppId) {
89
+ notifications.error("Chat app could not be created")
90
+ return
91
+ }
92
+
93
+ if (!chat._id && (!chat.messages || chat.messages.length === 0)) {
94
+ try {
95
+ const newChat = await API.createChatConversation(
96
+ {
97
+ chatAppId,
98
+ title: chat.title,
99
+ },
100
+ workspaceId
101
+ )
102
+
103
+ chat = {
104
+ ...chat,
105
+ ...newChat,
106
+ chatAppId,
107
+ }
108
+ } catch (err: any) {
109
+ console.error(err)
110
+ notifications.error(
111
+ err?.message || "Could not start a new chat conversation"
112
+ )
113
+ return
114
+ }
51
115
  }
52
116
 
53
117
  const userMessage: UIMessage = {
@@ -56,8 +120,9 @@
56
120
  parts: [{ type: "text", text: inputValue }],
57
121
  }
58
122
 
59
- const updatedChat: AgentChat = {
123
+ const updatedChat: ChatConversationLike = {
60
124
  ...chat,
125
+ chatAppId: chat.chatAppId,
61
126
  messages: [...chat.messages, userMessage],
62
127
  }
63
128
 
@@ -68,16 +133,41 @@
68
133
  loading = true
69
134
 
70
135
  try {
71
- const messageStream = await API.agentChatStream(updatedChat, workspaceId)
136
+ const messageStream = await API.streamChatConversation(
137
+ updatedChat,
138
+ workspaceId
139
+ )
140
+
141
+ let streamedMessages: UIMessage[] = [...updatedChat.messages]
72
142
 
73
143
  for await (const message of messageStream) {
144
+ streamedMessages = [...streamedMessages, message]
74
145
  chat = {
75
146
  ...updatedChat,
76
- messages: [...updatedChat.messages, message],
147
+ messages: streamedMessages,
77
148
  }
78
149
  scrollToBottom()
79
150
  }
80
151
 
152
+ // When a chat is created for the first time the server generates the ID.
153
+ // If we don't have it locally yet, retrieve the saved conversation so
154
+ // subsequent prompts append to the same document instead of creating a new one.
155
+ if (!chat._id && chat.chatAppId) {
156
+ try {
157
+ const history = await API.fetchChatHistory(chat.chatAppId)
158
+ const lastMessageId = chat.messages[chat.messages.length - 1]?.id
159
+ const savedConversation =
160
+ history?.find(convo =>
161
+ convo.messages.some(message => message.id === lastMessageId)
162
+ ) || history?.[0]
163
+ if (savedConversation) {
164
+ chat = savedConversation
165
+ }
166
+ } catch (historyError) {
167
+ console.error(historyError)
168
+ }
169
+ }
170
+
81
171
  loading = false
82
172
  dispatch("chatSaved", { chatId: chat._id, chat })
83
173
  } catch (err: any) {
@@ -93,6 +183,8 @@
93
183
  }
94
184
 
95
185
  onMount(async () => {
186
+ await ensureChatApp()
187
+
96
188
  // Ensure we always autoscroll to reveal new messages
97
189
  observer = new MutationObserver(async () => {
98
190
  await tick()