@budibase/frontend-core 3.39.22 → 3.39.24
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/agentRequests.ts +28 -0
- package/src/api/chatLinks.ts +44 -0
- package/src/api/escalations.ts +41 -0
- package/src/api/index.ts +8 -0
- package/src/api/projects.ts +78 -0
- package/src/api/types.ts +8 -0
- package/src/components/Chatbox/EscalationCard.svelte +113 -0
- package/src/components/Chatbox/index.svelte +113 -0
- package/src/components/CoreFilterBuilder.svelte +12 -1
- package/src/components/FilterField.svelte +3 -0
- package/src/components/grid/cells/DateCell.svelte +12 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@budibase/frontend-core",
|
|
3
|
-
"version": "3.39.
|
|
3
|
+
"version": "3.39.24",
|
|
4
4
|
"description": "Budibase frontend core libraries used in builder and client",
|
|
5
5
|
"author": "Budibase",
|
|
6
6
|
"license": "MPL-2.0",
|
|
@@ -23,5 +23,5 @@
|
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"vitest": "^4.1.0"
|
|
25
25
|
},
|
|
26
|
-
"gitHead": "
|
|
26
|
+
"gitHead": "2427bcf96d0624cdbd4aaf028177ba7b6f6519c2"
|
|
27
27
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { FetchAgentRequestsResponse } from "@budibase/types"
|
|
2
|
+
import type { BaseAPIClient } from "./types"
|
|
3
|
+
|
|
4
|
+
export interface AgentRequestEndpoints {
|
|
5
|
+
fetchAgentRequests: (opts?: {
|
|
6
|
+
limit?: number
|
|
7
|
+
page?: number
|
|
8
|
+
}) => Promise<FetchAgentRequestsResponse>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const buildAgentRequestEndpoints = (
|
|
12
|
+
API: BaseAPIClient
|
|
13
|
+
): AgentRequestEndpoints => ({
|
|
14
|
+
fetchAgentRequests: async (opts = {}) => {
|
|
15
|
+
const params = new URLSearchParams()
|
|
16
|
+
if (opts.limit !== undefined) {
|
|
17
|
+
params.set("limit", String(opts.limit))
|
|
18
|
+
}
|
|
19
|
+
if (opts.page !== undefined) {
|
|
20
|
+
params.set("page", String(opts.page))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const query = params.toString()
|
|
24
|
+
return await API.get({
|
|
25
|
+
url: `/api/agent/requests${query ? `?${query}` : ""}`,
|
|
26
|
+
})
|
|
27
|
+
},
|
|
28
|
+
})
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ChatIdentityLink,
|
|
3
|
+
type ChatIdentityLinkProvider,
|
|
4
|
+
} from "@budibase/types"
|
|
5
|
+
import { BaseAPIClient } from "./types"
|
|
6
|
+
|
|
7
|
+
export interface SlackChannel {
|
|
8
|
+
id: string
|
|
9
|
+
name: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface MSTeamsChannel {
|
|
13
|
+
id: string
|
|
14
|
+
name: string
|
|
15
|
+
teamId: string
|
|
16
|
+
teamName: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ChatLinksEndpoints {
|
|
20
|
+
fetchChatIdentityLinks: (
|
|
21
|
+
provider?: ChatIdentityLinkProvider
|
|
22
|
+
) => Promise<ChatIdentityLink[]>
|
|
23
|
+
fetchSlackChannels: (agentId: string) => Promise<SlackChannel[]>
|
|
24
|
+
fetchMSTeamsChannels: (agentId: string) => Promise<MSTeamsChannel[]>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const buildChatLinksEndpoints = (
|
|
28
|
+
API: BaseAPIClient
|
|
29
|
+
): ChatLinksEndpoints => ({
|
|
30
|
+
fetchChatIdentityLinks: async provider => {
|
|
31
|
+
const query = provider
|
|
32
|
+
? `?${new URLSearchParams({ provider }).toString()}`
|
|
33
|
+
: ""
|
|
34
|
+
return await API.get({ url: `/api/chat-links${query}` })
|
|
35
|
+
},
|
|
36
|
+
fetchSlackChannels: async agentId => {
|
|
37
|
+
const query = new URLSearchParams({ agentId }).toString()
|
|
38
|
+
return await API.get({ url: `/api/slack-channels?${query}` })
|
|
39
|
+
},
|
|
40
|
+
fetchMSTeamsChannels: async agentId => {
|
|
41
|
+
const query = new URLSearchParams({ agentId }).toString()
|
|
42
|
+
return await API.get({ url: `/api/teams-channels?${query}` })
|
|
43
|
+
},
|
|
44
|
+
})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
EscalationContextDoc,
|
|
3
|
+
EscalationRespondResult,
|
|
4
|
+
EscalationResponse,
|
|
5
|
+
EscalationResult,
|
|
6
|
+
} from "@budibase/types"
|
|
7
|
+
import type { BaseAPIClient } from "./types"
|
|
8
|
+
|
|
9
|
+
export interface EscalationEndpoints {
|
|
10
|
+
fetchEscalationContext: (
|
|
11
|
+
escalationId: string
|
|
12
|
+
) => Promise<EscalationContextDoc>
|
|
13
|
+
fetchEscalationResult: (escalationId: string) => Promise<EscalationResult>
|
|
14
|
+
resolveEscalation: (
|
|
15
|
+
escalationId: string,
|
|
16
|
+
response: EscalationResponse
|
|
17
|
+
) => Promise<EscalationRespondResult>
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const buildEscalationEndpoints = (
|
|
21
|
+
API: BaseAPIClient
|
|
22
|
+
): EscalationEndpoints => ({
|
|
23
|
+
fetchEscalationContext: async escalationId => {
|
|
24
|
+
return await API.get({
|
|
25
|
+
url: `/api/escalations/context/${escalationId}`,
|
|
26
|
+
})
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
fetchEscalationResult: async escalationId => {
|
|
30
|
+
return await API.get({
|
|
31
|
+
url: `/api/escalations/${escalationId}/result`,
|
|
32
|
+
})
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
resolveEscalation: async (escalationId, response) => {
|
|
36
|
+
return await API.post({
|
|
37
|
+
url: `/api/escalations/${escalationId}/resolve`,
|
|
38
|
+
body: { response },
|
|
39
|
+
})
|
|
40
|
+
},
|
|
41
|
+
})
|
package/src/api/index.ts
CHANGED
|
@@ -23,6 +23,7 @@ import { buildFlagEndpoints } from "./flags"
|
|
|
23
23
|
import { buildLayoutEndpoints } from "./layouts"
|
|
24
24
|
import { buildOtherEndpoints } from "./other"
|
|
25
25
|
import { buildPermissionsEndpoints } from "./permissions"
|
|
26
|
+
import { buildProjectEndpoints } from "./projects"
|
|
26
27
|
import { buildQueryEndpoints } from "./queries"
|
|
27
28
|
import { buildRelationshipEndpoints } from "./relationships"
|
|
28
29
|
import { buildRoleEndpoints } from "./roles"
|
|
@@ -49,7 +50,10 @@ import { buildOAuth2Endpoints } from "./oauth2"
|
|
|
49
50
|
import { buildAgentEndpoints } from "./agents"
|
|
50
51
|
import { buildAgentTestEndpoints } from "./agentTests"
|
|
51
52
|
import { buildAgentLogEndpoints } from "./agentLogs"
|
|
53
|
+
import { buildAgentRequestEndpoints } from "./agentRequests"
|
|
52
54
|
import { buildChatAppEndpoints } from "./chatApps"
|
|
55
|
+
import { buildEscalationEndpoints } from "./escalations"
|
|
56
|
+
import { buildChatLinksEndpoints } from "./chatLinks"
|
|
53
57
|
import { buildFeatureFlagEndpoints } from "./features"
|
|
54
58
|
import { buildNavigationEndpoints } from "./navigation"
|
|
55
59
|
import { buildWorkspaceAppEndpoints } from "./workspaceApps"
|
|
@@ -320,13 +324,17 @@ export const createAPIClient = (config: APIClientConfig = {}): APIClient => {
|
|
|
320
324
|
...buildAgentEndpoints(API),
|
|
321
325
|
...buildAgentTestEndpoints(API),
|
|
322
326
|
...buildAgentLogEndpoints(API),
|
|
327
|
+
...buildAgentRequestEndpoints(API),
|
|
323
328
|
...buildChatAppEndpoints(API),
|
|
329
|
+
...buildEscalationEndpoints(API),
|
|
330
|
+
...buildChatLinksEndpoints(API),
|
|
324
331
|
...buildFeatureFlagEndpoints(API),
|
|
325
332
|
deployment: buildDeploymentEndpoints(API),
|
|
326
333
|
viewV2: buildViewV2Endpoints(API),
|
|
327
334
|
rowActions: buildRowActionEndpoints(API),
|
|
328
335
|
oauth2: buildOAuth2Endpoints(API),
|
|
329
336
|
navigation: buildNavigationEndpoints(API),
|
|
337
|
+
projects: buildProjectEndpoints(API),
|
|
330
338
|
workspaceApp: buildWorkspaceAppEndpoints(API),
|
|
331
339
|
workspace: buildWorkspaceFavouriteEndpoints(API),
|
|
332
340
|
workspaceHome: buildWorkspaceHomeEndpoints(API),
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CreateProjectRequest,
|
|
3
|
+
CreateProjectResponse,
|
|
4
|
+
ExportProjectRequest,
|
|
5
|
+
FetchProjectsResponse,
|
|
6
|
+
ImportProjectRequest,
|
|
7
|
+
ImportProjectResponse,
|
|
8
|
+
UpdateProjectRequest,
|
|
9
|
+
UpdateProjectResponse,
|
|
10
|
+
} from "@budibase/types"
|
|
11
|
+
import { BaseAPIClient } from "./types"
|
|
12
|
+
|
|
13
|
+
export interface ProjectEndpoints {
|
|
14
|
+
fetch: () => Promise<FetchProjectsResponse>
|
|
15
|
+
create: (project: CreateProjectRequest) => Promise<CreateProjectResponse>
|
|
16
|
+
exportBundle: (id: string, body?: ExportProjectRequest) => Promise<Response>
|
|
17
|
+
importBundle: (
|
|
18
|
+
file: File,
|
|
19
|
+
body?: ImportProjectRequest
|
|
20
|
+
) => Promise<ImportProjectResponse>
|
|
21
|
+
update: (project: UpdateProjectRequest) => Promise<UpdateProjectResponse>
|
|
22
|
+
delete: (id: string, rev: string) => Promise<void>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const buildProjectEndpoints = (
|
|
26
|
+
API: BaseAPIClient
|
|
27
|
+
): ProjectEndpoints => ({
|
|
28
|
+
fetch: async () => {
|
|
29
|
+
return await API.get({
|
|
30
|
+
url: "/api/projects",
|
|
31
|
+
})
|
|
32
|
+
},
|
|
33
|
+
create: async project => {
|
|
34
|
+
return await API.post({
|
|
35
|
+
url: "/api/projects",
|
|
36
|
+
body: project,
|
|
37
|
+
})
|
|
38
|
+
},
|
|
39
|
+
exportBundle: async (id, body) => {
|
|
40
|
+
return await API.post<ExportProjectRequest | undefined, Response>({
|
|
41
|
+
url: `/api/projects/${id}/export`,
|
|
42
|
+
body,
|
|
43
|
+
parseResponse: response => response,
|
|
44
|
+
})
|
|
45
|
+
},
|
|
46
|
+
importBundle: async (file, body) => {
|
|
47
|
+
const formData = new FormData()
|
|
48
|
+
formData.append("file", file)
|
|
49
|
+
for (const [key, value] of Object.entries(body || {})) {
|
|
50
|
+
if (value !== undefined) {
|
|
51
|
+
formData.append(key, value)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return await API.post<FormData, ImportProjectResponse>({
|
|
55
|
+
url: "/api/projects/import",
|
|
56
|
+
body: formData,
|
|
57
|
+
json: false,
|
|
58
|
+
})
|
|
59
|
+
},
|
|
60
|
+
update: async project => {
|
|
61
|
+
const { _id, _rev, name, description, color } = project
|
|
62
|
+
return await API.put({
|
|
63
|
+
url: `/api/projects/${_id}`,
|
|
64
|
+
body: {
|
|
65
|
+
_id,
|
|
66
|
+
_rev,
|
|
67
|
+
name,
|
|
68
|
+
description,
|
|
69
|
+
color,
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
},
|
|
73
|
+
delete: async (id, rev) => {
|
|
74
|
+
return await API.delete({
|
|
75
|
+
url: `/api/projects/${id}/${rev}`,
|
|
76
|
+
})
|
|
77
|
+
},
|
|
78
|
+
})
|
package/src/api/types.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { MigrationEndpoints } from "./migrations"
|
|
|
20
20
|
import { OAuth2Endpoints } from "./oauth2"
|
|
21
21
|
import { OtherEndpoints } from "./other"
|
|
22
22
|
import { PermissionEndpoints } from "./permissions"
|
|
23
|
+
import { ProjectEndpoints } from "./projects"
|
|
23
24
|
import { PluginEndpoins } from "./plugins"
|
|
24
25
|
import { QueryEndpoints } from "./queries"
|
|
25
26
|
import { RelationshipEndpoints } from "./relationships"
|
|
@@ -37,7 +38,10 @@ import { ViewV2Endpoints } from "./viewsV2"
|
|
|
37
38
|
import { AgentEndpoints } from "./agents"
|
|
38
39
|
import { AgentTestEndpoints } from "./agentTests"
|
|
39
40
|
import { AgentLogEndpoints } from "./agentLogs"
|
|
41
|
+
import { AgentRequestEndpoints } from "./agentRequests"
|
|
40
42
|
import { ChatAppEndpoints } from "./chatApps"
|
|
43
|
+
import { ChatLinksEndpoints } from "./chatLinks"
|
|
44
|
+
import { EscalationEndpoints } from "./escalations"
|
|
41
45
|
import { NavigationEndpoints } from "./navigation"
|
|
42
46
|
import { WorkspaceAppEndpoints } from "./workspaceApps"
|
|
43
47
|
import { ResourceEndpoints } from "./resource"
|
|
@@ -122,7 +126,10 @@ export type APIClient = BaseAPIClient &
|
|
|
122
126
|
AgentEndpoints &
|
|
123
127
|
AgentTestEndpoints &
|
|
124
128
|
AgentLogEndpoints &
|
|
129
|
+
AgentRequestEndpoints &
|
|
125
130
|
ChatAppEndpoints &
|
|
131
|
+
ChatLinksEndpoints &
|
|
132
|
+
EscalationEndpoints &
|
|
126
133
|
AnalyticsEndpoints &
|
|
127
134
|
AppEndpoints &
|
|
128
135
|
AttachmentEndpoints &
|
|
@@ -160,6 +167,7 @@ export type APIClient = BaseAPIClient &
|
|
|
160
167
|
viewV2: ViewV2Endpoints
|
|
161
168
|
oauth2: OAuth2Endpoints
|
|
162
169
|
navigation: NavigationEndpoints
|
|
170
|
+
projects: ProjectEndpoints
|
|
163
171
|
workspaceApp: WorkspaceAppEndpoints
|
|
164
172
|
workspace: WorkspaceFavouriteEndpoints
|
|
165
173
|
workspaceHome: WorkspaceHomeEndpoints
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Body, Button, Icon } from "@budibase/bbui"
|
|
3
|
+
import type { EscalationContextDoc } from "@budibase/types"
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
title?: string
|
|
7
|
+
summary?: string
|
|
8
|
+
// Live resolution from the poll (not the frozen tool output).
|
|
9
|
+
resolution: EscalationContextDoc["resolution"]
|
|
10
|
+
// Message relayed from resolve ("Response recorded." etc.) - set as soon as
|
|
11
|
+
// the action is taken here, before the poll flips resolution.
|
|
12
|
+
statusMessage?: string
|
|
13
|
+
// Dev-only: whether to show the inline Approve/Reject buttons.
|
|
14
|
+
showApproval?: boolean
|
|
15
|
+
resolving?: boolean
|
|
16
|
+
onApprove?: () => void
|
|
17
|
+
onReject?: () => void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let {
|
|
21
|
+
title,
|
|
22
|
+
summary,
|
|
23
|
+
resolution,
|
|
24
|
+
statusMessage,
|
|
25
|
+
showApproval = false,
|
|
26
|
+
resolving = false,
|
|
27
|
+
onApprove,
|
|
28
|
+
onReject,
|
|
29
|
+
}: Props = $props()
|
|
30
|
+
|
|
31
|
+
// Resolved either by this card's action (statusMessage) or elsewhere (poll).
|
|
32
|
+
let isResolved = $derived(!!statusMessage || resolution !== "pending")
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<div class="escalation-card" aria-live="polite">
|
|
36
|
+
<div class="escalation-card-header">
|
|
37
|
+
<Icon name={isResolved ? "check-circle" : "clock"} size="M" />
|
|
38
|
+
<span class="escalation-card-title">{title || "Approval required"}</span>
|
|
39
|
+
{#if showApproval && !isResolved}
|
|
40
|
+
<span class="escalation-card-badge">Test mode</span>
|
|
41
|
+
{/if}
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
{#if summary}
|
|
45
|
+
<Body size="S" color="var(--spectrum-global-color-gray-700)">
|
|
46
|
+
{summary}
|
|
47
|
+
</Body>
|
|
48
|
+
{/if}
|
|
49
|
+
|
|
50
|
+
{#if isResolved}
|
|
51
|
+
<Body size="XS" color="var(--spectrum-global-color-gray-600)">
|
|
52
|
+
{statusMessage || "Response recorded."}
|
|
53
|
+
</Body>
|
|
54
|
+
{:else if showApproval}
|
|
55
|
+
<Body size="XS" color="var(--spectrum-global-color-gray-600)">
|
|
56
|
+
Approve or reject here to simulate a reviewer response.
|
|
57
|
+
</Body>
|
|
58
|
+
<div class="escalation-card-actions">
|
|
59
|
+
<Button cta disabled={resolving} on:click={() => onApprove?.()}>
|
|
60
|
+
Approve
|
|
61
|
+
</Button>
|
|
62
|
+
<Button secondary disabled={resolving} on:click={() => onReject?.()}>
|
|
63
|
+
Reject
|
|
64
|
+
</Button>
|
|
65
|
+
</div>
|
|
66
|
+
{:else}
|
|
67
|
+
<Body size="XS" color="var(--spectrum-global-color-gray-600)">
|
|
68
|
+
Awaiting a human response.
|
|
69
|
+
</Body>
|
|
70
|
+
{/if}
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<style>
|
|
74
|
+
.escalation-card {
|
|
75
|
+
align-self: flex-start;
|
|
76
|
+
width: 100%;
|
|
77
|
+
max-width: 100%;
|
|
78
|
+
box-sizing: border-box;
|
|
79
|
+
display: flex;
|
|
80
|
+
flex-direction: column;
|
|
81
|
+
gap: var(--spacing-s);
|
|
82
|
+
padding: var(--spacing-m);
|
|
83
|
+
border: 1px solid var(--spectrum-global-color-gray-300);
|
|
84
|
+
border-radius: 12px;
|
|
85
|
+
background: var(--spectrum-global-color-gray-75);
|
|
86
|
+
}
|
|
87
|
+
.escalation-card-header {
|
|
88
|
+
display: flex;
|
|
89
|
+
align-items: center;
|
|
90
|
+
gap: var(--spacing-s);
|
|
91
|
+
}
|
|
92
|
+
.escalation-card-title {
|
|
93
|
+
font-weight: 600;
|
|
94
|
+
font-size: 14px;
|
|
95
|
+
color: var(--spectrum-global-color-gray-900);
|
|
96
|
+
}
|
|
97
|
+
.escalation-card-badge {
|
|
98
|
+
margin-left: auto;
|
|
99
|
+
border-radius: 999px;
|
|
100
|
+
border: 1px solid var(--spectrum-global-color-blue-400);
|
|
101
|
+
color: var(--spectrum-global-color-blue-700);
|
|
102
|
+
background: var(--spectrum-global-color-blue-100);
|
|
103
|
+
font-size: 11px;
|
|
104
|
+
font-weight: 600;
|
|
105
|
+
line-height: 1;
|
|
106
|
+
padding: 4px 8px;
|
|
107
|
+
}
|
|
108
|
+
.escalation-card-actions {
|
|
109
|
+
display: flex;
|
|
110
|
+
gap: var(--spacing-s);
|
|
111
|
+
margin-top: var(--spacing-xs);
|
|
112
|
+
}
|
|
113
|
+
</style>
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
ChatConversation,
|
|
12
12
|
DraftChatConversation,
|
|
13
13
|
AgentMessageMetadata,
|
|
14
|
+
EscalationContextDoc,
|
|
15
|
+
EscalationRespondResult,
|
|
14
16
|
} from "@budibase/types"
|
|
15
17
|
import { Header } from "@budibase/shared-core"
|
|
16
18
|
import { tick, untrack } from "svelte"
|
|
@@ -19,6 +21,7 @@
|
|
|
19
21
|
import { formatToolName } from "../../utils/aiTools"
|
|
20
22
|
import ReasoningStatus from "./ReasoningStatus.svelte"
|
|
21
23
|
import ContextUsage from "./ContextUsage.svelte"
|
|
24
|
+
import EscalationCard from "./EscalationCard.svelte"
|
|
22
25
|
import {
|
|
23
26
|
DefaultChatTransport,
|
|
24
27
|
isTextUIPart,
|
|
@@ -38,6 +41,20 @@
|
|
|
38
41
|
onchatsaved?: (_event: {
|
|
39
42
|
detail: { chatId?: string; chat: ChatConversationLike }
|
|
40
43
|
}) => void
|
|
44
|
+
// Fired when an escalation parks; the consumer polls the outcome and
|
|
45
|
+
// injects it via appendAssistantMessage.
|
|
46
|
+
onEscalationPending?: (_detail: { escalationId: string }) => void
|
|
47
|
+
// Live resolution per escalationId (from the poll) - drives the card state.
|
|
48
|
+
escalationState?: Record<
|
|
49
|
+
string,
|
|
50
|
+
{ resolution: EscalationContextDoc["resolution"] }
|
|
51
|
+
>
|
|
52
|
+
// Dev-only: show the inline Approve/Reject buttons on the escalation card.
|
|
53
|
+
showInlineApproval?: boolean
|
|
54
|
+
onResolve?: (
|
|
55
|
+
_escalationId: string,
|
|
56
|
+
_accepted: boolean
|
|
57
|
+
) => Promise<EscalationRespondResult | undefined>
|
|
41
58
|
isAgentPreviewChat?: boolean
|
|
42
59
|
readOnly?: boolean
|
|
43
60
|
readOnlyReason?: "disabled" | "deleted" | "offline"
|
|
@@ -50,11 +67,47 @@
|
|
|
50
67
|
conversationStarters = [],
|
|
51
68
|
initialPrompt = "",
|
|
52
69
|
onchatsaved,
|
|
70
|
+
onEscalationPending,
|
|
71
|
+
escalationState,
|
|
72
|
+
showInlineApproval = false,
|
|
73
|
+
onResolve,
|
|
53
74
|
isAgentPreviewChat = false,
|
|
54
75
|
readOnly = false,
|
|
55
76
|
readOnlyReason,
|
|
56
77
|
}: Props = $props()
|
|
57
78
|
|
|
79
|
+
// Per-escalation in-flight flag + the message relayed from resolve, so the
|
|
80
|
+
// card shows the real outcome on click rather than a static label.
|
|
81
|
+
let resolvingEscalations = $state<Record<string, boolean>>({})
|
|
82
|
+
let resolveMessages = $state<Record<string, string>>({})
|
|
83
|
+
const handleResolve = async (escalationId: string, accepted: boolean) => {
|
|
84
|
+
resolvingEscalations = { ...resolvingEscalations, [escalationId]: true }
|
|
85
|
+
try {
|
|
86
|
+
const result = await onResolve?.(escalationId, accepted)
|
|
87
|
+
if (result?.message) {
|
|
88
|
+
resolveMessages = { ...resolveMessages, [escalationId]: result.message }
|
|
89
|
+
}
|
|
90
|
+
} finally {
|
|
91
|
+
resolvingEscalations = { ...resolvingEscalations, [escalationId]: false }
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// The escalate part's input/output are loosely typed by the AI SDK, so the
|
|
96
|
+
// casts live here rather than cluttering the template.
|
|
97
|
+
const escalationCardProps = (part: { input?: unknown; output?: unknown }) => {
|
|
98
|
+
const output = part.output as { escalationId?: string } | undefined
|
|
99
|
+
const input = part.input as { title?: string; summary?: string } | undefined
|
|
100
|
+
const escalationId = output?.escalationId
|
|
101
|
+
return {
|
|
102
|
+
escalationId,
|
|
103
|
+
title: input?.title,
|
|
104
|
+
summary: input?.summary,
|
|
105
|
+
resolution:
|
|
106
|
+
(escalationId && escalationState?.[escalationId]?.resolution) ||
|
|
107
|
+
"pending",
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
58
111
|
let API = $state(
|
|
59
112
|
createAPIClient({
|
|
60
113
|
attachHeaders: headers => {
|
|
@@ -322,6 +375,48 @@
|
|
|
322
375
|
let messages = $derived(chatInstance.messages)
|
|
323
376
|
let lastMessage = $derived(messages[messages.length - 1])
|
|
324
377
|
|
|
378
|
+
// Notify the consumer of every unseen parked escalation. The escalate part's
|
|
379
|
+
// output stays frozen at pending_approval, so notifying only the first would
|
|
380
|
+
// shadow later escalations.
|
|
381
|
+
const notifiedEscalations = new Set<string>()
|
|
382
|
+
let pendingEscalationIds = $derived.by(() => {
|
|
383
|
+
const ids: string[] = []
|
|
384
|
+
for (const message of messages) {
|
|
385
|
+
for (const part of message.parts ?? []) {
|
|
386
|
+
if (
|
|
387
|
+
!isToolUIPart(part) ||
|
|
388
|
+
getToolName(part) !== "escalate" ||
|
|
389
|
+
part.state !== "output-available"
|
|
390
|
+
) {
|
|
391
|
+
continue
|
|
392
|
+
}
|
|
393
|
+
const output = part.output as
|
|
394
|
+
| { status?: string; escalationId?: string }
|
|
395
|
+
| undefined
|
|
396
|
+
if (output?.status === "pending_approval" && output.escalationId) {
|
|
397
|
+
ids.push(output.escalationId)
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return ids
|
|
402
|
+
})
|
|
403
|
+
$effect(() => {
|
|
404
|
+
for (const escalationId of pendingEscalationIds) {
|
|
405
|
+
if (!notifiedEscalations.has(escalationId)) {
|
|
406
|
+
notifiedEscalations.add(escalationId)
|
|
407
|
+
onEscalationPending?.({ escalationId })
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
// Injects the polled resume outcome as a plain assistant turn - the agent's
|
|
413
|
+
// own reply self-frames the approval.
|
|
414
|
+
export function appendAssistantMessage(
|
|
415
|
+
message: UIMessage<AgentMessageMetadata>
|
|
416
|
+
) {
|
|
417
|
+
chatInstance.messages = [...chatInstance.messages, message]
|
|
418
|
+
}
|
|
419
|
+
|
|
325
420
|
let lastAssistantUsage = $derived(
|
|
326
421
|
messages.findLast(m => m.role === "assistant" && m.metadata?.usage)
|
|
327
422
|
?.metadata?.usage
|
|
@@ -678,6 +773,24 @@
|
|
|
678
773
|
{#each message.parts ?? [] as part, partIndex}
|
|
679
774
|
{#if isTextUIPart(part)}
|
|
680
775
|
<MarkdownViewer value={part.text} />
|
|
776
|
+
{:else if isToolUIPart(part) && getToolName(part) === "escalate"}
|
|
777
|
+
{@const card = escalationCardProps(part)}
|
|
778
|
+
<EscalationCard
|
|
779
|
+
title={card.title}
|
|
780
|
+
summary={card.summary}
|
|
781
|
+
resolution={card.resolution}
|
|
782
|
+
statusMessage={card.escalationId
|
|
783
|
+
? resolveMessages[card.escalationId]
|
|
784
|
+
: undefined}
|
|
785
|
+
showApproval={showInlineApproval}
|
|
786
|
+
resolving={!!card.escalationId &&
|
|
787
|
+
!!resolvingEscalations[card.escalationId]}
|
|
788
|
+
onApprove={() =>
|
|
789
|
+
card.escalationId && handleResolve(card.escalationId, true)}
|
|
790
|
+
onReject={() =>
|
|
791
|
+
card.escalationId &&
|
|
792
|
+
handleResolve(card.escalationId, false)}
|
|
793
|
+
/>
|
|
681
794
|
{:else if isToolUIPart(part)}
|
|
682
795
|
{@const rawToolName = getToolName(part)}
|
|
683
796
|
{@const displayToolName = formatToolName(
|
|
@@ -16,7 +16,11 @@
|
|
|
16
16
|
import { QueryUtils, Constants } from "@budibase/frontend-core"
|
|
17
17
|
import { getContext, createEventDispatcher } from "svelte"
|
|
18
18
|
import FilterField from "./FilterField.svelte"
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
resolveTranslationGroup,
|
|
21
|
+
resolveWorkspaceTranslations,
|
|
22
|
+
utils,
|
|
23
|
+
} from "@budibase/shared-core"
|
|
20
24
|
|
|
21
25
|
const dispatch = createEventDispatcher()
|
|
22
26
|
const {
|
|
@@ -46,7 +50,13 @@
|
|
|
46
50
|
export let toReadable
|
|
47
51
|
export let toRuntime
|
|
48
52
|
export let evaluationContext = {}
|
|
53
|
+
const sdk = getContext("sdk") || {}
|
|
54
|
+
const { appStore } = sdk
|
|
49
55
|
|
|
56
|
+
$: translationOverrides = resolveWorkspaceTranslations(
|
|
57
|
+
appStore ? $appStore.application?.translationOverrides : undefined
|
|
58
|
+
)
|
|
59
|
+
$: calendarLabels = resolveTranslationGroup("calendar", translationOverrides)
|
|
50
60
|
$: editableFilters = migrateFilters(filters)
|
|
51
61
|
$: {
|
|
52
62
|
if (
|
|
@@ -423,6 +433,7 @@
|
|
|
423
433
|
...filter,
|
|
424
434
|
}}
|
|
425
435
|
{schemaFields}
|
|
436
|
+
{calendarLabels}
|
|
426
437
|
{bindings}
|
|
427
438
|
{panel}
|
|
428
439
|
{toReadable}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { FieldType, ArrayOperator } from "@budibase/types"
|
|
15
15
|
import * as Constants from "../constants"
|
|
16
16
|
import { isJSBinding, findHBSBlocks } from "@budibase/string-templates"
|
|
17
|
+
import { resolveTranslationGroup } from "@budibase/shared-core"
|
|
17
18
|
import { createEventDispatcher } from "svelte"
|
|
18
19
|
|
|
19
20
|
export let filter
|
|
@@ -28,6 +29,7 @@
|
|
|
28
29
|
export let evaluationContext = {}
|
|
29
30
|
export let bindingValueType = Constants.FilterValueType.BINDING
|
|
30
31
|
export let useConditionValueControls = false
|
|
32
|
+
export let calendarLabels = resolveTranslationGroup("calendar")
|
|
31
33
|
|
|
32
34
|
const dispatch = createEventDispatcher()
|
|
33
35
|
const { OperatorOptions, FilterValueType } = Constants
|
|
@@ -238,6 +240,7 @@
|
|
|
238
240
|
disabled={filter.noValue}
|
|
239
241
|
enableTime={!getSchema(filter)?.dateOnly}
|
|
240
242
|
timeOnly={getSchema(filter)?.timeOnly}
|
|
243
|
+
{calendarLabels}
|
|
241
244
|
value={readableValue}
|
|
242
245
|
on:change={onChange}
|
|
243
246
|
/>
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import { CoreDatePickerPopoverContents, Icon, Helpers } from "@budibase/bbui"
|
|
3
|
-
import { onMount } from "svelte"
|
|
3
|
+
import { getContext, onMount } from "svelte"
|
|
4
4
|
import dayjs from "dayjs"
|
|
5
5
|
import GridPopover from "../overlays/GridPopover.svelte"
|
|
6
|
+
import {
|
|
7
|
+
resolveTranslationGroup,
|
|
8
|
+
resolveWorkspaceTranslations,
|
|
9
|
+
} from "@budibase/shared-core"
|
|
6
10
|
|
|
7
11
|
export let value
|
|
8
12
|
export let schema
|
|
@@ -14,7 +18,13 @@
|
|
|
14
18
|
|
|
15
19
|
let isOpen
|
|
16
20
|
let anchor
|
|
21
|
+
const sdk = getContext("sdk") || {}
|
|
22
|
+
const { appStore } = sdk
|
|
17
23
|
|
|
24
|
+
$: translationOverrides = resolveWorkspaceTranslations(
|
|
25
|
+
appStore ? $appStore.application?.translationOverrides : undefined
|
|
26
|
+
)
|
|
27
|
+
$: calendarLabels = resolveTranslationGroup("calendar", translationOverrides)
|
|
18
28
|
$: timeOnly = schema?.timeOnly
|
|
19
29
|
$: enableTime = !schema?.dateOnly
|
|
20
30
|
$: ignoreTimezones = schema?.ignoreTimezones
|
|
@@ -120,6 +130,7 @@
|
|
|
120
130
|
{timeOnly}
|
|
121
131
|
{ignoreTimezones}
|
|
122
132
|
{startDayOfWeek}
|
|
133
|
+
{calendarLabels}
|
|
123
134
|
/>
|
|
124
135
|
</GridPopover>
|
|
125
136
|
{/if}
|