@budibase/frontend-core 3.32.4 → 3.32.5

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.32.4",
3
+ "version": "3.32.5",
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": "2bca48effb7679e79385bd7515d1fd604c609d8f"
27
+ "gitHead": "9584bb3f87662f62b08760a78ea5fc0e95818410"
28
28
  }
@@ -0,0 +1,62 @@
1
+ import type {
2
+ AgentLogEnvironment,
3
+ FetchAgentLogsResponse,
4
+ AgentLogRequestDetail,
5
+ AgentLogSession,
6
+ } from "@budibase/types"
7
+ import type { BaseAPIClient } from "./types"
8
+
9
+ export interface AgentLogEndpoints {
10
+ fetchAgentLogs: (
11
+ agentId: string,
12
+ opts?: {
13
+ startDate?: string
14
+ endDate?: string
15
+ bookmark?: string
16
+ limit?: number
17
+ statusFilter?: string
18
+ triggerFilter?: string
19
+ }
20
+ ) => Promise<FetchAgentLogsResponse>
21
+ fetchAgentLogDetail: (
22
+ agentId: string,
23
+ requestId: string
24
+ ) => Promise<AgentLogRequestDetail>
25
+ fetchAgentLogSession: (
26
+ agentId: string,
27
+ sessionId: string,
28
+ environment: AgentLogEnvironment
29
+ ) => Promise<AgentLogSession | null>
30
+ }
31
+
32
+ export const buildAgentLogEndpoints = (
33
+ API: BaseAPIClient
34
+ ): AgentLogEndpoints => ({
35
+ fetchAgentLogs: async (agentId, opts = {}) => {
36
+ const params = new URLSearchParams()
37
+ if (opts.startDate) params.set("startDate", opts.startDate)
38
+ if (opts.endDate) params.set("endDate", opts.endDate)
39
+ if (opts.bookmark) params.set("bookmark", opts.bookmark)
40
+ if (opts.limit !== undefined) params.set("limit", String(opts.limit))
41
+ if (opts.statusFilter) params.set("statusFilter", opts.statusFilter)
42
+ if (opts.triggerFilter) params.set("triggerFilter", opts.triggerFilter)
43
+ const query = params.toString()
44
+ return await API.get({
45
+ url: `/api/agent/${agentId}/logs${query ? `?${query}` : ""}`,
46
+ })
47
+ },
48
+
49
+ fetchAgentLogDetail: async (agentId, requestId) => {
50
+ return await API.get({
51
+ url: `/api/agent/${agentId}/logs/${requestId}`,
52
+ })
53
+ },
54
+
55
+ fetchAgentLogSession: async (agentId, sessionId, environment) => {
56
+ const params = new URLSearchParams({ sessionId })
57
+ params.set("environment", environment)
58
+ return await API.get({
59
+ url: `/api/agent/${agentId}/logs/session?${params.toString()}`,
60
+ })
61
+ },
62
+ })
package/src/api/agents.ts CHANGED
@@ -1,9 +1,7 @@
1
1
  import {
2
- AgentFileUploadResponse,
3
2
  CreateAgentRequest,
4
3
  CreateAgentResponse,
5
4
  DuplicateAgentResponse,
6
- FetchAgentFilesResponse,
7
5
  FetchAgentsResponse,
8
6
  ProvisionAgentSlackChannelRequest,
9
7
  ProvisionAgentSlackChannelResponse,
@@ -27,15 +25,6 @@ export interface AgentEndpoints {
27
25
  updateAgent: (agent: UpdateAgentRequest) => Promise<UpdateAgentResponse>
28
26
  duplicateAgent: (agentId: string) => Promise<DuplicateAgentResponse>
29
27
  deleteAgent: (agentId: string) => Promise<{ deleted: true }>
30
- fetchAgentFiles: (agentId: string) => Promise<FetchAgentFilesResponse>
31
- uploadAgentFile: (
32
- agentId: string,
33
- file: File
34
- ) => Promise<AgentFileUploadResponse>
35
- deleteAgentFile: (
36
- agentId: string,
37
- fileId: string
38
- ) => Promise<{ deleted: true }>
39
28
  syncAgentDiscordCommands: (
40
29
  agentId: string,
41
30
  body?: SyncAgentDiscordCommandsRequest
@@ -103,28 +92,6 @@ export const buildAgentEndpoints = (API: BaseAPIClient): AgentEndpoints => ({
103
92
  })
104
93
  },
105
94
 
106
- fetchAgentFiles: async (agentId: string) => {
107
- return await API.get({
108
- url: `/api/agent/${agentId}/files`,
109
- })
110
- },
111
-
112
- uploadAgentFile: async (agentId: string, file: File) => {
113
- const formData = new FormData()
114
- formData.append("file", file)
115
- return await API.post<FormData, AgentFileUploadResponse>({
116
- url: `/api/agent/${agentId}/files`,
117
- body: formData,
118
- json: false,
119
- })
120
- },
121
-
122
- deleteAgentFile: async (agentId: string, fileId: string) => {
123
- return await API.delete({
124
- url: `/api/agent/${agentId}/files/${fileId}`,
125
- })
126
- },
127
-
128
95
  syncAgentDiscordCommands: async (agentId: string, body) => {
129
96
  return await API.post<
130
97
  SyncAgentDiscordCommandsRequest | undefined,
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 { buildAgentLogEndpoints } from "./agentLogs"
50
51
  import { buildChatAppEndpoints } from "./chatApps"
51
52
  import { buildFeatureFlagEndpoints } from "./features"
52
53
  import { buildNavigationEndpoints } from "./navigation"
@@ -58,6 +59,7 @@ import { buildWorkspaceHomeEndpoints } from "./workspaceHome"
58
59
  import { buildRecaptchaEndpoints } from "./recaptcha"
59
60
  import { buildAIConfigEndpoints } from "./aiConfig"
60
61
  import { buildVectorDbEndpoints } from "./vectorDbs"
62
+ import { buildKnowledgeBaseEndpoints } from "./knowledgeBases"
61
63
 
62
64
  export type { APIClient } from "./types"
63
65
 
@@ -317,6 +319,7 @@ export const createAPIClient = (config: APIClientConfig = {}): APIClient => {
317
319
  ...buildLogsEndpoints(API),
318
320
  ...buildMigrationEndpoints(API),
319
321
  ...buildAgentEndpoints(API),
322
+ ...buildAgentLogEndpoints(API),
320
323
  ...buildChatAppEndpoints(API),
321
324
  ...buildFeatureFlagEndpoints(API),
322
325
  deployment: buildDeploymentEndpoints(API),
@@ -331,5 +334,6 @@ export const createAPIClient = (config: APIClientConfig = {}): APIClient => {
331
334
  recaptcha: buildRecaptchaEndpoints(API),
332
335
  aiConfig: buildAIConfigEndpoints(API),
333
336
  vectorDb: buildVectorDbEndpoints(API),
337
+ knowledgeBase: buildKnowledgeBaseEndpoints(API),
334
338
  }
335
339
  }
@@ -0,0 +1,79 @@
1
+ import {
2
+ CreateKnowledgeBaseRequest,
3
+ FetchKnowledgeBaseFilesResponse,
4
+ KnowledgeBase,
5
+ KnowledgeBaseFileUploadResponse,
6
+ KnowledgeBaseListResponse,
7
+ UpdateKnowledgeBaseRequest,
8
+ } from "@budibase/types"
9
+ import { BaseAPIClient } from "./types"
10
+
11
+ export interface KnowledgeBaseEndpoints {
12
+ fetch: () => Promise<KnowledgeBaseListResponse>
13
+ create: (config: CreateKnowledgeBaseRequest) => Promise<KnowledgeBase>
14
+ update: (config: UpdateKnowledgeBaseRequest) => Promise<KnowledgeBase>
15
+ delete: (id: string) => Promise<{ deleted: true }>
16
+ fetchFiles: (
17
+ knowledgeBaseId: string
18
+ ) => Promise<FetchKnowledgeBaseFilesResponse>
19
+ uploadFile: (
20
+ knowledgeBaseId: string,
21
+ file: File
22
+ ) => Promise<KnowledgeBaseFileUploadResponse>
23
+ deleteFile: (
24
+ knowledgeBaseId: string,
25
+ fileId: string
26
+ ) => Promise<{ deleted: true }>
27
+ }
28
+
29
+ export const buildKnowledgeBaseEndpoints = (
30
+ API: BaseAPIClient
31
+ ): KnowledgeBaseEndpoints => ({
32
+ fetch: async () => {
33
+ return await API.get({
34
+ url: "/api/knowledge-base",
35
+ })
36
+ },
37
+
38
+ create: async config => {
39
+ return await API.post({
40
+ url: "/api/knowledge-base",
41
+ body: config,
42
+ })
43
+ },
44
+
45
+ update: async config => {
46
+ return await API.put({
47
+ url: "/api/knowledge-base",
48
+ body: config,
49
+ })
50
+ },
51
+
52
+ delete: async id => {
53
+ return await API.delete({
54
+ url: `/api/knowledge-base/${id}`,
55
+ })
56
+ },
57
+
58
+ fetchFiles: async knowledgeBaseId => {
59
+ return await API.get({
60
+ url: `/api/knowledge-base/${knowledgeBaseId}/files`,
61
+ })
62
+ },
63
+
64
+ uploadFile: async (knowledgeBaseId, file) => {
65
+ const formData = new FormData()
66
+ formData.append("file", file)
67
+ return await API.post<FormData, KnowledgeBaseFileUploadResponse>({
68
+ url: `/api/knowledge-base/${knowledgeBaseId}/files`,
69
+ body: formData,
70
+ json: false,
71
+ })
72
+ },
73
+
74
+ deleteFile: async (knowledgeBaseId, fileId) => {
75
+ return await API.delete({
76
+ url: `/api/knowledge-base/${knowledgeBaseId}/files/${fileId}`,
77
+ })
78
+ },
79
+ })
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 { AgentLogEndpoints } from "./agentLogs"
38
39
  import { ChatAppEndpoints } from "./chatApps"
39
40
  import { NavigationEndpoints } from "./navigation"
40
41
  import { WorkspaceAppEndpoints } from "./workspaceApps"
@@ -45,6 +46,7 @@ import { WorkspaceHomeEndpoints } from "./workspaceHome"
45
46
  import { RecaptchaEndpoints } from "./recaptcha"
46
47
  import { AIConfigEndpoints } from "./aiConfig"
47
48
  import { VectorDbEndpoints } from "./vectorDbs"
49
+ import { KnowledgeBaseEndpoints } from "./knowledgeBases"
48
50
 
49
51
  export enum HTTPMethod {
50
52
  POST = "POST",
@@ -116,6 +118,7 @@ export type APIError = {
116
118
  export type APIClient = BaseAPIClient &
117
119
  AIEndpoints &
118
120
  AgentEndpoints &
121
+ AgentLogEndpoints &
119
122
  ChatAppEndpoints &
120
123
  AnalyticsEndpoints &
121
124
  AppEndpoints &
@@ -161,4 +164,5 @@ export type APIClient = BaseAPIClient &
161
164
  recaptcha: RecaptchaEndpoints
162
165
  aiConfig: AIConfigEndpoints
163
166
  vectorDb: VectorDbEndpoints
167
+ knowledgeBase: KnowledgeBaseEndpoints
164
168
  }
@@ -16,6 +16,7 @@
16
16
  import { tick } from "svelte"
17
17
  import { createAPIClient } from "@budibase/frontend-core"
18
18
  import { Chat } from "@ai-sdk/svelte"
19
+ import { formatToolName } from "../../utils/aiTools"
19
20
  import {
20
21
  DefaultChatTransport,
21
22
  isTextUIPart,
@@ -63,7 +64,10 @@
63
64
  })
64
65
  )
65
66
 
66
- let stableSessionId = $state(Helpers.uuid())
67
+ const createStableSessionId = () =>
68
+ isAgentPreviewChat ? `chat-preview:${Helpers.uuid()}` : Helpers.uuid()
69
+
70
+ let stableSessionId = $state(createStableSessionId())
67
71
  let chatAreaElement = $state<HTMLDivElement>()
68
72
  let textareaElement = $state<HTMLTextAreaElement>()
69
73
  let expandedTools = $state<Record<string, boolean>>({})
@@ -257,7 +261,7 @@
257
261
  $effect(() => {
258
262
  if (chat?._id !== lastChatId) {
259
263
  lastChatId = chat?._id
260
- stableSessionId = Helpers.uuid()
264
+ stableSessionId = createStableSessionId()
261
265
  chatInstance.messages = chat?.messages || []
262
266
  expandedTools = {}
263
267
  }
@@ -534,7 +538,12 @@
534
538
  {#if isTextUIPart(part)}
535
539
  <MarkdownViewer value={part.text} />
536
540
  {:else if isToolUIPart(part)}
537
- {@const toolId = `${message.id}-${getToolName(part)}-${partIndex}`}
541
+ {@const rawToolName = getToolName(part)}
542
+ {@const displayToolName = formatToolName(
543
+ rawToolName,
544
+ message.metadata?.toolDisplayNames?.[rawToolName]
545
+ )}
546
+ {@const toolId = `${message.id}-${rawToolName}-${partIndex}`}
538
547
  {@const isRunning =
539
548
  part.state === "input-streaming" ||
540
549
  part.state === "input-available"}
@@ -553,7 +562,7 @@
553
562
  >
554
563
  <span class="tool-chevron-icon tool-chevron-icon-default">
555
564
  <Icon
556
- name="globe-simple"
565
+ name="wrench"
557
566
  size="M"
558
567
  weight="regular"
559
568
  color="var(--spectrum-global-color-gray-600)"
@@ -570,7 +579,14 @@
570
579
  </span>
571
580
  <span class="tool-call-label">Tool call</span>
572
581
  <div class="tool-name-wrapper">
573
- <span class="tool-name">{getToolName(part)}</span>
582
+ <span class="tool-name-primary"
583
+ >{displayToolName.primary}</span
584
+ >
585
+ {#if displayToolName.secondary}
586
+ <span class="tool-name-secondary">
587
+ {displayToolName.secondary}
588
+ </span>
589
+ {/if}
574
590
  </div>
575
591
  {#if isRunning || isError || isSuccess}
576
592
  <span class="tool-status">
@@ -979,18 +995,25 @@
979
995
 
980
996
  .tool-name-wrapper {
981
997
  display: flex;
982
- align-items: center;
983
- gap: var(--spacing-s);
984
- padding: 3px 6px;
998
+ flex-direction: column;
999
+ align-items: flex-start;
1000
+ gap: 2px;
1001
+ padding: 6px 8px;
985
1002
  background-color: var(--spectrum-global-color-gray-200);
986
- border-radius: 4px;
1003
+ border-radius: 6px;
987
1004
  }
988
1005
 
989
- .tool-name {
990
- font-family: var(--font-mono), monospace;
1006
+ .tool-name-primary {
991
1007
  font-size: 13px;
992
1008
  color: var(--spectrum-global-color-gray-800);
993
- font-weight: 400;
1009
+ font-weight: 600;
1010
+ line-height: 1.2;
1011
+ }
1012
+
1013
+ .tool-name-secondary {
1014
+ font-size: 11px;
1015
+ color: var(--spectrum-global-color-gray-600);
1016
+ line-height: 1.2;
994
1017
  }
995
1018
 
996
1019
  .tool-status {
package/src/constants.ts CHANGED
@@ -88,6 +88,7 @@ export const PlanType = {
88
88
  BUSINESS: "business",
89
89
  PREMIUM: "premium",
90
90
  PREMIUM_PLUS: "premium_plus",
91
+ PREMIUM_PLUS_TRIAL: "premium_plus_trial",
91
92
  ENTERPRISE: "enterprise",
92
93
  ENTERPRISE_BASIC_TRIAL: "enterprise_basic_trial",
93
94
  }
@@ -0,0 +1,95 @@
1
+ import { describe, expect, it } from "vitest"
2
+ import { formatToolName } from "./aiTools"
3
+
4
+ describe("formatToolName", () => {
5
+ describe("without readableName", () => {
6
+ it("humanizes underscored names", () => {
7
+ expect(formatToolName("fetch_users")).toEqual({
8
+ primary: "Fetch Users",
9
+ full: "Fetch Users",
10
+ })
11
+ })
12
+
13
+ it("humanizes hyphenated names", () => {
14
+ expect(formatToolName("create-record")).toEqual({
15
+ primary: "Create Record",
16
+ full: "Create Record",
17
+ })
18
+ })
19
+
20
+ it("humanizes mixed separators", () => {
21
+ expect(formatToolName("get_all-records")).toEqual({
22
+ primary: "Get All Records",
23
+ full: "Get All Records",
24
+ })
25
+ })
26
+
27
+ it("collapses multiple separators", () => {
28
+ expect(formatToolName("fetch___users")).toEqual({
29
+ primary: "Fetch Users",
30
+ full: "Fetch Users",
31
+ })
32
+ })
33
+
34
+ it("trims whitespace", () => {
35
+ expect(formatToolName(" fetch_users ")).toEqual({
36
+ primary: "Fetch Users",
37
+ full: "Fetch Users",
38
+ })
39
+ })
40
+
41
+ it("handles single word", () => {
42
+ expect(formatToolName("users")).toEqual({
43
+ primary: "Users",
44
+ full: "Users",
45
+ })
46
+ })
47
+ })
48
+
49
+ describe("with readableName", () => {
50
+ it("splits on last dot into primary and secondary", () => {
51
+ expect(formatToolName("raw", "datasource.fetch_rows")).toEqual({
52
+ primary: "datasource",
53
+ secondary: "Fetch Rows",
54
+ full: "datasource - Fetch Rows",
55
+ })
56
+ })
57
+
58
+ it("returns primary only when no dot present", () => {
59
+ expect(formatToolName("raw", "fetch_rows")).toEqual({
60
+ primary: "Fetch Rows",
61
+ full: "Fetch Rows",
62
+ })
63
+ })
64
+
65
+ it("handles dot at start (index 0) as single segment", () => {
66
+ expect(formatToolName("raw", ".fetch_rows")).toEqual({
67
+ primary: ".Fetch Rows",
68
+ full: ".Fetch Rows",
69
+ })
70
+ })
71
+
72
+ it("handles dot at end as single segment", () => {
73
+ expect(formatToolName("raw", "datasource.")).toEqual({
74
+ primary: "Datasource.",
75
+ full: "Datasource.",
76
+ })
77
+ })
78
+
79
+ it("splits on the last dot when multiple dots exist", () => {
80
+ expect(formatToolName("raw", "group.datasource.fetch_rows")).toEqual({
81
+ primary: "group.datasource",
82
+ secondary: "Fetch Rows",
83
+ full: "group.datasource - Fetch Rows",
84
+ })
85
+ })
86
+
87
+ it("humanizes the secondary segment only", () => {
88
+ expect(formatToolName("raw", "My Source.get_all_users")).toEqual({
89
+ primary: "My Source",
90
+ secondary: "Get All Users",
91
+ full: "My Source - Get All Users",
92
+ })
93
+ })
94
+ })
95
+ })
@@ -0,0 +1,43 @@
1
+ export interface FormattedToolName {
2
+ full: string
3
+ primary: string
4
+ secondary?: string
5
+ }
6
+
7
+ const humanizeSegment = (value: string): string =>
8
+ value
9
+ .replace(/[_-]+/g, " ")
10
+ .replace(/\s+/g, " ")
11
+ .trim()
12
+ .replace(/\b\w/g, letter => letter.toUpperCase())
13
+
14
+ const splitReadableName = (value: string) => {
15
+ const lastDotIndex = value.lastIndexOf(".")
16
+ if (lastDotIndex <= 0 || lastDotIndex >= value.length - 1) {
17
+ return {
18
+ primary: humanizeSegment(value),
19
+ }
20
+ }
21
+
22
+ const primary = value.slice(0, lastDotIndex).trim()
23
+ const secondary = humanizeSegment(value.slice(lastDotIndex + 1))
24
+ return {
25
+ primary,
26
+ ...(secondary ? { secondary } : {}),
27
+ }
28
+ }
29
+
30
+ export const formatToolName = (
31
+ rawName: string,
32
+ readableName?: string
33
+ ): FormattedToolName => {
34
+ const { primary, secondary } = readableName
35
+ ? splitReadableName(readableName)
36
+ : { primary: humanizeSegment(rawName) }
37
+
38
+ return {
39
+ primary,
40
+ ...(secondary ? { secondary } : {}),
41
+ full: secondary ? `${primary} - ${secondary}` : primary,
42
+ }
43
+ }
@@ -16,5 +16,6 @@ export * from "./table"
16
16
  export * from "./components"
17
17
  export * from "./validation"
18
18
  export * from "./formatting"
19
+ export * from "./aiTools"
19
20
  export * from "./login"
20
21
  export * from "./translationGroups"