cap-copilot-widget 0.1.1 → 0.1.2
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/dist/btp-copilot.esm.js +646 -266
- package/dist/btp-copilot.esm.js.map +1 -1
- package/dist/btp-copilot.js +15 -10
- package/dist/btp-copilot.js.map +1 -1
- package/package.json +3 -3
package/dist/btp-copilot.esm.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
const
|
|
2
|
-
static resolve(
|
|
3
|
-
if (t) return t.replace(/^Bearer\s+/i, "");
|
|
4
|
-
const e = window.__BTP_COPILOT_TOKEN__;
|
|
1
|
+
const A = class A {
|
|
2
|
+
static resolve(e) {
|
|
5
3
|
if (e) return e.replace(/^Bearer\s+/i, "");
|
|
6
|
-
const
|
|
4
|
+
const t = window.__BTP_COPILOT_TOKEN__;
|
|
5
|
+
if (t) return t.replace(/^Bearer\s+/i, "");
|
|
6
|
+
const n = A._fromCookie();
|
|
7
7
|
if (n) return n;
|
|
8
8
|
try {
|
|
9
9
|
const r = localStorage.getItem("access_token");
|
|
@@ -12,15 +12,15 @@ const f = class f {
|
|
|
12
12
|
}
|
|
13
13
|
return null;
|
|
14
14
|
}
|
|
15
|
-
static getAuthHeader(
|
|
16
|
-
return
|
|
15
|
+
static getAuthHeader(e) {
|
|
16
|
+
return e ? { Authorization: `Bearer ${e}` } : {};
|
|
17
17
|
}
|
|
18
18
|
static _fromCookie() {
|
|
19
19
|
try {
|
|
20
|
-
const
|
|
21
|
-
for (const
|
|
22
|
-
const [n, ...r] =
|
|
23
|
-
if (
|
|
20
|
+
const e = document.cookie.split(";");
|
|
21
|
+
for (const t of e) {
|
|
22
|
+
const [n, ...r] = t.trim().split("=");
|
|
23
|
+
if (A.XSUAA_COOKIE_NAMES.includes(n.trim()))
|
|
24
24
|
return decodeURIComponent(r.join("=").trim());
|
|
25
25
|
}
|
|
26
26
|
} catch {
|
|
@@ -28,81 +28,87 @@ const f = class f {
|
|
|
28
28
|
return null;
|
|
29
29
|
}
|
|
30
30
|
};
|
|
31
|
-
|
|
31
|
+
A.XSUAA_COOKIE_NAMES = [
|
|
32
32
|
"x-uaa-token",
|
|
33
33
|
"xsuaa_token",
|
|
34
34
|
"access_token",
|
|
35
35
|
"X-Authorization"
|
|
36
36
|
];
|
|
37
|
-
let
|
|
38
|
-
class
|
|
37
|
+
let S = A;
|
|
38
|
+
class C {
|
|
39
39
|
static getHash() {
|
|
40
|
-
var
|
|
40
|
+
var e, t, n, r;
|
|
41
41
|
try {
|
|
42
|
-
const
|
|
43
|
-
|
|
42
|
+
const a = (n = (t = (e = sap == null ? void 0 : sap.ushell) == null ? void 0 : e.Container) == null ? void 0 : t.getService) == null ? void 0 : n.call(
|
|
43
|
+
t,
|
|
44
44
|
"CrossApplicationNavigation"
|
|
45
45
|
);
|
|
46
|
-
if (
|
|
47
|
-
const i = (r =
|
|
46
|
+
if (a) {
|
|
47
|
+
const i = (r = a.hrefForExternal) == null ? void 0 : r.call(a);
|
|
48
48
|
if (i) return i;
|
|
49
49
|
}
|
|
50
|
-
} catch (
|
|
51
|
-
console.log(
|
|
50
|
+
} catch (a) {
|
|
51
|
+
console.log(a);
|
|
52
52
|
}
|
|
53
53
|
return window.location.hash || "";
|
|
54
54
|
}
|
|
55
55
|
static getCurrentAppId() {
|
|
56
56
|
try {
|
|
57
|
-
const
|
|
58
|
-
return
|
|
57
|
+
const t = C.getHash().match(/^#([^&?/]+)/);
|
|
58
|
+
return t ? t[1] : null;
|
|
59
59
|
} catch {
|
|
60
60
|
return null;
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
static getUserLocale() {
|
|
64
|
-
var
|
|
64
|
+
var e, t, n, r, a, i;
|
|
65
65
|
try {
|
|
66
|
-
return ((i = (
|
|
66
|
+
return ((i = (a = (r = (n = (t = (e = sap == null ? void 0 : sap.ui) == null ? void 0 : e.getCore) == null ? void 0 : t.call(e)) == null ? void 0 : n.getConfiguration) == null ? void 0 : r.call(n)) == null ? void 0 : a.getLanguageTag) == null ? void 0 : i.call(a)) ?? navigator.language ?? "en";
|
|
67
67
|
} catch {
|
|
68
68
|
return navigator.language ?? "en";
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
|
-
class
|
|
72
|
+
class _ {
|
|
73
73
|
static getCurrentEntity() {
|
|
74
|
-
var
|
|
74
|
+
var e, t, n, r, a;
|
|
75
75
|
try {
|
|
76
|
-
const
|
|
77
|
-
if (!
|
|
78
|
-
let
|
|
79
|
-
for (;
|
|
80
|
-
const
|
|
81
|
-
if (
|
|
82
|
-
const s =
|
|
83
|
-
if (
|
|
84
|
-
const
|
|
85
|
-
if (
|
|
86
|
-
return
|
|
76
|
+
const i = (t = (e = sap == null ? void 0 : sap.ui) == null ? void 0 : e.getCore) == null ? void 0 : t.call(e);
|
|
77
|
+
if (!i) return null;
|
|
78
|
+
let o = document.activeElement;
|
|
79
|
+
for (; o; ) {
|
|
80
|
+
const l = o.getAttribute("id") || o.getAttribute("data-sap-ui");
|
|
81
|
+
if (l) {
|
|
82
|
+
const s = i.byId(l), c = (n = s == null ? void 0 : s.getBindingContext) == null ? void 0 : n.call(s);
|
|
83
|
+
if (c) {
|
|
84
|
+
const h = (r = c.getObject) == null ? void 0 : r.call(c);
|
|
85
|
+
if (h && typeof h == "object")
|
|
86
|
+
return _._withEntityName(
|
|
87
|
+
_._sanitize(h),
|
|
88
|
+
((a = c.getPath) == null ? void 0 : a.call(c)) ?? ""
|
|
89
|
+
);
|
|
87
90
|
}
|
|
88
91
|
}
|
|
89
|
-
|
|
92
|
+
o = o.parentElement;
|
|
90
93
|
}
|
|
91
|
-
return
|
|
94
|
+
return _._scanAllControls(i);
|
|
92
95
|
} catch {
|
|
93
96
|
return null;
|
|
94
97
|
}
|
|
95
98
|
}
|
|
96
|
-
static _scanAllControls(
|
|
97
|
-
var
|
|
99
|
+
static _scanAllControls(e) {
|
|
100
|
+
var t, n, r;
|
|
98
101
|
try {
|
|
99
|
-
const
|
|
100
|
-
for (const
|
|
101
|
-
const
|
|
102
|
-
if (
|
|
103
|
-
const s = (n =
|
|
102
|
+
const a = e.mElements ?? {};
|
|
103
|
+
for (const i of Object.keys(a)) {
|
|
104
|
+
const o = a[i], l = (t = o == null ? void 0 : o.getBindingContext) == null ? void 0 : t.call(o);
|
|
105
|
+
if (l) {
|
|
106
|
+
const s = (n = l.getObject) == null ? void 0 : n.call(l);
|
|
104
107
|
if (s && typeof s == "object" && Object.keys(s).length > 0)
|
|
105
|
-
return
|
|
108
|
+
return _._withEntityName(
|
|
109
|
+
_._sanitize(s),
|
|
110
|
+
((r = l.getPath) == null ? void 0 : r.call(l)) ?? ""
|
|
111
|
+
);
|
|
106
112
|
}
|
|
107
113
|
}
|
|
108
114
|
} catch {
|
|
@@ -110,239 +116,573 @@ class u {
|
|
|
110
116
|
return null;
|
|
111
117
|
}
|
|
112
118
|
static getServiceUrl() {
|
|
113
|
-
var
|
|
119
|
+
var e, t;
|
|
114
120
|
try {
|
|
115
|
-
const n = (
|
|
121
|
+
const n = (t = (e = sap == null ? void 0 : sap.ui) == null ? void 0 : e.getCore) == null ? void 0 : t.call(e);
|
|
116
122
|
if (!n) return null;
|
|
117
123
|
const r = n.mElements ?? {};
|
|
118
|
-
for (const
|
|
119
|
-
const i = r[
|
|
124
|
+
for (const a of Object.keys(r)) {
|
|
125
|
+
const i = r[a];
|
|
120
126
|
if (!(i != null && i.getModel)) continue;
|
|
121
|
-
const
|
|
122
|
-
if (!
|
|
123
|
-
const
|
|
124
|
-
if (typeof
|
|
125
|
-
return
|
|
127
|
+
const o = i.getModel();
|
|
128
|
+
if (!o) continue;
|
|
129
|
+
const l = o.sServiceUrl ?? o._sServiceUrl;
|
|
130
|
+
if (typeof l == "string" && l.length > 4)
|
|
131
|
+
return l.replace(/\/$/, "");
|
|
126
132
|
}
|
|
127
133
|
} catch {
|
|
128
134
|
}
|
|
129
135
|
return null;
|
|
130
136
|
}
|
|
131
|
-
static _sanitize(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
137
|
+
static _sanitize(e) {
|
|
138
|
+
var n;
|
|
139
|
+
const t = {};
|
|
140
|
+
for (const [r, a] of Object.entries(e))
|
|
141
|
+
if (!r.startsWith("__"))
|
|
142
|
+
if (a === null || typeof a != "object")
|
|
143
|
+
t[r] = a;
|
|
144
|
+
else if (a.__deferred) {
|
|
145
|
+
const i = (n = a.__deferred) == null ? void 0 : n.uri;
|
|
146
|
+
typeof i == "string" && i && (t[`${r}_navUri`] = i);
|
|
147
|
+
} else
|
|
148
|
+
t[r] = a;
|
|
149
|
+
return t;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Inject `_entity` into a sanitized binding context object by parsing the
|
|
153
|
+
* OData context path. The path looks like `/FertilizerBlend(orderID='2466',...)`
|
|
154
|
+
* or `/Farms('abc-123')` — the entity set name is the first path segment.
|
|
155
|
+
*/
|
|
156
|
+
static _withEntityName(e, t) {
|
|
157
|
+
if (e._entity) return e;
|
|
158
|
+
try {
|
|
159
|
+
const n = t.match(/^\/([A-Za-z_][A-Za-z0-9_]*)(?:\(|$|\/)/);
|
|
160
|
+
if (n != null && n[1]) return { _entity: n[1], ...e };
|
|
161
|
+
} catch {
|
|
162
|
+
}
|
|
135
163
|
return e;
|
|
136
164
|
}
|
|
137
165
|
}
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
166
|
+
const m = class m {
|
|
167
|
+
// ── Public API ──────────────────────────────────────────────────────────────
|
|
168
|
+
/**
|
|
169
|
+
* Fetch and fully parse $metadata for a service URL.
|
|
170
|
+
* Result is memory-cached for the page lifetime and persisted to sessionStorage.
|
|
171
|
+
* Returns null on any network / parse failure.
|
|
172
|
+
*/
|
|
173
|
+
static async fetchMetadata(e, t) {
|
|
174
|
+
const n = `btp-copilot-meta-v2-${e}`;
|
|
175
|
+
if (m._metaCache.has(n))
|
|
176
|
+
return m._metaCache.get(n);
|
|
143
177
|
const r = sessionStorage.getItem(n);
|
|
144
178
|
if (r)
|
|
145
179
|
try {
|
|
146
|
-
const
|
|
147
|
-
return
|
|
180
|
+
const i = { ...JSON.parse(r), rawXml: "" };
|
|
181
|
+
return m._metaCache.set(n, i), i;
|
|
148
182
|
} catch {
|
|
149
183
|
}
|
|
150
184
|
try {
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
const
|
|
154
|
-
if (!
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const r = new DOMParser().parseFromString(t, "text/xml").querySelectorAll("EntityType");
|
|
164
|
-
return Array.from(r).map((o) => {
|
|
165
|
-
const i = o.getAttribute("Name") ?? "", a = o.querySelectorAll("Key > PropertyRef"), s = new Set(
|
|
166
|
-
Array.from(a).map((h) => h.getAttribute("Name") ?? "")
|
|
167
|
-
), l = Array.from(
|
|
168
|
-
o.querySelectorAll("Property")
|
|
169
|
-
).map((h) => ({
|
|
170
|
-
name: h.getAttribute("Name") ?? "",
|
|
171
|
-
type: c._shortType(h.getAttribute("Type") ?? ""),
|
|
172
|
-
isKey: s.has(h.getAttribute("Name") ?? ""),
|
|
173
|
-
nullable: h.getAttribute("Nullable") !== "false"
|
|
174
|
-
})), p = Array.from(
|
|
175
|
-
o.querySelectorAll("NavigationProperty")
|
|
176
|
-
).map((h) => ({
|
|
177
|
-
name: h.getAttribute("Name") ?? "",
|
|
178
|
-
type: c._shortType(
|
|
179
|
-
h.getAttribute("Type") ?? h.getAttribute("ToRole") ?? ""
|
|
180
|
-
)
|
|
181
|
-
}));
|
|
182
|
-
return { name: i, properties: l, navProperties: p };
|
|
183
|
-
});
|
|
185
|
+
const a = `${e.replace(/\/$/, "")}/$metadata`, i = { Accept: "application/xml" };
|
|
186
|
+
t && (i.Authorization = `Bearer ${t}`);
|
|
187
|
+
const o = await fetch(a, { headers: i });
|
|
188
|
+
if (!o.ok) return null;
|
|
189
|
+
const l = await o.text(), s = m._parseEDMX(l, e);
|
|
190
|
+
m._metaCache.set(n, s);
|
|
191
|
+
try {
|
|
192
|
+
const { rawXml: c, ...h } = s;
|
|
193
|
+
sessionStorage.setItem(n, JSON.stringify(h));
|
|
194
|
+
} catch {
|
|
195
|
+
}
|
|
196
|
+
return s;
|
|
184
197
|
} catch {
|
|
185
|
-
return
|
|
198
|
+
return null;
|
|
186
199
|
}
|
|
187
200
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
201
|
+
/**
|
|
202
|
+
* Build schema hint documents from parsed metadata.
|
|
203
|
+
* Used to populate fiori_context.extra.schema_hint for each chat message.
|
|
204
|
+
*/
|
|
205
|
+
static buildSchemaDocuments(e, t) {
|
|
192
206
|
const n = [];
|
|
193
|
-
for (const r of
|
|
207
|
+
for (const r of e.entities) {
|
|
194
208
|
if (!r.name) continue;
|
|
195
|
-
const
|
|
209
|
+
const a = r.entitySetName ?? r.name, i = r.properties.filter((s) => s.isKey), o = r.properties.filter((s) => !s.isKey), l = [
|
|
196
210
|
`Entity: ${r.name}`,
|
|
197
|
-
`
|
|
198
|
-
`
|
|
199
|
-
`
|
|
211
|
+
`Entity set: ${a}`,
|
|
212
|
+
`OData service: ${t}`,
|
|
213
|
+
`Entity set path: ${t}/${a}`,
|
|
214
|
+
`Count path: ${t}/${a}/$count`
|
|
200
215
|
];
|
|
201
|
-
|
|
202
|
-
`Key fields: ${
|
|
203
|
-
),
|
|
204
|
-
`Fields: ${
|
|
205
|
-
|
|
206
|
-
).join(", ")}`
|
|
207
|
-
), r.
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
216
|
+
i.length && l.push(
|
|
217
|
+
`Key fields: ${i.map((s) => `${s.name} (${s.type})`).join(", ")}`
|
|
218
|
+
), o.length && l.push(
|
|
219
|
+
`Fields: ${o.map((s) => `${s.name} (${s.type}${s.nullable ? "" : ", required"})`).join(", ")}`
|
|
220
|
+
), r.navProperties.length && l.push(
|
|
221
|
+
`Navigation: ${r.navProperties.map((s) => `${s.name} → ${s.type}${s.cardinality === "*" ? "[]" : ""}`).join(", ")}`
|
|
222
|
+
), n.push({ title: `${r.name} entity schema`, content: l.join(`
|
|
223
|
+
`) });
|
|
224
|
+
}
|
|
225
|
+
if (e.associations.length) {
|
|
226
|
+
const r = e.associations.map(
|
|
227
|
+
(a) => `${a.sourceEntity}.${a.navigationProperty} → ${a.targetEntity} (${a.cardinality === "*" ? "many" : "one"})`
|
|
228
|
+
);
|
|
229
|
+
n.push({
|
|
230
|
+
title: "Entity associations and navigation",
|
|
231
|
+
content: ["Associations:", ...r].join(`
|
|
232
|
+
`)
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
if (e.actions.length) {
|
|
236
|
+
const r = e.actions.map((a) => {
|
|
237
|
+
const i = a.parameters.map((o) => `${o.name}: ${o.type}`).join(", ");
|
|
238
|
+
return `${a.name}(${i})${a.bound && a.entity ? ` [bound to ${a.entity}]` : ""}`;
|
|
239
|
+
});
|
|
240
|
+
n.push({
|
|
241
|
+
title: "OData actions and functions",
|
|
242
|
+
content: ["Actions/Functions:", ...r].join(`
|
|
212
243
|
`)
|
|
213
244
|
});
|
|
214
245
|
}
|
|
215
|
-
return
|
|
246
|
+
return e.entities.length > 0 && n.push({
|
|
216
247
|
title: "Available OData entity sets",
|
|
217
248
|
content: [
|
|
218
|
-
`OData service: ${
|
|
219
|
-
`Entities: ${
|
|
220
|
-
"GET {
|
|
249
|
+
`OData service: ${t}`,
|
|
250
|
+
`Entities: ${e.entities.map((r) => r.entitySetName ?? r.name).join(", ")}`,
|
|
251
|
+
"Patterns: GET {entitySet}/$count | ?$filter=field eq 'value' | ?$top=10&$skip=0 | ?$orderby=field desc"
|
|
221
252
|
].join(`
|
|
222
253
|
`)
|
|
223
254
|
}), n;
|
|
224
255
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
256
|
+
/**
|
|
257
|
+
* Legacy helper kept for backward compatibility.
|
|
258
|
+
* Prefer fetchMetadata() + buildSchemaDocuments() for new code.
|
|
259
|
+
*/
|
|
260
|
+
static async fetchSchemaDocuments(e, t) {
|
|
261
|
+
const n = await m.fetchMetadata(e, t);
|
|
262
|
+
return n ? m.buildSchemaDocuments(n, e) : [];
|
|
263
|
+
}
|
|
264
|
+
/** Invalidate cached metadata for a service (call when metadata may have changed). */
|
|
265
|
+
static invalidate(e) {
|
|
266
|
+
const t = `btp-copilot-meta-v2-${e}`;
|
|
267
|
+
m._metaCache.delete(t), sessionStorage.removeItem(t);
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Fetch live entity data from an OData service with full error capture.
|
|
271
|
+
*
|
|
272
|
+
* Unlike a raw fetch(), this method:
|
|
273
|
+
* - Returns { data, error } — never throws
|
|
274
|
+
* - Captures the HTTP status and error body on failure
|
|
275
|
+
* - Builds a human-readable error context string the chatbot can use to
|
|
276
|
+
* explain to the user why data is unavailable, instead of returning
|
|
277
|
+
* an empty result or crashing silently
|
|
278
|
+
*
|
|
279
|
+
* @example
|
|
280
|
+
* const { data, error, errorContext } = await ODataProbe.fetchEntityData(
|
|
281
|
+
* '/odata/v4/fertilizer-blend/Farms', token
|
|
282
|
+
* );
|
|
283
|
+
* if (error) ctx.extra.fetch_errors = [...(ctx.extra.fetch_errors ?? []), errorContext];
|
|
284
|
+
*/
|
|
285
|
+
static async fetchEntityData(e, t, n) {
|
|
286
|
+
try {
|
|
287
|
+
const r = { Accept: "application/json" };
|
|
288
|
+
t && (r.Authorization = `Bearer ${t}`);
|
|
289
|
+
const a = await fetch(e, { ...n, headers: { ...r, ...(n == null ? void 0 : n.headers) ?? {} } });
|
|
290
|
+
if (!a.ok) {
|
|
291
|
+
let o = "";
|
|
292
|
+
try {
|
|
293
|
+
o = await a.text();
|
|
294
|
+
} catch {
|
|
295
|
+
}
|
|
296
|
+
const l = `HTTP ${a.status} ${a.statusText}`, s = [
|
|
297
|
+
`OData fetch failed: ${e}`,
|
|
298
|
+
`Status: ${l}`,
|
|
299
|
+
o ? `Response: ${o.slice(0, 500)}` : "",
|
|
300
|
+
"This entity's data cannot be displayed to the user right now.",
|
|
301
|
+
"The underlying service may have an API mismatch (e.g. invalid $expand)."
|
|
302
|
+
].filter(Boolean).join(`
|
|
303
|
+
`);
|
|
304
|
+
return { data: null, error: l, errorContext: s };
|
|
305
|
+
}
|
|
306
|
+
return { data: await a.json(), error: null, errorContext: null };
|
|
307
|
+
} catch (r) {
|
|
308
|
+
const a = r instanceof Error ? r.message : String(r), i = [
|
|
309
|
+
`OData fetch failed: ${e}`,
|
|
310
|
+
`Error: ${a}`,
|
|
311
|
+
"This entity's data cannot be displayed to the user right now."
|
|
312
|
+
].join(`
|
|
313
|
+
`);
|
|
314
|
+
return { data: null, error: a, errorContext: i };
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Track an OData fetch error in sessionStorage so the ContextBridge can
|
|
319
|
+
* include it in the next context push to the chatbot iframe.
|
|
320
|
+
* Call this whenever a live data fetch in the host app fails.
|
|
321
|
+
*/
|
|
322
|
+
static recordFetchError(e, t, n) {
|
|
323
|
+
try {
|
|
324
|
+
const r = "btp-copilot-fetch-errors", i = [
|
|
325
|
+
...JSON.parse(sessionStorage.getItem(r) ?? "[]").filter((o) => o.entity !== e),
|
|
326
|
+
{ entity: e, url: t, reason: n, at: (/* @__PURE__ */ new Date()).toISOString() }
|
|
327
|
+
].slice(-10);
|
|
328
|
+
sessionStorage.setItem(r, JSON.stringify(i));
|
|
329
|
+
} catch {
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
/** Read all recorded fetch errors as a schema_hint-style string. */
|
|
333
|
+
static getFetchErrorContext() {
|
|
334
|
+
try {
|
|
335
|
+
const t = JSON.parse(sessionStorage.getItem("btp-copilot-fetch-errors") ?? "[]");
|
|
336
|
+
return t.length === 0 ? null : [
|
|
337
|
+
"The following entities currently have backend errors and may not show live data:",
|
|
338
|
+
...t.map(
|
|
339
|
+
(r) => `- ${r.entity}: ${r.reason} (${r.url}) [at ${r.at}]`
|
|
340
|
+
)
|
|
341
|
+
].join(`
|
|
342
|
+
`);
|
|
343
|
+
} catch {
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Find all entities in the parsed metadata that link TO the given entity type.
|
|
349
|
+
* These are the "reverse" associations — i.e. other entities that have a FK
|
|
350
|
+
* pointing at `entityName`.
|
|
351
|
+
*
|
|
352
|
+
* For each such entity, returns the entity set name, the navigation property
|
|
353
|
+
* name, and the FK / referenced-key pair so the caller can build an OData
|
|
354
|
+
* $filter query like:
|
|
355
|
+
* `GET /Farms?$filter=to_FertilizerBlend_orderID eq '2466'`
|
|
356
|
+
*
|
|
357
|
+
* @example
|
|
358
|
+
* // User is viewing FertilizerBlend(orderID='2466')
|
|
359
|
+
* const links = ODataProbe.findLinkedEntities(parsed, "FertilizerBlend");
|
|
360
|
+
* // → [{ entitySetName: "Farms", navProperty: "to_FertilizerBlend",
|
|
361
|
+
* // foreignKey: "to_FertilizerBlend_orderID", referencedKey: "orderID" }]
|
|
362
|
+
*/
|
|
363
|
+
static findLinkedEntities(e, t) {
|
|
364
|
+
const n = [];
|
|
365
|
+
for (const r of e.associations)
|
|
366
|
+
if (r.targetEntity === t && r.foreignKey && r.referencedKey) {
|
|
367
|
+
const a = e.entities.find(
|
|
368
|
+
(i) => i.name === r.sourceEntity
|
|
369
|
+
);
|
|
370
|
+
a && n.push({
|
|
371
|
+
entitySetName: a.entitySetName ?? a.name,
|
|
372
|
+
navProperty: r.navigationProperty,
|
|
373
|
+
foreignKey: r.foreignKey,
|
|
374
|
+
referencedKey: r.referencedKey
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
return n;
|
|
378
|
+
}
|
|
379
|
+
// ── EDMX Parser ─────────────────────────────────────────────────────────────
|
|
380
|
+
static _parseEDMX(e, t) {
|
|
381
|
+
const n = [], r = [], a = [];
|
|
382
|
+
try {
|
|
383
|
+
const i = new DOMParser().parseFromString(e, "text/xml"), o = /* @__PURE__ */ new Map();
|
|
384
|
+
i.querySelectorAll("EntitySet").forEach((s) => {
|
|
385
|
+
const c = s.getAttribute("EntityType") ?? "", h = c.split(".").pop() ?? c, p = s.getAttribute("Name") ?? h;
|
|
386
|
+
o.set(h, p);
|
|
387
|
+
}), i.querySelectorAll("EntityType").forEach((s) => {
|
|
388
|
+
const c = s.getAttribute("Name") ?? "";
|
|
389
|
+
if (!c) return;
|
|
390
|
+
const h = new Set(
|
|
391
|
+
Array.from(s.querySelectorAll("Key > PropertyRef")).map(
|
|
392
|
+
(d) => d.getAttribute("Name") ?? ""
|
|
393
|
+
)
|
|
394
|
+
), p = Array.from(
|
|
395
|
+
s.querySelectorAll("Property")
|
|
396
|
+
).map((d) => ({
|
|
397
|
+
name: d.getAttribute("Name") ?? "",
|
|
398
|
+
type: m._shortType(d.getAttribute("Type") ?? ""),
|
|
399
|
+
isKey: h.has(d.getAttribute("Name") ?? ""),
|
|
400
|
+
nullable: d.getAttribute("Nullable") !== "false"
|
|
401
|
+
})), b = Array.from(
|
|
402
|
+
s.querySelectorAll("NavigationProperty")
|
|
403
|
+
).map((d) => {
|
|
404
|
+
const v = d.getAttribute("Type") ?? d.getAttribute("ToRole") ?? "", w = v.startsWith("Collection("), u = d.querySelector("ReferentialConstraint");
|
|
405
|
+
return {
|
|
406
|
+
name: d.getAttribute("Name") ?? "",
|
|
407
|
+
type: m._shortType(v),
|
|
408
|
+
cardinality: w ? "*" : "1",
|
|
409
|
+
partner: d.getAttribute("Partner") ?? void 0,
|
|
410
|
+
foreignKey: (u == null ? void 0 : u.getAttribute("Property")) ?? void 0,
|
|
411
|
+
referencedKey: (u == null ? void 0 : u.getAttribute("ReferencedProperty")) ?? void 0
|
|
412
|
+
};
|
|
413
|
+
});
|
|
414
|
+
n.push({
|
|
415
|
+
name: c,
|
|
416
|
+
entitySetName: o.get(c) ?? c,
|
|
417
|
+
properties: p,
|
|
418
|
+
navProperties: b
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
for (const s of n)
|
|
422
|
+
for (const c of s.navProperties) {
|
|
423
|
+
const h = c.type.replace(/\[\]$/, "");
|
|
424
|
+
r.push({
|
|
425
|
+
sourceEntity: s.name,
|
|
426
|
+
navigationProperty: c.name,
|
|
427
|
+
targetEntity: h,
|
|
428
|
+
cardinality: c.cardinality === "*" ? "*" : "1",
|
|
429
|
+
foreignKey: c.foreignKey,
|
|
430
|
+
referencedKey: c.referencedKey
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
const l = (s, c) => {
|
|
434
|
+
const h = s.getAttribute("Name") ?? "", p = [];
|
|
435
|
+
let b;
|
|
436
|
+
s.querySelectorAll("Parameter").forEach((w, u) => {
|
|
437
|
+
const x = w.getAttribute("Name") ?? "", g = m._shortType(w.getAttribute("Type") ?? "");
|
|
438
|
+
c && u === 0 ? b = g.replace(/\[\]$/, "") : p.push({ name: x, type: g });
|
|
439
|
+
});
|
|
440
|
+
const d = s.querySelector("ReturnType"), v = d ? m._shortType(d.getAttribute("Type") ?? "") : void 0;
|
|
441
|
+
return { name: h, bound: c, entity: b, parameters: p, returnType: v };
|
|
442
|
+
};
|
|
443
|
+
i.querySelectorAll("Action").forEach((s) => {
|
|
444
|
+
const c = s.getAttribute("IsBound") === "true";
|
|
445
|
+
a.push(l(s, c));
|
|
446
|
+
}), i.querySelectorAll("Function").forEach((s) => {
|
|
447
|
+
const c = s.getAttribute("IsBound") === "true";
|
|
448
|
+
a.push(l(s, c));
|
|
449
|
+
}), i.querySelectorAll("FunctionImport").forEach((s) => {
|
|
450
|
+
const c = s.getAttribute("Name") ?? "", h = [];
|
|
451
|
+
s.querySelectorAll("Parameter").forEach((p) => {
|
|
452
|
+
h.push({
|
|
453
|
+
name: p.getAttribute("Name") ?? "",
|
|
454
|
+
type: m._shortType(p.getAttribute("Type") ?? "")
|
|
455
|
+
});
|
|
456
|
+
}), a.push({ name: c, bound: !1, parameters: h });
|
|
457
|
+
});
|
|
458
|
+
} catch {
|
|
459
|
+
}
|
|
460
|
+
return { entities: n, associations: r, actions: a, rawXml: e };
|
|
461
|
+
}
|
|
462
|
+
static _shortType(e) {
|
|
463
|
+
return e.startsWith("Collection(") ? `${e.slice(11, -1).split(".").pop()}[]` : e.split(".").pop() ?? e;
|
|
228
464
|
}
|
|
229
465
|
};
|
|
230
|
-
|
|
231
|
-
let
|
|
232
|
-
class
|
|
233
|
-
static capture(
|
|
234
|
-
var n, r,
|
|
235
|
-
const
|
|
466
|
+
m._metaCache = /* @__PURE__ */ new Map();
|
|
467
|
+
let y = m;
|
|
468
|
+
const f = class f {
|
|
469
|
+
static capture(e) {
|
|
470
|
+
var n, r, a;
|
|
471
|
+
const t = { ...e };
|
|
236
472
|
try {
|
|
237
|
-
const i =
|
|
238
|
-
i && (
|
|
473
|
+
const i = C.getHash();
|
|
474
|
+
i && (t.current_view = i);
|
|
239
475
|
} catch {
|
|
240
476
|
}
|
|
241
477
|
try {
|
|
242
|
-
const i =
|
|
243
|
-
i && Object.keys(i).length > 0 && (
|
|
478
|
+
const i = _.getCurrentEntity();
|
|
479
|
+
i && Object.keys(i).length > 0 && (t.entity_data = i);
|
|
244
480
|
} catch {
|
|
245
481
|
}
|
|
246
482
|
try {
|
|
247
|
-
|
|
483
|
+
t.user_locale || (t.user_locale = C.getUserLocale());
|
|
248
484
|
} catch {
|
|
249
485
|
}
|
|
250
486
|
try {
|
|
251
487
|
const i = window.location.pathname + window.location.search + window.location.hash;
|
|
252
|
-
(!
|
|
488
|
+
(!t.current_view || t.current_view === "") && (t.current_view = i);
|
|
253
489
|
} catch {
|
|
254
490
|
}
|
|
255
491
|
try {
|
|
256
|
-
|
|
492
|
+
t.user_locale || (t.user_locale = navigator.language || ((n = navigator.languages) == null ? void 0 : n[0]) || "en");
|
|
257
493
|
} catch {
|
|
258
494
|
}
|
|
259
495
|
try {
|
|
260
|
-
|
|
261
|
-
...
|
|
262
|
-
page_title: ((r =
|
|
263
|
-
page_url: ((
|
|
496
|
+
t.extra = {
|
|
497
|
+
...t.extra ?? {},
|
|
498
|
+
page_title: ((r = t.extra) == null ? void 0 : r.page_title) ?? document.title ?? "",
|
|
499
|
+
page_url: ((a = t.extra) == null ? void 0 : a.page_url) ?? window.location.href ?? ""
|
|
264
500
|
};
|
|
265
501
|
} catch {
|
|
266
502
|
}
|
|
267
503
|
try {
|
|
268
504
|
const i = window.__APP_CONTEXT__;
|
|
269
|
-
i && (i.entity_data && (
|
|
505
|
+
i && (i.entity_data && (t.entity_data = f._safeMerge(t.entity_data ?? {}, i.entity_data)), i.current_view && !t.current_view && (t.current_view = i.current_view), i.extra && (t.extra = f._safeMerge(i.extra, t.extra)), i.service_url && !t.service_url && (t.service_url = i.service_url));
|
|
270
506
|
} catch {
|
|
271
507
|
}
|
|
272
|
-
return
|
|
508
|
+
return t;
|
|
273
509
|
}
|
|
274
|
-
static async captureAsync(
|
|
275
|
-
var n;
|
|
276
|
-
const
|
|
277
|
-
if (
|
|
510
|
+
static async captureAsync(e) {
|
|
511
|
+
var n, r, a;
|
|
512
|
+
const t = f.capture(e);
|
|
513
|
+
if (t.service_url || (t.service_url = f._discoverServiceUrl() ?? void 0), t.service_url)
|
|
278
514
|
try {
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
${a.content}`).join(`
|
|
515
|
+
const i = ((n = t.extra) == null ? void 0 : n.auth_token) ?? null, o = await y.fetchMetadata(t.service_url, i);
|
|
516
|
+
if (o) {
|
|
517
|
+
const l = y.buildSchemaDocuments(o, t.service_url);
|
|
518
|
+
if (l.length > 0) {
|
|
519
|
+
const h = l.map((p) => `## ${p.title}
|
|
520
|
+
${p.content}`).join(`
|
|
286
521
|
|
|
287
522
|
`);
|
|
288
|
-
|
|
523
|
+
t.extra = { ...t.extra ?? {}, schema_hint: h };
|
|
524
|
+
}
|
|
525
|
+
const s = (r = t.extra) == null ? void 0 : r.backend_url;
|
|
526
|
+
if (s && o.rawXml) {
|
|
527
|
+
const h = t.app_id, p = t.service_url;
|
|
528
|
+
f._registerMetadata(s, h, p, o.rawXml, i);
|
|
529
|
+
}
|
|
530
|
+
const c = (a = t.entity_data) == null ? void 0 : a._entity;
|
|
531
|
+
if (c && t.service_url)
|
|
532
|
+
try {
|
|
533
|
+
const h = await f._fetchLinkedEntities(
|
|
534
|
+
t.service_url,
|
|
535
|
+
c,
|
|
536
|
+
t.entity_data,
|
|
537
|
+
o,
|
|
538
|
+
i
|
|
539
|
+
);
|
|
540
|
+
Object.keys(h).length > 0 && (t.entity_data = { ...t.entity_data ?? {}, ...h });
|
|
541
|
+
} catch {
|
|
542
|
+
}
|
|
289
543
|
}
|
|
290
544
|
} catch {
|
|
291
545
|
}
|
|
292
|
-
|
|
546
|
+
try {
|
|
547
|
+
const i = y.getFetchErrorContext();
|
|
548
|
+
i && (t.extra = { ...t.extra ?? {}, fetch_errors: i });
|
|
549
|
+
} catch {
|
|
550
|
+
}
|
|
551
|
+
return t;
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Safe shallow merge that blocks prototype pollution.
|
|
555
|
+
* Skips __proto__, constructor, and prototype keys from the incoming object.
|
|
556
|
+
*/
|
|
557
|
+
static _safeMerge(e, t) {
|
|
558
|
+
if (!t || typeof t != "object" || Array.isArray(t)) return e;
|
|
559
|
+
const n = { ...e };
|
|
560
|
+
for (const [r, a] of Object.entries(t))
|
|
561
|
+
r === "__proto__" || r === "constructor" || r === "prototype" || (n[r] = a);
|
|
562
|
+
return n;
|
|
563
|
+
}
|
|
564
|
+
static async _fetchLinkedEntities(e, t, n, r, a) {
|
|
565
|
+
const i = r.entities.find(
|
|
566
|
+
(u) => u.name === t || u.entitySetName === t
|
|
567
|
+
);
|
|
568
|
+
if (!i) return {};
|
|
569
|
+
const o = i.properties.find((u) => u.isKey);
|
|
570
|
+
if (!o) return {};
|
|
571
|
+
const l = n[o.name];
|
|
572
|
+
if (l == null) return {};
|
|
573
|
+
const s = `${t}::${l}`, c = Date.now(), h = f._linkedEntityCache.get(s);
|
|
574
|
+
if (h && c - h.fetchedAt < f._LINKED_CACHE_TTL)
|
|
575
|
+
return h.data;
|
|
576
|
+
const p = {}, b = typeof l == "number" ? String(l) : `'${l}'`, d = y.findLinkedEntities(r, i.name);
|
|
577
|
+
await Promise.all(
|
|
578
|
+
d.map(async (u) => {
|
|
579
|
+
try {
|
|
580
|
+
const x = `${e}/${u.entitySetName}?$filter=${encodeURIComponent(`${u.foreignKey} eq ${b}`)}&$top=50`, { data: g, error: E, errorContext: $ } = await y.fetchEntityData(x, a);
|
|
581
|
+
if (E)
|
|
582
|
+
y.recordFetchError(u.entitySetName, x, E), $ && (p[`linked_${u.entitySetName}_error`] = $);
|
|
583
|
+
else if (g) {
|
|
584
|
+
const I = g.value ?? (Array.isArray(g) ? g : [g]);
|
|
585
|
+
I.length > 0 && (p[`linked_${u.entitySetName}`] = I);
|
|
586
|
+
}
|
|
587
|
+
} catch {
|
|
588
|
+
}
|
|
589
|
+
})
|
|
590
|
+
);
|
|
591
|
+
const v = i.entitySetName ?? i.name, w = i.navProperties.filter(
|
|
592
|
+
(u) => u.cardinality !== "*"
|
|
593
|
+
);
|
|
594
|
+
return await Promise.all(
|
|
595
|
+
w.map(async (u) => {
|
|
596
|
+
if (p[`linked_${u.name}`] == null)
|
|
597
|
+
try {
|
|
598
|
+
const x = `${e}/${v}(${b})/${u.name}`, { data: g, error: E } = await y.fetchEntityData(x, a);
|
|
599
|
+
if (!E && g) {
|
|
600
|
+
const $ = g.value ?? g;
|
|
601
|
+
$ != null && (p[`linked_${u.name}`] = $);
|
|
602
|
+
}
|
|
603
|
+
} catch {
|
|
604
|
+
}
|
|
605
|
+
})
|
|
606
|
+
), f._linkedEntityCache.set(s, {
|
|
607
|
+
data: p,
|
|
608
|
+
fetchedAt: c
|
|
609
|
+
}), p;
|
|
610
|
+
}
|
|
611
|
+
static async _registerMetadata(e, t, n, r, a) {
|
|
612
|
+
const i = `${t}::${n}`;
|
|
613
|
+
if (!f._registeredKeys.has(i)) {
|
|
614
|
+
f._registeredKeys.add(i);
|
|
615
|
+
try {
|
|
616
|
+
const o = { "Content-Type": "application/json" };
|
|
617
|
+
a && (o.Authorization = `Bearer ${a}`), await fetch(`${e}/api/apps/register-metadata`, {
|
|
618
|
+
method: "POST",
|
|
619
|
+
headers: o,
|
|
620
|
+
body: JSON.stringify({ app_id: t, service_url: n, raw_xml: r })
|
|
621
|
+
});
|
|
622
|
+
} catch {
|
|
623
|
+
f._registeredKeys.delete(i);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
293
626
|
}
|
|
294
627
|
static _discoverServiceUrl() {
|
|
295
628
|
try {
|
|
296
|
-
const
|
|
297
|
-
if (
|
|
629
|
+
const e = _.getServiceUrl();
|
|
630
|
+
if (e) return e;
|
|
298
631
|
} catch {
|
|
299
632
|
}
|
|
300
633
|
try {
|
|
301
|
-
const
|
|
634
|
+
const e = performance.getEntriesByType(
|
|
302
635
|
"resource"
|
|
303
636
|
);
|
|
304
|
-
for (const
|
|
305
|
-
const n =
|
|
637
|
+
for (const t of e) {
|
|
638
|
+
const n = t.name.match(/^(https?:\/\/[^?#]+)\/\$metadata/);
|
|
306
639
|
if (n) return n[1];
|
|
307
640
|
}
|
|
308
641
|
} catch {
|
|
309
642
|
}
|
|
310
643
|
try {
|
|
311
|
-
const
|
|
644
|
+
const e = window.location.href.match(
|
|
312
645
|
/(https?:\/\/[^/]+(?:\/odata\/v[24]\/[^/?#]+|\/sap\/opu\/odata\/[^/?#]+))/
|
|
313
646
|
);
|
|
314
|
-
if (
|
|
647
|
+
if (e) return e[1];
|
|
315
648
|
} catch {
|
|
316
649
|
}
|
|
317
650
|
return null;
|
|
318
651
|
}
|
|
319
652
|
static async discoverFromManifest() {
|
|
320
|
-
var
|
|
653
|
+
var e;
|
|
321
654
|
try {
|
|
322
|
-
const
|
|
323
|
-
if (!
|
|
324
|
-
const n = await
|
|
325
|
-
for (const
|
|
326
|
-
const i = r[
|
|
655
|
+
const t = await fetch("/manifest.json");
|
|
656
|
+
if (!t.ok) return null;
|
|
657
|
+
const n = await t.json(), r = ((e = n == null ? void 0 : n["sap.app"]) == null ? void 0 : e.dataSources) ?? {};
|
|
658
|
+
for (const a of Object.keys(r)) {
|
|
659
|
+
const i = r[a];
|
|
327
660
|
if ((i == null ? void 0 : i.type) === "ODataAnnotation") continue;
|
|
328
|
-
const
|
|
329
|
-
if (typeof
|
|
330
|
-
return new URL(
|
|
661
|
+
const o = i == null ? void 0 : i.uri;
|
|
662
|
+
if (typeof o == "string" && o.length > 0)
|
|
663
|
+
return new URL(o, window.location.href).toString().replace(/\/$/, "");
|
|
331
664
|
}
|
|
332
665
|
} catch {
|
|
333
666
|
}
|
|
334
667
|
return null;
|
|
335
668
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
669
|
+
/**
|
|
670
|
+
* Listen for postMessage events from the BTP Copilot iframe.
|
|
671
|
+
*
|
|
672
|
+
* @param allowedOrigin The iframe's origin (e.g. "https://your-chatbot.cfapps.eu10.hana.ondemand.com").
|
|
673
|
+
* Messages from any other origin are silently ignored.
|
|
674
|
+
* Pass "*" only in trusted, same-origin deployments.
|
|
675
|
+
*/
|
|
676
|
+
static listenForUpdates(e, t, n, r, a) {
|
|
677
|
+
const i = (o) => {
|
|
678
|
+
if (!o.data || typeof o.data != "object" || a && a !== "*" && o.origin !== a) return;
|
|
679
|
+
const { type: l, payload: s, text: c } = o.data;
|
|
680
|
+
switch (l) {
|
|
341
681
|
case "btp-copilot:set-context":
|
|
342
|
-
s &&
|
|
682
|
+
s && e(s);
|
|
343
683
|
break;
|
|
344
684
|
case "btp-copilot:send-message":
|
|
345
|
-
|
|
685
|
+
c && t(c);
|
|
346
686
|
break;
|
|
347
687
|
case "btp-copilot:open":
|
|
348
688
|
n();
|
|
@@ -352,10 +692,12 @@ ${a.content}`).join(`
|
|
|
352
692
|
break;
|
|
353
693
|
}
|
|
354
694
|
};
|
|
355
|
-
return window.addEventListener("message",
|
|
695
|
+
return window.addEventListener("message", i), () => window.removeEventListener("message", i);
|
|
356
696
|
}
|
|
357
|
-
}
|
|
358
|
-
|
|
697
|
+
};
|
|
698
|
+
f._registeredKeys = /* @__PURE__ */ new Set(), f._linkedEntityCache = /* @__PURE__ */ new Map(), f._LINKED_CACHE_TTL = 3e4;
|
|
699
|
+
let k = f;
|
|
700
|
+
const j = `/* ── Host element ─────────────────────────────────────────────────────────── */
|
|
359
701
|
:host {
|
|
360
702
|
position: fixed;
|
|
361
703
|
z-index: 2147483647;
|
|
@@ -482,35 +824,38 @@ const v = `/* ── Host element ───────────────
|
|
|
482
824
|
}
|
|
483
825
|
.chat-iframe { border-radius: 1rem 1rem 0 0; }
|
|
484
826
|
}
|
|
485
|
-
`,
|
|
827
|
+
`, O = [
|
|
486
828
|
"iframe-url",
|
|
487
829
|
"app-id",
|
|
488
830
|
"app-name",
|
|
489
831
|
"service-url",
|
|
832
|
+
"backend-url",
|
|
490
833
|
"auto-context",
|
|
491
834
|
"position",
|
|
492
835
|
"theme",
|
|
493
836
|
"token",
|
|
494
837
|
"context-interval"
|
|
495
|
-
],
|
|
838
|
+
], N = class N extends HTMLElement {
|
|
496
839
|
constructor() {
|
|
497
|
-
super(...arguments), this._open = !1, this._isFullscreen = !1, this._iframeEl = null, this._contextTimer = null;
|
|
840
|
+
super(...arguments), this._open = !1, this._isFullscreen = !1, this._iframeEl = null, this._contextTimer = null, this._manualRecord = null;
|
|
498
841
|
}
|
|
499
842
|
connectedCallback() {
|
|
500
843
|
this._config = this._readConfig(), this._buildShadow(), this._startContextPolling();
|
|
501
|
-
const
|
|
502
|
-
if (!
|
|
503
|
-
const
|
|
504
|
-
|
|
844
|
+
const e = (t) => {
|
|
845
|
+
if (!t.data || typeof t.data != "object") return;
|
|
846
|
+
const n = this._iframeOrigin();
|
|
847
|
+
if (n !== "*" && t.origin !== n) return;
|
|
848
|
+
const { type: r } = t.data;
|
|
849
|
+
r === "btp-copilot:close" && this.close(), r === "btp-copilot:open" && this.open(), r === "btp-copilot:fullscreen" && this._toggleFullscreen(), r === "btp-copilot:minimize" && this.close();
|
|
505
850
|
};
|
|
506
|
-
window.addEventListener("message",
|
|
851
|
+
window.addEventListener("message", e), this._removeMessageListener = () => window.removeEventListener("message", e);
|
|
507
852
|
}
|
|
508
853
|
disconnectedCallback() {
|
|
509
|
-
var
|
|
510
|
-
(
|
|
854
|
+
var e;
|
|
855
|
+
(e = this._removeMessageListener) == null || e.call(this), this._stopContextPolling();
|
|
511
856
|
}
|
|
512
|
-
attributeChangedCallback(
|
|
513
|
-
|
|
857
|
+
attributeChangedCallback(e, t, n) {
|
|
858
|
+
t === n || !this.isConnected || (this._config = this._readConfig(), this._updateAfterAttributeChange());
|
|
514
859
|
}
|
|
515
860
|
// ── Public API ─────────────────────────────────────────────────────────────
|
|
516
861
|
open() {
|
|
@@ -518,43 +863,74 @@ const v = `/* ── Host element ───────────────
|
|
|
518
863
|
}
|
|
519
864
|
close() {
|
|
520
865
|
this._open = !1, this._isFullscreen = !1;
|
|
521
|
-
const
|
|
522
|
-
|
|
523
|
-
const
|
|
524
|
-
|
|
866
|
+
const e = this.shadowRoot.querySelector(".panel");
|
|
867
|
+
e.classList.add("closed"), e.classList.remove("fullscreen");
|
|
868
|
+
const t = this.shadowRoot.querySelector(".toggle-btn");
|
|
869
|
+
t.style.display = "", t.setAttribute("aria-expanded", "false");
|
|
525
870
|
}
|
|
526
871
|
_toggleFullscreen() {
|
|
527
872
|
this._isFullscreen = !this._isFullscreen;
|
|
528
|
-
const
|
|
529
|
-
this._isFullscreen ? (
|
|
873
|
+
const e = this.shadowRoot.querySelector(".panel"), t = this.shadowRoot.querySelector(".toggle-btn");
|
|
874
|
+
this._isFullscreen ? (e.classList.add("fullscreen"), t.style.display = "none") : (e.classList.remove("fullscreen"), t.style.display = "");
|
|
530
875
|
}
|
|
531
|
-
sendMessage(
|
|
876
|
+
sendMessage(e) {
|
|
532
877
|
this._open || this.open(), setTimeout(() => {
|
|
533
|
-
var
|
|
534
|
-
(n = (
|
|
535
|
-
{ type: "btp-copilot:send-message", text:
|
|
536
|
-
|
|
878
|
+
var t, n;
|
|
879
|
+
(n = (t = this._iframeEl) == null ? void 0 : t.contentWindow) == null || n.postMessage(
|
|
880
|
+
{ type: "btp-copilot:send-message", text: e },
|
|
881
|
+
this._iframeOrigin()
|
|
537
882
|
);
|
|
538
883
|
}, 100);
|
|
539
884
|
}
|
|
885
|
+
/**
|
|
886
|
+
* Push the currently-viewed record into the chatbot context.
|
|
887
|
+
*
|
|
888
|
+
* Call this from your Fiori controller or CAP handler after a record is loaded.
|
|
889
|
+
* The chatbot will have all field values and can answer questions like
|
|
890
|
+
* "what is the applicator?" or "what status is this order?" immediately.
|
|
891
|
+
*
|
|
892
|
+
* @param entityName The OData entity set name, e.g. "FertilizerBlend"
|
|
893
|
+
* @param data Plain object of field → value pairs (the record)
|
|
894
|
+
*
|
|
895
|
+
* @example // Fiori UI5 controller (1 line):
|
|
896
|
+
* document.querySelector('btp-copilot')
|
|
897
|
+
* .setRecord('FertilizerBlend', this.getView().getBindingContext().getObject());
|
|
898
|
+
*/
|
|
899
|
+
setRecord(e, t) {
|
|
900
|
+
this._manualRecord = { _entity: e, ...t }, this._pushContextToIframe();
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Push arbitrary key/value pairs into the chatbot context (without an entity name).
|
|
904
|
+
* Useful for custom metadata, computed values, or non-OData context.
|
|
905
|
+
*
|
|
906
|
+
* @example
|
|
907
|
+
* widget.pushData({ currentUser: 'John', selectedRegion: 'EU' });
|
|
908
|
+
*/
|
|
909
|
+
pushData(e) {
|
|
910
|
+
this._manualRecord = { ...this._manualRecord, ...e }, this._pushContextToIframe();
|
|
911
|
+
}
|
|
912
|
+
/** Remove the manually-set record so auto-context takes over again. */
|
|
913
|
+
clearRecord() {
|
|
914
|
+
this._manualRecord = null, this._pushContextToIframe();
|
|
915
|
+
}
|
|
540
916
|
// ── Shadow DOM ─────────────────────────────────────────────────────────────
|
|
541
917
|
_buildShadow() {
|
|
542
918
|
var r;
|
|
543
|
-
const
|
|
544
|
-
|
|
919
|
+
const e = this.attachShadow({ mode: "open" }), t = document.createElement("style");
|
|
920
|
+
t.textContent = j, e.appendChild(t);
|
|
545
921
|
const n = document.createElement("div");
|
|
546
|
-
for (n.innerHTML = this._template(); n.firstChild; )
|
|
547
|
-
|
|
922
|
+
for (n.innerHTML = this._template(); n.firstChild; ) e.appendChild(n.firstChild);
|
|
923
|
+
e.querySelector(".toggle-btn").addEventListener("click", () => {
|
|
548
924
|
this._open ? this.close() : this.open();
|
|
549
|
-
}), this._iframeEl =
|
|
925
|
+
}), this._iframeEl = e.querySelector(".chat-iframe"), (r = this._iframeEl) == null || r.addEventListener("load", () => {
|
|
550
926
|
this._pushAuthToIframe(), this._pushContextToIframe();
|
|
551
927
|
});
|
|
552
928
|
}
|
|
553
929
|
_template() {
|
|
554
930
|
var n;
|
|
555
|
-
const
|
|
931
|
+
const e = ((n = this._config) == null ? void 0 : n.position) ?? "bottom-right", t = this._esc(this._buildIframeUrl());
|
|
556
932
|
return `
|
|
557
|
-
<button class="toggle-btn ${
|
|
933
|
+
<button class="toggle-btn ${e}" aria-label="Open AI assistant" aria-expanded="false" aria-haspopup="dialog">
|
|
558
934
|
<svg class="icon-chat" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
559
935
|
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
|
|
560
936
|
</svg>
|
|
@@ -564,10 +940,10 @@ const v = `/* ── Host element ───────────────
|
|
|
564
940
|
<span class="badge" aria-hidden="true"></span>
|
|
565
941
|
</button>
|
|
566
942
|
|
|
567
|
-
<div class="panel closed ${
|
|
943
|
+
<div class="panel closed ${e}" role="dialog" aria-label="AI Assistant" aria-modal="true">
|
|
568
944
|
<iframe
|
|
569
945
|
class="chat-iframe"
|
|
570
|
-
src="${
|
|
946
|
+
src="${t}"
|
|
571
947
|
title="AI Assistant"
|
|
572
948
|
allow="clipboard-read; clipboard-write"
|
|
573
949
|
loading="lazy">
|
|
@@ -576,27 +952,30 @@ const v = `/* ── Host element ───────────────
|
|
|
576
952
|
}
|
|
577
953
|
// ── Context + Auth ─────────────────────────────────────────────────────────
|
|
578
954
|
_buildBaseContext() {
|
|
579
|
-
const
|
|
955
|
+
const e = S.resolve(this._config.token), t = this._config.serviceUrl ?? "", n = t ? t.startsWith("http") ? t : `${window.location.origin}${t}` : void 0;
|
|
580
956
|
return {
|
|
581
957
|
app_id: this._config.appId,
|
|
582
958
|
app_name: this._config.appName,
|
|
583
|
-
service_url:
|
|
959
|
+
service_url: n,
|
|
960
|
+
// Manually set record (via setRecord / pushData) — overrides auto-context entity_data
|
|
961
|
+
...this._manualRecord ? { entity_data: this._manualRecord } : {},
|
|
584
962
|
// Include the resolved auth token in extra so the backend can
|
|
585
963
|
// proxy OData calls on behalf of the user to fetch real data
|
|
586
|
-
|
|
964
|
+
extra: {
|
|
965
|
+
...e ? { auth_token: e } : {},
|
|
966
|
+
...this._config.backendUrl ? { backend_url: this._config.backendUrl } : {}
|
|
967
|
+
}
|
|
587
968
|
};
|
|
588
969
|
}
|
|
589
970
|
_buildIframeUrl() {
|
|
590
|
-
var
|
|
591
|
-
const
|
|
592
|
-
if (!
|
|
971
|
+
var t;
|
|
972
|
+
const e = ((t = this._config) == null ? void 0 : t.iframeUrl) ?? "";
|
|
973
|
+
if (!e) return "";
|
|
593
974
|
try {
|
|
594
|
-
const n = new URL(
|
|
595
|
-
r && n.searchParams.set("appId", r),
|
|
596
|
-
const a = d.resolve(this._config.token);
|
|
597
|
-
return a && n.searchParams.set("token", a), n.toString();
|
|
975
|
+
const n = new URL(e), { appId: r, appName: a, serviceUrl: i } = this._config;
|
|
976
|
+
return r && n.searchParams.set("appId", r), a && n.searchParams.set("appName", a), i && n.searchParams.set("serviceUrl", i), n.toString();
|
|
598
977
|
} catch {
|
|
599
|
-
return
|
|
978
|
+
return e;
|
|
600
979
|
}
|
|
601
980
|
}
|
|
602
981
|
/** Derive the allowed postMessage target origin from the configured iframe URL.
|
|
@@ -609,31 +988,31 @@ const v = `/* ── Host element ───────────────
|
|
|
609
988
|
}
|
|
610
989
|
}
|
|
611
990
|
_pushContextToIframe() {
|
|
612
|
-
var
|
|
613
|
-
if (!((
|
|
614
|
-
const
|
|
615
|
-
this._config.autoContext ?
|
|
616
|
-
var r,
|
|
617
|
-
(
|
|
991
|
+
var t;
|
|
992
|
+
if (!((t = this._iframeEl) != null && t.contentWindow)) return;
|
|
993
|
+
const e = this._iframeOrigin();
|
|
994
|
+
this._config.autoContext ? k.captureAsync(this._buildBaseContext()).then((n) => {
|
|
995
|
+
var r, a;
|
|
996
|
+
(a = (r = this._iframeEl) == null ? void 0 : r.contentWindow) == null || a.postMessage(
|
|
618
997
|
{ type: "btp-copilot:set-context", payload: n },
|
|
619
|
-
|
|
998
|
+
e
|
|
620
999
|
), n.service_url && !this._config.serviceUrl && (this._config = { ...this._config, serviceUrl: n.service_url });
|
|
621
1000
|
}).catch(() => {
|
|
622
1001
|
var n, r;
|
|
623
1002
|
(r = (n = this._iframeEl) == null ? void 0 : n.contentWindow) == null || r.postMessage(
|
|
624
1003
|
{ type: "btp-copilot:set-context", payload: this._buildBaseContext() },
|
|
625
|
-
|
|
1004
|
+
e
|
|
626
1005
|
);
|
|
627
1006
|
}) : this._iframeEl.contentWindow.postMessage(
|
|
628
1007
|
{ type: "btp-copilot:set-context", payload: this._buildBaseContext() },
|
|
629
|
-
|
|
1008
|
+
e
|
|
630
1009
|
);
|
|
631
1010
|
}
|
|
632
1011
|
_pushAuthToIframe() {
|
|
633
|
-
var
|
|
634
|
-
const
|
|
635
|
-
!
|
|
636
|
-
{ type: "btp-copilot:auth", token:
|
|
1012
|
+
var t;
|
|
1013
|
+
const e = S.resolve(this._config.token);
|
|
1014
|
+
!e || !((t = this._iframeEl) != null && t.contentWindow) || this._iframeEl.contentWindow.postMessage(
|
|
1015
|
+
{ type: "btp-copilot:auth", token: e },
|
|
637
1016
|
this._iframeOrigin()
|
|
638
1017
|
);
|
|
639
1018
|
}
|
|
@@ -647,39 +1026,40 @@ const v = `/* ── Host element ───────────────
|
|
|
647
1026
|
}
|
|
648
1027
|
// ── Config ─────────────────────────────────────────────────────────────────
|
|
649
1028
|
_readConfig() {
|
|
650
|
-
const
|
|
1029
|
+
const e = (t) => this.getAttribute(t);
|
|
651
1030
|
return {
|
|
652
|
-
iframeUrl:
|
|
653
|
-
appId:
|
|
654
|
-
appName:
|
|
655
|
-
serviceUrl:
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
1031
|
+
iframeUrl: e("iframe-url") ?? "",
|
|
1032
|
+
appId: e("app-id") ?? "default",
|
|
1033
|
+
appName: e("app-name") ?? void 0,
|
|
1034
|
+
serviceUrl: e("service-url") ?? void 0,
|
|
1035
|
+
backendUrl: e("backend-url") ?? void 0,
|
|
1036
|
+
autoContext: e("auto-context") !== "false",
|
|
1037
|
+
position: e("position") ?? "bottom-right",
|
|
1038
|
+
theme: e("theme") ?? "auto",
|
|
1039
|
+
token: e("token") ?? void 0,
|
|
1040
|
+
contextInterval: Math.max(500, Number(e("context-interval")) || 3e3)
|
|
661
1041
|
};
|
|
662
1042
|
}
|
|
663
1043
|
_updateAfterAttributeChange() {
|
|
664
1044
|
var n;
|
|
665
|
-
const
|
|
666
|
-
|
|
667
|
-
const
|
|
668
|
-
this._iframeEl && this._iframeEl.src !==
|
|
1045
|
+
const e = (n = this.shadowRoot) == null ? void 0 : n.querySelector("style");
|
|
1046
|
+
e && (e.textContent = j);
|
|
1047
|
+
const t = this._buildIframeUrl();
|
|
1048
|
+
this._iframeEl && this._iframeEl.src !== t && t && (this._iframeEl.src = t), this._stopContextPolling(), this._startContextPolling();
|
|
669
1049
|
}
|
|
670
1050
|
_clearBadge() {
|
|
671
|
-
var
|
|
672
|
-
(
|
|
1051
|
+
var e, t;
|
|
1052
|
+
(t = (e = this.shadowRoot) == null ? void 0 : e.querySelector(".badge")) == null || t.classList.remove("visible");
|
|
673
1053
|
}
|
|
674
|
-
_esc(
|
|
675
|
-
return
|
|
1054
|
+
_esc(e) {
|
|
1055
|
+
return e.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
676
1056
|
}
|
|
677
1057
|
};
|
|
678
|
-
|
|
679
|
-
let
|
|
680
|
-
customElements.get("btp-copilot") || customElements.define("btp-copilot",
|
|
1058
|
+
N.observedAttributes = [...O];
|
|
1059
|
+
let T = N;
|
|
1060
|
+
customElements.get("btp-copilot") || customElements.define("btp-copilot", T);
|
|
681
1061
|
export {
|
|
682
|
-
|
|
683
|
-
|
|
1062
|
+
T as BtpCopilotElement,
|
|
1063
|
+
k as ContextBridge
|
|
684
1064
|
};
|
|
685
1065
|
//# sourceMappingURL=btp-copilot.esm.js.map
|