@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 +2 -2
- package/src/api/agentLogs.ts +62 -0
- package/src/api/agents.ts +0 -33
- package/src/api/index.ts +4 -0
- package/src/api/knowledgeBases.ts +79 -0
- package/src/api/types.ts +4 -0
- package/src/components/Chatbox/index.svelte +35 -12
- package/src/constants.ts +1 -0
- package/src/utils/aiTools.test.ts +95 -0
- package/src/utils/aiTools.ts +43 -0
- package/src/utils/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/frontend-core",
|
|
3
|
-
"version": "3.32.
|
|
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": "
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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="
|
|
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"
|
|
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
|
-
|
|
983
|
-
|
|
984
|
-
|
|
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:
|
|
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:
|
|
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
|
@@ -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
|
+
}
|