@schandlergarcia/sf-web-components 1.9.87 → 2.0.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.
- package/.a4drules/features/command-center-dashboard-rule.md +1 -1
- package/.a4drules/skills/command-center-builder/SKILL.md +33 -36
- package/.a4drules/skills/command-center-builder/getting-started.md +64 -104
- package/.a4drules/skills/command-center-builder/improved-build-process.md +28 -34
- package/.a4drules/skills/command-center-guide/SKILL.md +9 -9
- package/.a4drules/skills/command-center-project/SKILL.md +4 -5
- package/.a4drules/skills/component-library/SKILL.md +8 -10
- package/.a4drules/skills/component-library/card-components.md +3 -3
- package/.a4drules/skills/component-library/chat-data.md +4 -6
- package/.a4drules/troubleshooting/codegen-overwrites-types.md +21 -162
- package/.a4drules/troubleshooting/graphql-introspection-failure.md +13 -264
- package/.a4drules/validation-requirements.md +3 -5
- package/.a4drules/webapp-data.md +1 -1
- package/CHANGELOG.md +30 -0
- package/CLAUDE.md +19 -39
- package/dist/components/library/cards/ActivityCard.js +9 -9
- package/dist/components/library/cards/ActivityCard.js.map +1 -1
- package/dist/styles/base.css +112 -27
- package/dist/styles/global.css +15 -30
- package/package.json +2 -3
- package/scripts/postinstall.mjs +39 -178
- package/scripts/reset-command-center.sh +67 -406
- package/scripts/validate-dashboard.sh +4 -4
- package/src/components/library/cards/ActivityCard.jsx +2 -2
- package/src/styles/base.css +223 -0
- package/src/styles/global.css +223 -0
- package/src/templates/config/vite.config.ts.template +0 -18
- package/.a4drules/features/engine-dashboard-rule.md +0 -63
- package/.a4drules/features/phase2-data-pattern.md +0 -15
- package/assets/images/engine_logo.png +0 -0
- package/data/README.md +0 -202
- package/data/USAGE.md +0 -81
- package/data/agentApiConfig.ts +0 -36
- package/data/copy-to-webapp.sh +0 -61
- package/data/engine-command-center-prd.md +0 -575
- package/data/engine-live-data.js +0 -135
- package/data/engine-sample-data.js +0 -378
- package/data/schema.graphql +0 -292
- package/data/useEngineLiveData.ts +0 -49
- package/data/useEvaAgent.ts +0 -288
- package/scripts/generate-schema-from-sample.mjs +0 -370
package/data/schema.graphql
DELETED
|
@@ -1,292 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Generated schema for Engine Travel Command Center
|
|
3
|
-
Source: src/data/engine-sample-data.js field names
|
|
4
|
-
Note: This is a minimal schema for GraphQL tooling when introspection is broken
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
schema {
|
|
8
|
-
query: Query
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
type Query {
|
|
12
|
-
uiapi: UIAPI!
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
type UIAPI {
|
|
16
|
-
query: UIAPIQuery!
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
type UIAPIQuery {
|
|
20
|
-
Contact(first: Int, after: String, where: ContactFilter): ContactConnection
|
|
21
|
-
Trip__c(first: Int, after: String, where: Trip__cFilter): Trip__cConnection
|
|
22
|
-
Flight__c(first: Int, after: String, where: Flight__cFilter): Flight__cConnection
|
|
23
|
-
Booking__c(first: Int, after: String, where: Booking__cFilter): Booking__cConnection
|
|
24
|
-
Disruption__c(first: Int, after: String, where: Disruption__cFilter): Disruption__cConnection
|
|
25
|
-
Rebooking_Action__c(first: Int, after: String, where: Rebooking_Action__cFilter): Rebooking_Action__cConnection
|
|
26
|
-
Travel_Policy__c(first: Int, after: String, where: Travel_Policy__cFilter): Travel_Policy__cConnection
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
# Field value wrappers for FLS
|
|
30
|
-
type FieldValueString {
|
|
31
|
-
value: String
|
|
32
|
-
displayValue: String
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
type FieldValueFloat {
|
|
36
|
-
value: Float
|
|
37
|
-
displayValue: String
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
type FieldValueBoolean {
|
|
41
|
-
value: Boolean
|
|
42
|
-
displayValue: String
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
type FieldValueID {
|
|
46
|
-
value: ID
|
|
47
|
-
displayValue: String
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
# Contact (Travelers)
|
|
51
|
-
type Contact {
|
|
52
|
-
Id: ID!
|
|
53
|
-
FirstName: FieldValueString
|
|
54
|
-
LastName: FieldValueString
|
|
55
|
-
Department: FieldValueString
|
|
56
|
-
Home_Airport__c: FieldValueString
|
|
57
|
-
Travel_Policy_Tier__c: FieldValueString
|
|
58
|
-
Is_Active_Traveler__c: FieldValueBoolean
|
|
59
|
-
Role__c: FieldValueString
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
type ContactEdge {
|
|
63
|
-
node: Contact!
|
|
64
|
-
cursor: String!
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
type ContactConnection {
|
|
68
|
-
edges: [ContactEdge!]!
|
|
69
|
-
pageInfo: PageInfo!
|
|
70
|
-
totalCount: Int
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
input ContactFilter {
|
|
74
|
-
Id: IDFilter
|
|
75
|
-
FirstName: StringFilter
|
|
76
|
-
LastName: StringFilter
|
|
77
|
-
Department: StringFilter
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
# Trip__c
|
|
81
|
-
type Trip__c {
|
|
82
|
-
Id: ID!
|
|
83
|
-
Trip_Name__c: FieldValueString
|
|
84
|
-
Contact__c: FieldValueString
|
|
85
|
-
Origin_City__c: FieldValueString
|
|
86
|
-
Origin_Airport__c: FieldValueString
|
|
87
|
-
Destination_City__c: FieldValueString
|
|
88
|
-
Destination_Airport__c: FieldValueString
|
|
89
|
-
Start_Date__c: FieldValueString
|
|
90
|
-
End_Date__c: FieldValueString
|
|
91
|
-
Status__c: FieldValueString
|
|
92
|
-
Total_Cost__c: FieldValueFloat
|
|
93
|
-
In_Policy__c: FieldValueBoolean
|
|
94
|
-
Has_Disruption__c: FieldValueBoolean
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
type Trip__cEdge {
|
|
98
|
-
node: Trip__c!
|
|
99
|
-
cursor: String!
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
type Trip__cConnection {
|
|
103
|
-
edges: [Trip__cEdge!]!
|
|
104
|
-
pageInfo: PageInfo!
|
|
105
|
-
totalCount: Int
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
input Trip__cFilter {
|
|
109
|
-
Id: IDFilter
|
|
110
|
-
Trip_Name__c: StringFilter
|
|
111
|
-
Status__c: StringFilter
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
# Flight__c
|
|
115
|
-
type Flight__c {
|
|
116
|
-
Id: ID!
|
|
117
|
-
Flight_Number__c: FieldValueString
|
|
118
|
-
Airline__c: FieldValueString
|
|
119
|
-
Departure_Airport__c: FieldValueString
|
|
120
|
-
Departure_City__c: FieldValueString
|
|
121
|
-
Departure_Longitude__c: FieldValueFloat
|
|
122
|
-
Departure_Latitude__c: FieldValueFloat
|
|
123
|
-
Arrival_Airport__c: FieldValueString
|
|
124
|
-
Arrival_City__c: FieldValueString
|
|
125
|
-
Arrival_Longitude__c: FieldValueFloat
|
|
126
|
-
Arrival_Latitude__c: FieldValueFloat
|
|
127
|
-
Departure_DateTime__c: FieldValueString
|
|
128
|
-
Flight_Status__c: FieldValueString
|
|
129
|
-
Delay_Minutes__c: FieldValueFloat
|
|
130
|
-
Cabin_Class__c: FieldValueString
|
|
131
|
-
Contact__c: FieldValueString
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
type Flight__cEdge {
|
|
135
|
-
node: Flight__c!
|
|
136
|
-
cursor: String!
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
type Flight__cConnection {
|
|
140
|
-
edges: [Flight__cEdge!]!
|
|
141
|
-
pageInfo: PageInfo!
|
|
142
|
-
totalCount: Int
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
input Flight__cFilter {
|
|
146
|
-
Id: IDFilter
|
|
147
|
-
Flight_Number__c: StringFilter
|
|
148
|
-
Flight_Status__c: StringFilter
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
# Booking__c
|
|
152
|
-
type Booking__c {
|
|
153
|
-
Id: ID!
|
|
154
|
-
Contact__c: FieldValueString
|
|
155
|
-
Booking_Type__c: FieldValueString
|
|
156
|
-
Status__c: FieldValueString
|
|
157
|
-
Cost__c: FieldValueFloat
|
|
158
|
-
In_Policy__c: FieldValueBoolean
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
type Booking__cEdge {
|
|
162
|
-
node: Booking__c!
|
|
163
|
-
cursor: String!
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
type Booking__cConnection {
|
|
167
|
-
edges: [Booking__cEdge!]!
|
|
168
|
-
pageInfo: PageInfo!
|
|
169
|
-
totalCount: Int
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
input Booking__cFilter {
|
|
173
|
-
Id: IDFilter
|
|
174
|
-
Status__c: StringFilter
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
# Disruption__c
|
|
178
|
-
type Disruption__c {
|
|
179
|
-
Id: ID!
|
|
180
|
-
Flight_Number__c: FieldValueString
|
|
181
|
-
Disruption_Type__c: FieldValueString
|
|
182
|
-
Severity__c: FieldValueString
|
|
183
|
-
Status__c: FieldValueString
|
|
184
|
-
Impacted_Flight__c: FieldValueString
|
|
185
|
-
Trip__c: FieldValueString
|
|
186
|
-
City__c: FieldValueString
|
|
187
|
-
Affected_Traveler_Count__c: FieldValueFloat
|
|
188
|
-
Auto_Rebook_Eligible__c: FieldValueBoolean
|
|
189
|
-
Recommended_Action__c: FieldValueString
|
|
190
|
-
Description__c: FieldValueString
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
type Disruption__cEdge {
|
|
194
|
-
node: Disruption__c!
|
|
195
|
-
cursor: String!
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
type Disruption__cConnection {
|
|
199
|
-
edges: [Disruption__cEdge!]!
|
|
200
|
-
pageInfo: PageInfo!
|
|
201
|
-
totalCount: Int
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
input Disruption__cFilter {
|
|
205
|
-
Id: IDFilter
|
|
206
|
-
Severity__c: StringFilter
|
|
207
|
-
Status__c: StringFilter
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
# Rebooking_Action__c
|
|
211
|
-
type Rebooking_Action__c {
|
|
212
|
-
Id: ID!
|
|
213
|
-
Action_Type__c: FieldValueString
|
|
214
|
-
Status__c: FieldValueString
|
|
215
|
-
Handled_By__c: FieldValueString
|
|
216
|
-
Contact__c: FieldValueString
|
|
217
|
-
Original_Flight__c: FieldValueString
|
|
218
|
-
New_Flight__c: FieldValueString
|
|
219
|
-
Cost_Difference__c: FieldValueFloat
|
|
220
|
-
Notes__c: FieldValueString
|
|
221
|
-
Created_DateTime__c: FieldValueString
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
type Rebooking_Action__cEdge {
|
|
225
|
-
node: Rebooking_Action__c!
|
|
226
|
-
cursor: String!
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
type Rebooking_Action__cConnection {
|
|
230
|
-
edges: [Rebooking_Action__cEdge!]!
|
|
231
|
-
pageInfo: PageInfo!
|
|
232
|
-
totalCount: Int
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
input Rebooking_Action__cFilter {
|
|
236
|
-
Id: IDFilter
|
|
237
|
-
Status__c: StringFilter
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
# Travel_Policy__c
|
|
241
|
-
type Travel_Policy__c {
|
|
242
|
-
Id: ID!
|
|
243
|
-
Name: FieldValueString
|
|
244
|
-
Description__c: FieldValueString
|
|
245
|
-
Active__c: FieldValueBoolean
|
|
246
|
-
Policy_Tier__c: FieldValueString
|
|
247
|
-
Max_Flight_Cost__c: FieldValueFloat
|
|
248
|
-
Max_Hotel_Rate__c: FieldValueFloat
|
|
249
|
-
Advance_Booking_Days__c: FieldValueFloat
|
|
250
|
-
Preferred_Airlines__c: FieldValueString
|
|
251
|
-
Preferred_Hotel_Chains__c: FieldValueString
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
type Travel_Policy__cEdge {
|
|
255
|
-
node: Travel_Policy__c!
|
|
256
|
-
cursor: String!
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
type Travel_Policy__cConnection {
|
|
260
|
-
edges: [Travel_Policy__cEdge!]!
|
|
261
|
-
pageInfo: PageInfo!
|
|
262
|
-
totalCount: Int
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
input Travel_Policy__cFilter {
|
|
266
|
-
Id: IDFilter
|
|
267
|
-
Policy_Tier__c: StringFilter
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
# Pagination
|
|
271
|
-
type PageInfo {
|
|
272
|
-
hasNextPage: Boolean!
|
|
273
|
-
hasPreviousPage: Boolean!
|
|
274
|
-
startCursor: String
|
|
275
|
-
endCursor: String
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
# Filter input types
|
|
279
|
-
input IDFilter {
|
|
280
|
-
eq: ID
|
|
281
|
-
ne: ID
|
|
282
|
-
in: [ID!]
|
|
283
|
-
nin: [ID!]
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
input StringFilter {
|
|
287
|
-
eq: String
|
|
288
|
-
ne: String
|
|
289
|
-
like: String
|
|
290
|
-
in: [String!]
|
|
291
|
-
nin: [String!]
|
|
292
|
-
}
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect } from "react";
|
|
2
|
-
import * as live from "@/data/engine-live-data";
|
|
3
|
-
|
|
4
|
-
const SIMULATED_LOAD_MS = 2000;
|
|
5
|
-
|
|
6
|
-
export interface EngineLiveData {
|
|
7
|
-
loading: boolean;
|
|
8
|
-
mapMarkers: typeof live.MAP_MARKERS;
|
|
9
|
-
mapArcs: typeof live.MAP_ARCS;
|
|
10
|
-
mapOverlays: typeof live.MAP_OVERLAYS;
|
|
11
|
-
flightStatusList: typeof live.FLIGHT_STATUS_LIST;
|
|
12
|
-
travelerCards: typeof live.TRAVELER_CARDS;
|
|
13
|
-
disruptionCards: typeof live.DISRUPTION_CARDS;
|
|
14
|
-
escalationCards: typeof live.ESCALATION_CARDS;
|
|
15
|
-
monthlySpend: typeof live.MONTHLY_SPEND;
|
|
16
|
-
metrics: typeof live.METRICS;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Provides the live dataset for the Engine Travel Command Center.
|
|
21
|
-
*
|
|
22
|
-
* Simulates a 2-second network fetch, then resolves with all live data.
|
|
23
|
-
* Does NOT control the data mode — that's handled by ENABLE_SAMPLE_DATA_CACHE
|
|
24
|
-
* in src/lib/dataStrategy.ts. When the flag is false, every useDataSource call
|
|
25
|
-
* returns its live prop (which this hook provides) instead of its sample prop.
|
|
26
|
-
*/
|
|
27
|
-
export function useEngineLiveData(): EngineLiveData {
|
|
28
|
-
const [loading, setLoading] = useState(true);
|
|
29
|
-
|
|
30
|
-
useEffect(() => {
|
|
31
|
-
const timer = setTimeout(() => {
|
|
32
|
-
setLoading(false);
|
|
33
|
-
}, SIMULATED_LOAD_MS);
|
|
34
|
-
return () => clearTimeout(timer);
|
|
35
|
-
}, []);
|
|
36
|
-
|
|
37
|
-
return {
|
|
38
|
-
loading,
|
|
39
|
-
mapMarkers: live.MAP_MARKERS,
|
|
40
|
-
mapArcs: live.MAP_ARCS,
|
|
41
|
-
mapOverlays: live.MAP_OVERLAYS,
|
|
42
|
-
flightStatusList: live.FLIGHT_STATUS_LIST,
|
|
43
|
-
travelerCards: live.TRAVELER_CARDS,
|
|
44
|
-
disruptionCards: live.DISRUPTION_CARDS,
|
|
45
|
-
escalationCards: live.ESCALATION_CARDS,
|
|
46
|
-
monthlySpend: live.MONTHLY_SPEND,
|
|
47
|
-
metrics: live.METRICS,
|
|
48
|
-
};
|
|
49
|
-
}
|
package/data/useEvaAgent.ts
DELETED
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useEvaAgent — React hook for the Agentforce Agent REST API
|
|
3
|
-
*
|
|
4
|
-
* Handles the full lifecycle:
|
|
5
|
-
* 1. OAuth client-credentials → access_token
|
|
6
|
-
* 2. POST …/sessions → sessionId + message href
|
|
7
|
-
* 3. POST …/messages → agent response text
|
|
8
|
-
* 4. DELETE …/sessions → cleanup on unmount
|
|
9
|
-
*
|
|
10
|
-
* All HTTP calls go through the Vite proxy (see vite.config.ts):
|
|
11
|
-
* /sf-oauth/* → myDomainUrl
|
|
12
|
-
* /sf-agent/* → agentApiBaseUrl
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
16
|
-
import { AGENT_API_CONFIG } from "@/config/agentApi";
|
|
17
|
-
|
|
18
|
-
export interface AgentMessage {
|
|
19
|
-
role: "user" | "agent";
|
|
20
|
-
text: string;
|
|
21
|
-
id: string;
|
|
22
|
-
timestamp: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface SessionState {
|
|
26
|
-
accessToken: string;
|
|
27
|
-
sessionId: string;
|
|
28
|
-
messagesHref: string;
|
|
29
|
-
endHref: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export default function useEvaAgent() {
|
|
33
|
-
const [messages, setMessages] = useState<AgentMessage[]>([]);
|
|
34
|
-
const [isConnecting, setIsConnecting] = useState(false);
|
|
35
|
-
const [isReady, setIsReady] = useState(false);
|
|
36
|
-
const [isSending, setIsSending] = useState(false);
|
|
37
|
-
const [error, setError] = useState<string | null>(null);
|
|
38
|
-
|
|
39
|
-
const sessionRef = useRef<SessionState | null>(null);
|
|
40
|
-
const sequenceRef = useRef(1);
|
|
41
|
-
const abortRef = useRef<AbortController | null>(null);
|
|
42
|
-
|
|
43
|
-
/* ── Step 1: OAuth token ──────────────────────────────── */
|
|
44
|
-
async function authenticate(signal: AbortSignal): Promise<string> {
|
|
45
|
-
const { clientId, clientSecret } = AGENT_API_CONFIG;
|
|
46
|
-
|
|
47
|
-
const body = new URLSearchParams({
|
|
48
|
-
grant_type: "client_credentials",
|
|
49
|
-
client_id: clientId,
|
|
50
|
-
client_secret: clientSecret,
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
const res = await fetch("/sf-oauth/services/oauth2/token", {
|
|
54
|
-
method: "POST",
|
|
55
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
56
|
-
body,
|
|
57
|
-
signal,
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
if (!res.ok) {
|
|
61
|
-
const text = await res.text();
|
|
62
|
-
throw new Error(`OAuth failed (${res.status}): ${text}`);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const data = await res.json();
|
|
66
|
-
return data.access_token;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/* ── Step 2: Create session ───────────────────────────── */
|
|
70
|
-
async function createSession(
|
|
71
|
-
accessToken: string,
|
|
72
|
-
signal: AbortSignal
|
|
73
|
-
): Promise<Omit<SessionState, "accessToken">> {
|
|
74
|
-
const { agentId, bypassUser, demoTraveler, myDomainUrl } =
|
|
75
|
-
AGENT_API_CONFIG;
|
|
76
|
-
|
|
77
|
-
const payload = {
|
|
78
|
-
externalSessionKey: crypto.randomUUID(),
|
|
79
|
-
instanceConfig: { endpoint: myDomainUrl },
|
|
80
|
-
streamingCapabilities: { chunkTypes: ["Text"] },
|
|
81
|
-
bypassUser,
|
|
82
|
-
variables: [
|
|
83
|
-
{
|
|
84
|
-
name: "$Context.EndUserId",
|
|
85
|
-
type: "Text",
|
|
86
|
-
value: demoTraveler.contactId,
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
name: "$Context.EndUserEmail",
|
|
90
|
-
type: "Text",
|
|
91
|
-
value: demoTraveler.email,
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
name: "$Context.EndUserFirstName",
|
|
95
|
-
type: "Text",
|
|
96
|
-
value: demoTraveler.firstName,
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
name: "$Context.EndUserLastName",
|
|
100
|
-
type: "Text",
|
|
101
|
-
value: demoTraveler.lastName,
|
|
102
|
-
},
|
|
103
|
-
],
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
const res = await fetch(
|
|
107
|
-
`/sf-agent/einstein/ai-agent/v1/agents/${agentId}/sessions`,
|
|
108
|
-
{
|
|
109
|
-
method: "POST",
|
|
110
|
-
headers: {
|
|
111
|
-
Authorization: `Bearer ${accessToken}`,
|
|
112
|
-
"Content-Type": "application/json",
|
|
113
|
-
},
|
|
114
|
-
body: JSON.stringify(payload),
|
|
115
|
-
signal,
|
|
116
|
-
}
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
if (!res.ok) {
|
|
120
|
-
const text = await res.text();
|
|
121
|
-
throw new Error(`Session creation failed (${res.status}): ${text}`);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const data = await res.json();
|
|
125
|
-
return {
|
|
126
|
-
sessionId: data.sessionId,
|
|
127
|
-
messagesHref: data._links?.messages?.href ?? "",
|
|
128
|
-
endHref: data._links?.end?.href ?? "",
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/* ── Step 3: Send message ─────────────────────────────── */
|
|
133
|
-
const sendMessage = useCallback(async (text: string) => {
|
|
134
|
-
const session = sessionRef.current;
|
|
135
|
-
if (!session) return;
|
|
136
|
-
|
|
137
|
-
const userMsg: AgentMessage = {
|
|
138
|
-
role: "user",
|
|
139
|
-
text,
|
|
140
|
-
id: `user-${Date.now()}`,
|
|
141
|
-
timestamp: new Date().toISOString(),
|
|
142
|
-
};
|
|
143
|
-
setMessages((prev) => [...prev, userMsg]);
|
|
144
|
-
setIsSending(true);
|
|
145
|
-
|
|
146
|
-
try {
|
|
147
|
-
const seqId = sequenceRef.current++;
|
|
148
|
-
|
|
149
|
-
const messagesUrl = session.messagesHref.startsWith("http")
|
|
150
|
-
? `/sf-agent${new URL(session.messagesHref).pathname}`
|
|
151
|
-
: `/sf-agent${session.messagesHref}`;
|
|
152
|
-
|
|
153
|
-
const res = await fetch(messagesUrl, {
|
|
154
|
-
method: "POST",
|
|
155
|
-
headers: {
|
|
156
|
-
Authorization: `Bearer ${session.accessToken}`,
|
|
157
|
-
"Content-Type": "application/json",
|
|
158
|
-
Accept: "application/json",
|
|
159
|
-
},
|
|
160
|
-
body: JSON.stringify({
|
|
161
|
-
message: { sequenceId: seqId, type: "Text", text },
|
|
162
|
-
}),
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
if (!res.ok) {
|
|
166
|
-
const errText = await res.text();
|
|
167
|
-
throw new Error(`Agent message failed (${res.status}): ${errText}`);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const data = await res.json();
|
|
171
|
-
|
|
172
|
-
const agentTexts: string[] = [];
|
|
173
|
-
if (Array.isArray(data.messages)) {
|
|
174
|
-
for (const m of data.messages) {
|
|
175
|
-
if (m.type === "Inform" && m.message) {
|
|
176
|
-
agentTexts.push(m.message);
|
|
177
|
-
} else if (m.type === "Text" && m.message) {
|
|
178
|
-
agentTexts.push(m.message);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const responseText =
|
|
184
|
-
agentTexts.length > 0
|
|
185
|
-
? agentTexts.join("\n\n")
|
|
186
|
-
: data.message ?? "No response from agent.";
|
|
187
|
-
|
|
188
|
-
const agentMsg: AgentMessage = {
|
|
189
|
-
role: "agent",
|
|
190
|
-
text: responseText,
|
|
191
|
-
id: `agent-${Date.now()}`,
|
|
192
|
-
timestamp: new Date().toISOString(),
|
|
193
|
-
};
|
|
194
|
-
setMessages((prev) => [...prev, agentMsg]);
|
|
195
|
-
} catch (err: unknown) {
|
|
196
|
-
const msg = err instanceof Error ? err.message : "Unknown error";
|
|
197
|
-
setError(msg);
|
|
198
|
-
const errMsg: AgentMessage = {
|
|
199
|
-
role: "agent",
|
|
200
|
-
text: `Error: ${msg}`,
|
|
201
|
-
id: `error-${Date.now()}`,
|
|
202
|
-
timestamp: new Date().toISOString(),
|
|
203
|
-
};
|
|
204
|
-
setMessages((prev) => [...prev, errMsg]);
|
|
205
|
-
} finally {
|
|
206
|
-
setIsSending(false);
|
|
207
|
-
}
|
|
208
|
-
}, []);
|
|
209
|
-
|
|
210
|
-
/* ── Step 4: End session ──────────────────────────────── */
|
|
211
|
-
const endSession = useCallback(async () => {
|
|
212
|
-
const session = sessionRef.current;
|
|
213
|
-
if (!session) return;
|
|
214
|
-
|
|
215
|
-
try {
|
|
216
|
-
const endUrl = session.endHref.startsWith("http")
|
|
217
|
-
? `/sf-agent${new URL(session.endHref).pathname}`
|
|
218
|
-
: `/sf-agent${session.endHref}`;
|
|
219
|
-
|
|
220
|
-
await fetch(endUrl, {
|
|
221
|
-
method: "DELETE",
|
|
222
|
-
headers: { Authorization: `Bearer ${session.accessToken}` },
|
|
223
|
-
});
|
|
224
|
-
} catch {
|
|
225
|
-
/* best-effort cleanup */
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
sessionRef.current = null;
|
|
229
|
-
setIsReady(false);
|
|
230
|
-
}, []);
|
|
231
|
-
|
|
232
|
-
/* ── Init: authenticate + create session on mount ─────── */
|
|
233
|
-
const connect = useCallback(async () => {
|
|
234
|
-
if (sessionRef.current) return;
|
|
235
|
-
|
|
236
|
-
abortRef.current = new AbortController();
|
|
237
|
-
const { signal } = abortRef.current;
|
|
238
|
-
|
|
239
|
-
setIsConnecting(true);
|
|
240
|
-
setError(null);
|
|
241
|
-
|
|
242
|
-
try {
|
|
243
|
-
const accessToken = await authenticate(signal);
|
|
244
|
-
const session = await createSession(accessToken, signal);
|
|
245
|
-
|
|
246
|
-
sessionRef.current = { accessToken, ...session };
|
|
247
|
-
sequenceRef.current = 1;
|
|
248
|
-
setIsReady(true);
|
|
249
|
-
} catch (err: unknown) {
|
|
250
|
-
if ((err as Error).name !== "AbortError") {
|
|
251
|
-
const msg = err instanceof Error ? err.message : "Connection failed";
|
|
252
|
-
setError(msg);
|
|
253
|
-
}
|
|
254
|
-
} finally {
|
|
255
|
-
setIsConnecting(false);
|
|
256
|
-
}
|
|
257
|
-
}, []);
|
|
258
|
-
|
|
259
|
-
/* ── Cleanup on unmount ───────────────────────────────── */
|
|
260
|
-
useEffect(() => {
|
|
261
|
-
return () => {
|
|
262
|
-
abortRef.current?.abort();
|
|
263
|
-
if (sessionRef.current) {
|
|
264
|
-
const session = sessionRef.current;
|
|
265
|
-
const endUrl = session.endHref.startsWith("http")
|
|
266
|
-
? `/sf-agent${new URL(session.endHref).pathname}`
|
|
267
|
-
: `/sf-agent${session.endHref}`;
|
|
268
|
-
|
|
269
|
-
fetch(endUrl, {
|
|
270
|
-
method: "DELETE",
|
|
271
|
-
headers: { Authorization: `Bearer ${session.accessToken}` },
|
|
272
|
-
}).catch(() => {});
|
|
273
|
-
sessionRef.current = null;
|
|
274
|
-
}
|
|
275
|
-
};
|
|
276
|
-
}, []);
|
|
277
|
-
|
|
278
|
-
return {
|
|
279
|
-
messages,
|
|
280
|
-
isConnecting,
|
|
281
|
-
isReady,
|
|
282
|
-
isSending,
|
|
283
|
-
error,
|
|
284
|
-
connect,
|
|
285
|
-
sendMessage,
|
|
286
|
-
endSession,
|
|
287
|
-
};
|
|
288
|
-
}
|