@burdenoff/microfe-coolandlovely 2026.710.1 → 2026.710.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/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/services/createSandboxAssistantTransport.js +338 -0
- package/dist/services/createSandboxAssistantTransport.js.map +1 -0
- package/dist/utils/pageContext.js +51 -0
- package/dist/utils/pageContext.js.map +1 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,cAAc,iBAAiB,CAAC;AAChC,OAAO,EAAE,4BAA4B,EAAE,MAAM,4CAA4C,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -4,4 +4,5 @@ import { formatDate as r, formatNumber as i, formatPrice as a, formatRelativeTim
|
|
|
4
4
|
import { CoolAndLovelyRoot as c } from "./coolandlovely/CoolAndLovelyRoot.js";
|
|
5
5
|
import { useCoolAndLovelyStore as l } from "./coolandlovely/store/coolandlovelyStore.js";
|
|
6
6
|
import "./coolandlovely/index.js";
|
|
7
|
-
|
|
7
|
+
import { useSandboxAssistantTransport as u } from "./services/createSandboxAssistantTransport.js";
|
|
8
|
+
export { e as CoolAndLovelyProvider, c as CoolAndLovelyRoot, n as cn, r as formatDate, i as formatNumber, a as formatPrice, o as formatRelativeTime, s as titleCase, t as useCoolAndLovely, l as useCoolAndLovelyStore, u as useSandboxAssistantTransport };
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { gatherPageContext as e } from "../utils/pageContext.js";
|
|
2
|
+
import { useCallback as t, useMemo as n, useRef as r } from "react";
|
|
3
|
+
import { useGatewayClient as i } from "@burdenoff/fe-libs/shared/graphql";
|
|
4
|
+
import { useAuthToken as a } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
5
|
+
import { gql as o } from "@apollo/client";
|
|
6
|
+
//#region src/services/createSandboxAssistantTransport.ts
|
|
7
|
+
var s = o`
|
|
8
|
+
mutation CreateSandbox($input: CreateSandboxInput!) {
|
|
9
|
+
createSandbox(input: $input) {
|
|
10
|
+
id
|
|
11
|
+
name
|
|
12
|
+
status
|
|
13
|
+
network {
|
|
14
|
+
internalUrl
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
`, c = o`
|
|
19
|
+
query GetSandbox($id: ID!, $context: ContextInput!) {
|
|
20
|
+
getSandbox(id: $id, context: $context) {
|
|
21
|
+
id
|
|
22
|
+
status
|
|
23
|
+
errorMessage
|
|
24
|
+
network {
|
|
25
|
+
internalUrl
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
`, l = o`
|
|
30
|
+
query GetActiveSandboxes($context: ContextInput!) {
|
|
31
|
+
getActiveSandboxes(context: $context) {
|
|
32
|
+
id
|
|
33
|
+
name
|
|
34
|
+
status
|
|
35
|
+
metadata
|
|
36
|
+
template
|
|
37
|
+
createdBy
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
`, u = o`
|
|
41
|
+
mutation ProxySandboxRequest($input: ProxySandboxRequestInput!) {
|
|
42
|
+
proxySandboxRequest(input: $input) {
|
|
43
|
+
status
|
|
44
|
+
body
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
`, d = o`
|
|
48
|
+
mutation ExtendSandboxTtl($id: ID!, $additionalSeconds: Int!, $context: ContextInput!) {
|
|
49
|
+
extendSandboxTTL(id: $id, additionalSeconds: $additionalSeconds, context: $context) {
|
|
50
|
+
id
|
|
51
|
+
expiresAt
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
`, f = "assistant", p = 18e4, m = 1500, h = 3e4, g = "burdenoffregistry.azurecr.io", _ = "alpha-delegated-auth-v11", v = "sandbox-app-client-headers-v1";
|
|
55
|
+
function y(e) {
|
|
56
|
+
try {
|
|
57
|
+
let t = e === "workspaceId" ? "burdenoff-active-context-workspace" : "burdenoff-active-context-organization", n = localStorage.getItem(t);
|
|
58
|
+
if (n) return n;
|
|
59
|
+
let r = sessionStorage.getItem("bf-active-profile");
|
|
60
|
+
if (!r) return "";
|
|
61
|
+
let i = localStorage.getItem(`bf-p-${r}-context`);
|
|
62
|
+
return i ? JSON.parse(i)[e] ?? "" : "";
|
|
63
|
+
} catch {
|
|
64
|
+
return "";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function b() {
|
|
68
|
+
return new URLSearchParams(window.location.search).get("workspace") ?? y("workspaceId");
|
|
69
|
+
}
|
|
70
|
+
function x() {
|
|
71
|
+
return new URLSearchParams(window.location.search).get("org") ?? y("organizationId");
|
|
72
|
+
}
|
|
73
|
+
function S(e, t) {
|
|
74
|
+
let n = {};
|
|
75
|
+
return t?.accessToken && (n.authorization = `Bearer ${t.accessToken}`), t?.workspaceToken && (n["x-workspace-authorization"] = `Bearer ${t.workspaceToken}`), e && (n["x-workspace-id"] = e), t?.userId && (n["x-actor-id"] = t.userId, n["x-actor-type"] = "user"), t?.organizationId && (n["x-org-id"] = t.organizationId), Object.keys(n).length > 0 ? { headers: n } : void 0;
|
|
76
|
+
}
|
|
77
|
+
function C(e, t) {
|
|
78
|
+
return {
|
|
79
|
+
workspaceId: e,
|
|
80
|
+
...t?.organizationId ? { custom: { organizationId: t.organizationId } } : {}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
async function w(e) {
|
|
84
|
+
let t = await fetch("/workspaces/graphql", {
|
|
85
|
+
method: "POST",
|
|
86
|
+
headers: {
|
|
87
|
+
"Content-Type": "application/json",
|
|
88
|
+
...S(e.workspaceId, e.authContext)?.headers ?? {}
|
|
89
|
+
},
|
|
90
|
+
body: JSON.stringify({
|
|
91
|
+
query: e.document.loc?.source.body ?? "",
|
|
92
|
+
variables: e.variables
|
|
93
|
+
})
|
|
94
|
+
}), n = await t.json().catch(() => null);
|
|
95
|
+
if (!t.ok) {
|
|
96
|
+
let e = n?.errors?.map((e) => e.message).join(", "), r = Error(e || `Sandbox GraphQL HTTP ${t.status}`);
|
|
97
|
+
throw r.statusCode = t.status, r.result = n ?? void 0, r;
|
|
98
|
+
}
|
|
99
|
+
if (!n) throw Error("Sandbox GraphQL returned empty response");
|
|
100
|
+
if (n.errors?.length) throw Error(n.errors.map((e) => e.message).join(", "));
|
|
101
|
+
if (!n.data) throw Error("Sandbox GraphQL returned no data");
|
|
102
|
+
return n.data;
|
|
103
|
+
}
|
|
104
|
+
async function T(e, t) {
|
|
105
|
+
return ((await w({
|
|
106
|
+
document: l,
|
|
107
|
+
variables: { context: C(e, t) },
|
|
108
|
+
workspaceId: e,
|
|
109
|
+
authContext: t
|
|
110
|
+
})).getActiveSandboxes ?? []).find((e) => (e.status === "RUNNING" || e.status === "PROVISIONING" || e.status === "PENDING") && e.template === "AI_BUILDER" && e.metadata?.assistantMode === f && (!t?.userId || e.createdBy === t.userId) && e.metadata?.authStrategy === v && e.metadata?.imageTag === _)?.id ?? null;
|
|
111
|
+
}
|
|
112
|
+
async function E(e, t) {
|
|
113
|
+
let n = await w({
|
|
114
|
+
document: s,
|
|
115
|
+
variables: { input: {
|
|
116
|
+
name: `ai-assistant-${f}-${Date.now()}`,
|
|
117
|
+
description: `AI Assistant sandbox (${f} mode)`,
|
|
118
|
+
template: "AI_BUILDER",
|
|
119
|
+
mode: "DEPLOYMENT",
|
|
120
|
+
containers: [{
|
|
121
|
+
name: "ai-assistant",
|
|
122
|
+
image: `${g}/wspace/ai-assistant-api-calls:${_}`,
|
|
123
|
+
ports: [{
|
|
124
|
+
containerPort: 8080,
|
|
125
|
+
name: "http"
|
|
126
|
+
}],
|
|
127
|
+
env: [
|
|
128
|
+
{
|
|
129
|
+
name: "AI_ASSISTANT_MODE",
|
|
130
|
+
value: f
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: "HOSTNAME",
|
|
134
|
+
value: "0.0.0.0"
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: "WORKSPACE_ID",
|
|
138
|
+
value: e
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: "AI_ASSISTANT_PRODUCT",
|
|
142
|
+
value: "coolandlovely"
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
}],
|
|
146
|
+
resources: {
|
|
147
|
+
cpuRequest: "150m",
|
|
148
|
+
cpuLimit: "750m",
|
|
149
|
+
memoryRequest: "384Mi",
|
|
150
|
+
memoryLimit: "1536Mi"
|
|
151
|
+
},
|
|
152
|
+
ttlSeconds: 600,
|
|
153
|
+
metadata: {
|
|
154
|
+
assistantMode: f,
|
|
155
|
+
assistantOwnerUserId: t?.userId ?? null,
|
|
156
|
+
assistantOrganizationId: t?.organizationId ?? null,
|
|
157
|
+
authStrategy: v,
|
|
158
|
+
imageTag: _
|
|
159
|
+
},
|
|
160
|
+
context: C(e, t)
|
|
161
|
+
} },
|
|
162
|
+
workspaceId: e,
|
|
163
|
+
authContext: t
|
|
164
|
+
});
|
|
165
|
+
if (!n?.createSandbox?.id) throw Error("createSandbox returned empty response");
|
|
166
|
+
return n.createSandbox.id;
|
|
167
|
+
}
|
|
168
|
+
async function D(e, t, n, r = 3e5) {
|
|
169
|
+
let i = Date.now();
|
|
170
|
+
for (; Date.now() - i < r;) {
|
|
171
|
+
let r = await w({
|
|
172
|
+
document: c,
|
|
173
|
+
variables: {
|
|
174
|
+
id: e,
|
|
175
|
+
context: C(t, n)
|
|
176
|
+
},
|
|
177
|
+
workspaceId: t,
|
|
178
|
+
authContext: n
|
|
179
|
+
}), i = r?.getSandbox?.status, a = r?.getSandbox?.errorMessage;
|
|
180
|
+
if (i === "RUNNING") return;
|
|
181
|
+
if (i === "FAILED" || i === "STOPPED" || i === "EXPIRED") throw Error(a ? `Sandbox ${i.toLowerCase()}: ${a}` : `Sandbox ${i.toLowerCase()}`);
|
|
182
|
+
await new Promise((e) => setTimeout(e, 2e3));
|
|
183
|
+
}
|
|
184
|
+
throw Error("Sandbox startup timed out");
|
|
185
|
+
}
|
|
186
|
+
async function O(e, t, n, r, i, a) {
|
|
187
|
+
let o = (await w({
|
|
188
|
+
document: u,
|
|
189
|
+
variables: { input: {
|
|
190
|
+
sandboxId: e,
|
|
191
|
+
path: n,
|
|
192
|
+
method: r,
|
|
193
|
+
body: i,
|
|
194
|
+
context: C(t, a)
|
|
195
|
+
} },
|
|
196
|
+
workspaceId: t,
|
|
197
|
+
authContext: a
|
|
198
|
+
})).proxySandboxRequest;
|
|
199
|
+
if (!o || o.status >= 400) {
|
|
200
|
+
let e = o?.body;
|
|
201
|
+
throw Error(e?.error ?? `Proxy error: ${o?.status ?? "unknown"}`);
|
|
202
|
+
}
|
|
203
|
+
return o.body;
|
|
204
|
+
}
|
|
205
|
+
async function k(e, t, n, r = 9e4) {
|
|
206
|
+
let i = Date.now();
|
|
207
|
+
for (; Date.now() - i < r;) {
|
|
208
|
+
try {
|
|
209
|
+
await O(e, t, "/health", "GET", void 0, n);
|
|
210
|
+
return;
|
|
211
|
+
} catch (e) {
|
|
212
|
+
let t = e instanceof Error ? e.message : String(e);
|
|
213
|
+
if (!/proxy error: 502|proxy error: 503|proxy error: 404|service not available/i.test(t)) throw e;
|
|
214
|
+
}
|
|
215
|
+
await new Promise((e) => setTimeout(e, 2e3));
|
|
216
|
+
}
|
|
217
|
+
throw Error("Assistant service did not become healthy before timeout");
|
|
218
|
+
}
|
|
219
|
+
async function A(e, t, n) {
|
|
220
|
+
return { sessionId: (await O(e, t, "/sessions", "POST", { mode: f }, n)).session.id };
|
|
221
|
+
}
|
|
222
|
+
async function j(t, n, r, i, a) {
|
|
223
|
+
await O(t, n, `/sessions/${r}/prompt-async`, "POST", {
|
|
224
|
+
mode: f,
|
|
225
|
+
prompt: i,
|
|
226
|
+
pageContext: e()
|
|
227
|
+
}, a);
|
|
228
|
+
}
|
|
229
|
+
async function M(e, t, n, r, i = 50) {
|
|
230
|
+
return (await O(e, t, `/sessions/${n}/messages?limit=${i}`, "GET", void 0, r)).messages ?? [];
|
|
231
|
+
}
|
|
232
|
+
async function N(e, t, n = 600, r) {
|
|
233
|
+
await w({
|
|
234
|
+
document: d,
|
|
235
|
+
variables: {
|
|
236
|
+
id: e,
|
|
237
|
+
additionalSeconds: n,
|
|
238
|
+
context: C(t, r)
|
|
239
|
+
},
|
|
240
|
+
workspaceId: t,
|
|
241
|
+
authContext: r
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
function P(e) {
|
|
245
|
+
return e.info?.time?.created ?? 0;
|
|
246
|
+
}
|
|
247
|
+
function F(e) {
|
|
248
|
+
return (e ?? []).filter((e) => e.type === "text" && typeof e.text == "string").map((e) => e.text?.trim() ?? "").filter(Boolean).join("\n");
|
|
249
|
+
}
|
|
250
|
+
function I(e) {
|
|
251
|
+
return (e ?? []).filter((e) => e.type === "tool" && e.tool).map((e) => {
|
|
252
|
+
let t = e.state?.status ?? "running", n = e.tool ?? "tool";
|
|
253
|
+
return t === "completed" ? `Completed: ${n}` : t === "failed" ? `Failed: ${n}` : `Running: ${n}`;
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
function L(e, t) {
|
|
257
|
+
let n = e.filter((e) => e.info?.role === "assistant" && P(e) >= t).sort((e, t) => P(e) - P(t));
|
|
258
|
+
if (n.length === 0) return {
|
|
259
|
+
content: "Thinking…",
|
|
260
|
+
done: !1
|
|
261
|
+
};
|
|
262
|
+
let r = n[n.length - 1], i = F(r.parts), a = I(r.parts);
|
|
263
|
+
return {
|
|
264
|
+
content: [i, ...a].filter(Boolean).join("\n\n") || "Thinking…",
|
|
265
|
+
done: !!r.info?.time?.completed && (!!i || a.length > 0)
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
function R(e) {
|
|
269
|
+
return e.replace(/(Authorization\s*:\s*Bearer\s+)[^\s\n]+/gi, "$1[REDACTED]").replace(/(X-Workspace-Authorization\s*:\s*Bearer\s+)[^\s\n]+/gi, "$1[REDACTED]").replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9._-]+\.[A-Za-z0-9._-]+\b/g, "[REDACTED_JWT]").replace(/\bsk-ant-[A-Za-z0-9-]+\b/g, "[REDACTED_API_KEY]");
|
|
270
|
+
}
|
|
271
|
+
function z(e) {
|
|
272
|
+
let t = e instanceof Error ? e.message.toLowerCase() : String(e).toLowerCase();
|
|
273
|
+
return t.includes("rate limit exceeded") || t.includes("unauthorized") || t.includes("k8s api error 401") ? !1 : [
|
|
274
|
+
"sandbox not found",
|
|
275
|
+
"sandbox is not running",
|
|
276
|
+
"sandbox service not available yet",
|
|
277
|
+
"sandbox failed",
|
|
278
|
+
"sandbox startup timed out",
|
|
279
|
+
"assistant service did not become healthy",
|
|
280
|
+
"proxy error: 404",
|
|
281
|
+
"proxy error: 502",
|
|
282
|
+
"proxy error: 503"
|
|
283
|
+
].some((e) => t.includes(e));
|
|
284
|
+
}
|
|
285
|
+
function B() {
|
|
286
|
+
i("workspace");
|
|
287
|
+
let { getAccessToken: e, getWorkspaceToken: o, userId: s } = a(), c = r(null), l = r(null), u = t(async (t, n) => {
|
|
288
|
+
let r = c.current;
|
|
289
|
+
if (!r) {
|
|
290
|
+
if (r = await T(t, n), !r) {
|
|
291
|
+
if (!e()) throw Error("API Calls mode requires an authenticated session. Please sign in again.");
|
|
292
|
+
r = await E(t, n);
|
|
293
|
+
}
|
|
294
|
+
await D(r, t, n), await k(r, t, n), c.current = r;
|
|
295
|
+
}
|
|
296
|
+
let i = l.current;
|
|
297
|
+
return i || (i = (await A(r, t, n)).sessionId, l.current = i), {
|
|
298
|
+
sandboxId: r,
|
|
299
|
+
sessionId: i
|
|
300
|
+
};
|
|
301
|
+
}, [e]), d = t(async ({ prompt: t, onProgress: n, signal: r }) => {
|
|
302
|
+
let i = b();
|
|
303
|
+
if (!i) throw Error("The assistant needs an active workspace. Open a workspace and try again.");
|
|
304
|
+
let a = {
|
|
305
|
+
accessToken: e(),
|
|
306
|
+
workspaceToken: o(),
|
|
307
|
+
userId: s,
|
|
308
|
+
organizationId: x()
|
|
309
|
+
}, d = async (e) => {
|
|
310
|
+
try {
|
|
311
|
+
let { sandboxId: e, sessionId: o } = await u(i, a), s = Date.now();
|
|
312
|
+
await j(e, i, o, t, a);
|
|
313
|
+
let c = Date.now() + p, l = Date.now(), d = L([], s);
|
|
314
|
+
for (; Date.now() < c;) {
|
|
315
|
+
if (r.aborted) throw Error("Cancelled");
|
|
316
|
+
if (d = L(await M(e, i, o, a, 50), s), n(R(d.content)), d.done) break;
|
|
317
|
+
Date.now() - l > h && (await N(e, i, 600, a).catch(() => void 0), l = Date.now()), await new Promise((e) => setTimeout(e, m));
|
|
318
|
+
}
|
|
319
|
+
if (!d.done) throw Error("The assistant timed out while responding. Please try again.");
|
|
320
|
+
return { text: R(d.content) };
|
|
321
|
+
} catch (t) {
|
|
322
|
+
if (e === 1 && z(t)) return c.current = null, l.current = null, d(2);
|
|
323
|
+
throw t;
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
return d(1);
|
|
327
|
+
}, [
|
|
328
|
+
u,
|
|
329
|
+
e,
|
|
330
|
+
o,
|
|
331
|
+
s
|
|
332
|
+
]);
|
|
333
|
+
return n(() => ({ sendPrompt: d }), [d]);
|
|
334
|
+
}
|
|
335
|
+
//#endregion
|
|
336
|
+
export { B as useSandboxAssistantTransport };
|
|
337
|
+
|
|
338
|
+
//# sourceMappingURL=createSandboxAssistantTransport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createSandboxAssistantTransport.js","names":[],"sources":["../../src/services/createSandboxAssistantTransport.ts"],"sourcesContent":["/**\n * CoolAndLovely adapter for the shared fe-libs AssistantWidget.\n *\n * The floating widget (fe-libs, Layer 1) is backend-agnostic — it calls an\n * injected `AssistantTransport`. This hook builds a transport that reuses the\n * sandbox `api-calls` agent: it provisions/reuses a sandbox, opens a session,\n * dispatches the prompt async, then polls for the streamed answer. The agent\n * introspects the GraphQL schema and performs API calls on the user's behalf\n * using their workspace token, contextual to the current screen (via\n * `gatherPageContext`).\n *\n * This file is intentionally self-contained so the MFE can ship its transport\n * without depending on generated GraphQL operation files. All GraphQL\n * documents are declared inline with `gql` and executed through the workspace\n * Apollo client returned by `useGatewayClient('workspace')`.\n */\n\nimport { useCallback, useMemo, useRef } from 'react';\nimport { useGatewayClient } from '@burdenoff/fe-libs/shared/graphql';\nimport { useAuthToken } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { gql } from '@apollo/client';\nimport { gatherPageContext } from '@/utils/pageContext';\n\n/**\n * Locally-defined mirror of the fe-libs `AssistantTransport` contract.\n *\n * We intentionally do NOT import the type from `@burdenoff/fe-libs` here:\n * microfe's tsconfig maps `@burdenoff/fe-libs/*` to fe-libs *source*, so\n * vite-plugin-dts would rewrite a cross-package type used in this hook's\n * public signature to a broken source-relative path in the emitted `.d.ts`.\n * Structural typing makes this shape assignable to fe-libs' `AssistantTransport`\n * at the call site (coolandlovely-app's AppShell), which is where compatibility\n * is enforced — drift there fails the app type-check.\n */\nexport interface AssistantSendArgs {\n prompt: string;\n onProgress: (partialText: string) => void;\n signal: AbortSignal;\n}\n\nexport interface AssistantTransport {\n sendPrompt: (args: AssistantSendArgs) => Promise<{ text: string }>;\n}\n\n// ── Inline GraphQL documents (no generated-operation dependency) ─────\n\nconst CreateSandboxDocument = gql`\n mutation CreateSandbox($input: CreateSandboxInput!) {\n createSandbox(input: $input) {\n id\n name\n status\n network {\n internalUrl\n }\n }\n }\n`;\n\nconst GetSandboxDocument = gql`\n query GetSandbox($id: ID!, $context: ContextInput!) {\n getSandbox(id: $id, context: $context) {\n id\n status\n errorMessage\n network {\n internalUrl\n }\n }\n }\n`;\n\nconst GetActiveSandboxesDocument = gql`\n query GetActiveSandboxes($context: ContextInput!) {\n getActiveSandboxes(context: $context) {\n id\n name\n status\n metadata\n template\n createdBy\n }\n }\n`;\n\nconst ProxySandboxRequestDocument = gql`\n mutation ProxySandboxRequest($input: ProxySandboxRequestInput!) {\n proxySandboxRequest(input: $input) {\n status\n body\n }\n }\n`;\n\nconst ExtendSandboxTtlDocument = gql`\n mutation ExtendSandboxTtl($id: ID!, $additionalSeconds: Int!, $context: ContextInput!) {\n extendSandboxTTL(id: $id, additionalSeconds: $additionalSeconds, context: $context) {\n id\n expiresAt\n }\n }\n`;\n\n// ── Types ───────────────────────────────────────────────────────────\n\ntype AssistantMode = 'assistant';\n\ninterface AssistantSandboxAuthContext {\n accessToken?: string | null;\n workspaceToken?: string | null;\n userId?: string | null;\n organizationId?: string | null;\n}\n\ninterface AssistantRawMessagePart {\n type?: string;\n text?: string;\n tool?: string;\n state?: {\n status?: string;\n input?: unknown;\n output?: unknown;\n raw?: unknown;\n };\n}\n\ninterface AssistantRawMessage {\n info?: {\n id?: string;\n role?: string;\n providerID?: string;\n modelID?: string;\n tokens?: { input: number; output: number };\n time?: {\n created?: number;\n completed?: number;\n };\n };\n parts?: AssistantRawMessagePart[];\n}\n\ninterface GraphQLResponse<TData> {\n data?: TData;\n errors?: Array<{ message: string }>;\n}\n\n// ── Constants ───────────────────────────────────────────────────────\n\nconst MODE: AssistantMode = 'assistant';\nconst STREAM_BUDGET_MS = 180_000;\nconst POLL_INTERVAL_MS = 1500;\nconst TTL_EXTEND_INTERVAL_MS = 30_000;\nconst HARBOR_REGISTRY = 'burdenoffregistry.azurecr.io';\nconst API_CALLS_IMAGE_TAG =\n import.meta.env.VITE_APP_ENV === 'prod' ? 'prod' : 'alpha-delegated-auth-v11';\nconst ASSISTANT_API_AUTH_STRATEGY = 'sandbox-app-client-headers-v1';\n\n// ── Context helpers ─────────────────────────────────────────────────\n\nfunction getProfileContextValue(key: 'workspaceId' | 'organizationId'): string {\n try {\n const activeContextKey =\n key === 'workspaceId'\n ? 'burdenoff-active-context-workspace'\n : 'burdenoff-active-context-organization';\n const activeContextValue = localStorage.getItem(activeContextKey);\n if (activeContextValue) return activeContextValue;\n\n const activeProfileId = sessionStorage.getItem('bf-active-profile');\n if (!activeProfileId) return '';\n const raw = localStorage.getItem(`bf-p-${activeProfileId}-context`);\n if (!raw) return '';\n const context = JSON.parse(raw) as { workspaceId?: string; organizationId?: string };\n return context[key] ?? '';\n } catch {\n return '';\n }\n}\n\nfunction getWorkspaceId(): string {\n const params = new URLSearchParams(window.location.search);\n return params.get('workspace') ?? getProfileContextValue('workspaceId');\n}\n\nfunction getOrganizationId(): string {\n const params = new URLSearchParams(window.location.search);\n return params.get('org') ?? getProfileContextValue('organizationId');\n}\n\n// ── GraphQL execution helpers ───────────────────────────────────────\n\nfunction getGatewayRequestContext(\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext\n): { headers?: Record<string, string> } | undefined {\n const headers: Record<string, string> = {};\n if (authContext?.accessToken) {\n headers.authorization = `Bearer ${authContext.accessToken}`;\n }\n if (authContext?.workspaceToken) {\n headers['x-workspace-authorization'] = `Bearer ${authContext.workspaceToken}`;\n }\n if (workspaceId) {\n headers['x-workspace-id'] = workspaceId;\n }\n if (authContext?.userId) {\n headers['x-actor-id'] = authContext.userId;\n headers['x-actor-type'] = 'user';\n }\n if (authContext?.organizationId) {\n headers['x-org-id'] = authContext.organizationId;\n }\n return Object.keys(headers).length > 0 ? { headers } : undefined;\n}\n\nfunction buildSandboxContextInput(\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext\n): Record<string, unknown> {\n return {\n workspaceId,\n ...(authContext?.organizationId\n ? {\n custom: {\n organizationId: authContext.organizationId,\n },\n }\n : {}),\n };\n}\n\nasync function executeSandboxGraphQL<TData>(params: {\n document: ReturnType<typeof gql>;\n variables: Record<string, unknown>;\n workspaceId: string;\n authContext?: AssistantSandboxAuthContext;\n}): Promise<TData> {\n const response = await fetch('/workspaces/graphql', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(getGatewayRequestContext(params.workspaceId, params.authContext)?.headers ?? {}),\n },\n body: JSON.stringify({\n query: params.document.loc?.source.body ?? '',\n variables: params.variables,\n }),\n });\n\n const result = (await response.json().catch(() => null)) as GraphQLResponse<TData> | null;\n\n if (!response.ok) {\n const messages = result?.errors?.map((error) => error.message).join(', ');\n const httpError = new Error(messages || `Sandbox GraphQL HTTP ${response.status}`) as Error & {\n statusCode?: number;\n result?: unknown;\n };\n httpError.statusCode = response.status;\n httpError.result = result ?? undefined;\n throw httpError;\n }\n\n if (!result) {\n throw new Error('Sandbox GraphQL returned empty response');\n }\n if (result.errors?.length) {\n throw new Error(result.errors.map((error) => error.message).join(', '));\n }\n if (!result.data) {\n throw new Error('Sandbox GraphQL returned no data');\n }\n return result.data;\n}\n\n// ── Sandbox lifecycle ───────────────────────────────────────────────\n\nasync function findExistingSandbox(\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext\n): Promise<string | null> {\n const data = await executeSandboxGraphQL<{\n getActiveSandboxes: Array<{\n id: string;\n name: string;\n status: string;\n metadata: Record<string, unknown> | null;\n template: string;\n createdBy: string;\n }>;\n }>({\n document: GetActiveSandboxesDocument,\n variables: { context: buildSandboxContextInput(workspaceId, authContext) },\n workspaceId,\n authContext,\n });\n const sandboxes = data.getActiveSandboxes ?? [];\n return (\n sandboxes.find(\n (s) =>\n (s.status === 'RUNNING' || s.status === 'PROVISIONING' || s.status === 'PENDING') &&\n s.template === 'AI_BUILDER' &&\n s.metadata?.assistantMode === MODE &&\n (!authContext?.userId || s.createdBy === authContext.userId) &&\n s.metadata?.authStrategy === ASSISTANT_API_AUTH_STRATEGY &&\n s.metadata?.imageTag === API_CALLS_IMAGE_TAG\n )?.id ?? null\n );\n}\n\nasync function createAssistantSandbox(\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext\n): Promise<string> {\n const data = await executeSandboxGraphQL<{ createSandbox: { id: string } }>({\n document: CreateSandboxDocument,\n variables: {\n input: {\n name: `ai-assistant-${MODE}-${Date.now()}`,\n description: `AI Assistant sandbox (${MODE} mode)`,\n template: 'AI_BUILDER',\n mode: 'DEPLOYMENT',\n containers: [\n {\n name: 'ai-assistant',\n image: `${HARBOR_REGISTRY}/wspace/ai-assistant-api-calls:${API_CALLS_IMAGE_TAG}`,\n ports: [{ containerPort: 8080, name: 'http' }],\n env: [\n { name: 'AI_ASSISTANT_MODE', value: MODE },\n { name: 'HOSTNAME', value: '0.0.0.0' },\n { name: 'WORKSPACE_ID', value: workspaceId },\n { name: 'AI_ASSISTANT_PRODUCT', value: 'coolandlovely' },\n ],\n },\n ],\n resources: {\n cpuRequest: '150m',\n cpuLimit: '750m',\n memoryRequest: '384Mi',\n memoryLimit: '1536Mi',\n },\n ttlSeconds: 600,\n metadata: {\n assistantMode: MODE,\n assistantOwnerUserId: authContext?.userId ?? null,\n assistantOrganizationId: authContext?.organizationId ?? null,\n authStrategy: ASSISTANT_API_AUTH_STRATEGY,\n imageTag: API_CALLS_IMAGE_TAG,\n },\n context: buildSandboxContextInput(workspaceId, authContext),\n },\n },\n workspaceId,\n authContext,\n });\n\n if (!data?.createSandbox?.id) {\n throw new Error('createSandbox returned empty response');\n }\n return data.createSandbox.id;\n}\n\nasync function waitForSandboxReady(\n sandboxId: string,\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext,\n maxWaitMs = 300_000\n): Promise<void> {\n const start = Date.now();\n while (Date.now() - start < maxWaitMs) {\n const data = await executeSandboxGraphQL<{\n getSandbox: { status?: string; errorMessage?: string } | null;\n }>({\n document: GetSandboxDocument,\n variables: { id: sandboxId, context: buildSandboxContextInput(workspaceId, authContext) },\n workspaceId,\n authContext,\n });\n const status = data?.getSandbox?.status;\n const errorMessage = data?.getSandbox?.errorMessage as string | undefined;\n if (status === 'RUNNING') return;\n if (status === 'FAILED' || status === 'STOPPED' || status === 'EXPIRED') {\n throw new Error(\n errorMessage\n ? `Sandbox ${status.toLowerCase()}: ${errorMessage}`\n : `Sandbox ${status.toLowerCase()}`\n );\n }\n await new Promise((r) => setTimeout(r, 2000));\n }\n throw new Error('Sandbox startup timed out');\n}\n\nasync function proxyToSandbox(\n sandboxId: string,\n workspaceId: string,\n path: string,\n method: string,\n body?: unknown,\n authContext?: AssistantSandboxAuthContext\n): Promise<Record<string, unknown>> {\n const result = await executeSandboxGraphQL<{\n proxySandboxRequest: { status: number; body: Record<string, unknown> };\n }>({\n document: ProxySandboxRequestDocument,\n variables: {\n input: {\n sandboxId,\n path,\n method,\n body,\n context: buildSandboxContextInput(workspaceId, authContext),\n },\n },\n workspaceId,\n authContext,\n });\n\n const proxyResult = result.proxySandboxRequest;\n if (!proxyResult || proxyResult.status >= 400) {\n const errBody = proxyResult?.body as Record<string, unknown> | undefined;\n throw new Error(\n (errBody?.error as string) ?? `Proxy error: ${proxyResult?.status ?? 'unknown'}`\n );\n }\n return proxyResult.body;\n}\n\nasync function waitForAssistantServiceReady(\n sandboxId: string,\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext,\n maxWaitMs = 90_000\n): Promise<void> {\n const startedAt = Date.now();\n while (Date.now() - startedAt < maxWaitMs) {\n try {\n await proxyToSandbox(sandboxId, workspaceId, '/health', 'GET', undefined, authContext);\n return;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n if (\n !/proxy error: 502|proxy error: 503|proxy error: 404|service not available/i.test(message)\n ) {\n throw error;\n }\n }\n await new Promise((resolve) => setTimeout(resolve, 2000));\n }\n throw new Error('Assistant service did not become healthy before timeout');\n}\n\nasync function createAssistantSession(\n sandboxId: string,\n workspaceId: string,\n authContext?: AssistantSandboxAuthContext\n): Promise<{ sessionId: string }> {\n const body = await proxyToSandbox(\n sandboxId,\n workspaceId,\n '/sessions',\n 'POST',\n { mode: MODE },\n authContext\n );\n const session = body.session as Record<string, unknown>;\n return { sessionId: session.id as string };\n}\n\nasync function sendAssistantPromptAsync(\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n prompt: string,\n authContext?: AssistantSandboxAuthContext\n): Promise<void> {\n await proxyToSandbox(\n sandboxId,\n workspaceId,\n `/sessions/${sessionId}/prompt-async`,\n 'POST',\n {\n mode: MODE,\n prompt,\n pageContext: gatherPageContext(),\n },\n authContext\n );\n}\n\nasync function getAssistantMessages(\n sandboxId: string,\n workspaceId: string,\n sessionId: string,\n authContext?: AssistantSandboxAuthContext,\n limit = 50\n): Promise<AssistantRawMessage[]> {\n const body = await proxyToSandbox(\n sandboxId,\n workspaceId,\n `/sessions/${sessionId}/messages?limit=${limit}`,\n 'GET',\n undefined,\n authContext\n );\n return (body.messages as AssistantRawMessage[] | undefined) ?? [];\n}\n\nasync function extendAssistantSandboxTTL(\n sandboxId: string,\n workspaceId: string,\n additionalSeconds = 600,\n authContext?: AssistantSandboxAuthContext\n): Promise<void> {\n await executeSandboxGraphQL<{ extendSandboxTTL: { id: string } }>({\n document: ExtendSandboxTtlDocument,\n variables: {\n id: sandboxId,\n additionalSeconds,\n context: buildSandboxContextInput(workspaceId, authContext),\n },\n workspaceId,\n authContext,\n });\n}\n\n// ── Message-progress helpers ────────────────────────────────────────\n\nfunction getRawMessageCreatedAt(message: AssistantRawMessage): number {\n return message.info?.time?.created ?? 0;\n}\n\nfunction getAssistantText(parts: AssistantRawMessage['parts']): string {\n return (parts ?? [])\n .filter((part) => part.type === 'text' && typeof part.text === 'string')\n .map((part) => part.text?.trim() ?? '')\n .filter(Boolean)\n .join('\\n');\n}\n\nfunction getToolProgress(parts: AssistantRawMessage['parts']): string[] {\n return (parts ?? [])\n .filter((part) => part.type === 'tool' && part.tool)\n .map((part) => {\n const status = part.state?.status ?? 'running';\n const tool = part.tool ?? 'tool';\n if (status === 'completed') return `Completed: ${tool}`;\n if (status === 'failed') return `Failed: ${tool}`;\n return `Running: ${tool}`;\n });\n}\n\nfunction buildProgress(\n messages: AssistantRawMessage[],\n sinceMs: number\n): { content: string; done: boolean } {\n const relevant = messages\n .filter(\n (message) => message.info?.role === 'assistant' && getRawMessageCreatedAt(message) >= sinceMs\n )\n .sort((left, right) => getRawMessageCreatedAt(left) - getRawMessageCreatedAt(right));\n\n if (relevant.length === 0) return { content: 'Thinking…', done: false };\n\n const latest = relevant[relevant.length - 1]!;\n const text = getAssistantText(latest.parts);\n const toolProgress = getToolProgress(latest.parts);\n const content = [text, ...toolProgress].filter(Boolean).join('\\n\\n') || 'Thinking…';\n\n return {\n content,\n done: Boolean(latest.info?.time?.completed) && (Boolean(text) || toolProgress.length > 0),\n };\n}\n\nfunction sanitize(response: string): string {\n return response\n .replace(/(Authorization\\s*:\\s*Bearer\\s+)[^\\s\\n]+/gi, '$1[REDACTED]')\n .replace(/(X-Workspace-Authorization\\s*:\\s*Bearer\\s+)[^\\s\\n]+/gi, '$1[REDACTED]')\n .replace(/\\beyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9._-]+\\.[A-Za-z0-9._-]+\\b/g, '[REDACTED_JWT]')\n .replace(/\\bsk-ant-[A-Za-z0-9-]+\\b/g, '[REDACTED_API_KEY]');\n}\n\nfunction isRecoverable(error: unknown): boolean {\n const message =\n error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();\n if (\n message.includes('rate limit exceeded') ||\n message.includes('unauthorized') ||\n message.includes('k8s api error 401')\n ) {\n return false;\n }\n return [\n 'sandbox not found',\n 'sandbox is not running',\n 'sandbox service not available yet',\n 'sandbox failed',\n 'sandbox startup timed out',\n 'assistant service did not become healthy',\n 'proxy error: 404',\n 'proxy error: 502',\n 'proxy error: 503',\n ].some((fragment) => message.includes(fragment));\n}\n\n// ── Public hook ─────────────────────────────────────────────────────\n\n/**\n * Returns a memoized `AssistantTransport` wired to the CoolAndLovely sandbox\n * `api-calls` agent. The sandbox + session are cached in refs so follow-up\n * turns reuse the warm environment for the lifetime of the host shell.\n */\nexport function useSandboxAssistantTransport(): AssistantTransport {\n useGatewayClient('workspace');\n const { getAccessToken, getWorkspaceToken, userId } = useAuthToken();\n\n const sandboxIdRef = useRef<string | null>(null);\n const sessionIdRef = useRef<string | null>(null);\n\n const ensureRuntime = useCallback(\n async (\n workspaceId: string,\n authContext: AssistantSandboxAuthContext\n ): Promise<{ sandboxId: string; sessionId: string }> => {\n let sandboxId = sandboxIdRef.current;\n if (!sandboxId) {\n sandboxId = await findExistingSandbox(workspaceId, authContext);\n if (!sandboxId) {\n if (!getAccessToken()) {\n throw new Error(\n 'API Calls mode requires an authenticated session. Please sign in again.'\n );\n }\n sandboxId = await createAssistantSandbox(workspaceId, authContext);\n }\n await waitForSandboxReady(sandboxId, workspaceId, authContext);\n await waitForAssistantServiceReady(sandboxId, workspaceId, authContext);\n sandboxIdRef.current = sandboxId;\n }\n\n let sessionId = sessionIdRef.current;\n if (!sessionId) {\n const result = await createAssistantSession(sandboxId, workspaceId, authContext);\n sessionId = result.sessionId;\n sessionIdRef.current = sessionId;\n }\n\n return { sandboxId, sessionId };\n },\n [getAccessToken]\n );\n\n const sendPrompt = useCallback(\n async ({ prompt, onProgress, signal }: AssistantSendArgs): Promise<{ text: string }> => {\n const workspaceId = getWorkspaceId();\n if (!workspaceId) {\n throw new Error('The assistant needs an active workspace. Open a workspace and try again.');\n }\n const authContext: AssistantSandboxAuthContext = {\n accessToken: getAccessToken(),\n workspaceToken: getWorkspaceToken(),\n userId,\n organizationId: getOrganizationId(),\n };\n\n const run = async (attempt: 1 | 2): Promise<{ text: string }> => {\n try {\n const { sandboxId, sessionId } = await ensureRuntime(workspaceId, authContext);\n\n const startedAt = Date.now();\n await sendAssistantPromptAsync(sandboxId, workspaceId, sessionId, prompt, authContext);\n\n const timeoutAt = Date.now() + STREAM_BUDGET_MS;\n let lastTtlExtensionAt = Date.now();\n let progress = buildProgress([], startedAt);\n\n while (Date.now() < timeoutAt) {\n if (signal.aborted) throw new Error('Cancelled');\n\n const messages = await getAssistantMessages(\n sandboxId,\n workspaceId,\n sessionId,\n authContext,\n 50\n );\n progress = buildProgress(messages, startedAt);\n onProgress(sanitize(progress.content));\n if (progress.done) break;\n\n if (Date.now() - lastTtlExtensionAt > TTL_EXTEND_INTERVAL_MS) {\n await extendAssistantSandboxTTL(sandboxId, workspaceId, 600, authContext).catch(\n () => undefined\n );\n lastTtlExtensionAt = Date.now();\n }\n\n await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));\n }\n\n if (!progress.done) {\n throw new Error('The assistant timed out while responding. Please try again.');\n }\n\n return { text: sanitize(progress.content) };\n } catch (error) {\n if (attempt === 1 && isRecoverable(error)) {\n sandboxIdRef.current = null;\n sessionIdRef.current = null;\n return run(2);\n }\n throw error;\n }\n };\n\n return run(1);\n },\n [ensureRuntime, getAccessToken, getWorkspaceToken, userId]\n );\n\n return useMemo<AssistantTransport>(() => ({ sendPrompt }), [sendPrompt]);\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
//#region src/utils/pageContext.ts
|
|
2
|
+
function e() {
|
|
3
|
+
let e = window.location.href, t = window.location.pathname.split("/").filter(Boolean).slice(0, 3).join("/") || "home", r = n(window.location.pathname, window.location.search);
|
|
4
|
+
return {
|
|
5
|
+
currentUrl: e,
|
|
6
|
+
currentPage: t,
|
|
7
|
+
entityType: r.entityType,
|
|
8
|
+
entityId: r.entityId,
|
|
9
|
+
platform: "coolandlovely",
|
|
10
|
+
version: "2026.614.1",
|
|
11
|
+
viewportSize: `${window.innerWidth}x${window.innerHeight}`,
|
|
12
|
+
userAgent: navigator.userAgent,
|
|
13
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function t(e, t) {
|
|
17
|
+
return t.match(e);
|
|
18
|
+
}
|
|
19
|
+
function n(e, n) {
|
|
20
|
+
let r = new URLSearchParams(n);
|
|
21
|
+
for (let [n, r] of [
|
|
22
|
+
[/^\/coolandlovely\/brands\/([^/]+)$/, "COOLANDLOVELY_BRAND"],
|
|
23
|
+
[/^\/coolandlovely\/collections\/([^/]+)$/, "COOLANDLOVELY_COLLECTION"],
|
|
24
|
+
[/^\/coolandlovely\/items\/([^/]+)$/, "COOLANDLOVELY_STYLE_ITEM"],
|
|
25
|
+
[/^\/coolandlovely\/looks\/([^/]+)$/, "COOLANDLOVELY_LOOK"],
|
|
26
|
+
[/^\/coolandlovely\/collaborations\/([^/]+)$/, "COOLANDLOVELY_COLLABORATION"],
|
|
27
|
+
[/^\/coolandlovely\/lookbooks\/([^/]+)$/, "COOLANDLOVELY_LOOKBOOK"]
|
|
28
|
+
]) {
|
|
29
|
+
let i = t(n, e);
|
|
30
|
+
if (i) return {
|
|
31
|
+
entityType: r,
|
|
32
|
+
entityId: i[1],
|
|
33
|
+
metadata: { routePattern: n.source }
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
let i = r.get("tab") ?? (() => {
|
|
37
|
+
let t = e.split("/").filter(Boolean);
|
|
38
|
+
return t.length > 2 ? t[t.length - 1] : void 0;
|
|
39
|
+
})();
|
|
40
|
+
return {
|
|
41
|
+
activeTab: i ?? void 0,
|
|
42
|
+
metadata: {
|
|
43
|
+
...i ? { activeTab: i } : {},
|
|
44
|
+
...r.get("selectedCollectionId") ? { selectedCollectionId: r.get("selectedCollectionId") } : {}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
export { e as gatherPageContext };
|
|
50
|
+
|
|
51
|
+
//# sourceMappingURL=pageContext.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pageContext.js","names":[],"sources":["../../src/utils/pageContext.ts"],"sourcesContent":["/**\n * Page context gathering for the CoolAndLovely AI assistant.\n *\n * Captures the user's current browser context (URL, route, entity) so the\n * floating assistant can answer \"this page\" / \"current\" questions and run\n * operations scoped to the screen the user is looking at.\n */\n\nexport interface PageContext {\n currentUrl: string;\n currentPage: string;\n entityType?: string;\n entityId?: string;\n platform: string;\n version: string;\n viewportSize: string;\n userAgent: string;\n timestamp: string;\n}\n\ninterface ConversationContextShape {\n entityType?: string;\n entityId?: string;\n activeTab?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport function gatherPageContext(): PageContext {\n const url = window.location.href;\n const pathSegments = window.location.pathname.split('/').filter(Boolean);\n const currentPage = pathSegments.slice(0, 3).join('/') || 'home';\n const entity = resolveCoolAndLovelyConversationContext(\n window.location.pathname,\n window.location.search\n );\n\n return {\n currentUrl: url,\n currentPage,\n entityType: entity.entityType,\n entityId: entity.entityId,\n platform: 'coolandlovely',\n version: import.meta.env.VITE_APP_VERSION ?? '2026.614.1',\n viewportSize: `${window.innerWidth}x${window.innerHeight}`,\n userAgent: navigator.userAgent,\n timestamp: new Date().toISOString(),\n };\n}\n\nfunction matchPath(pattern: RegExp, pathname: string): RegExpMatchArray | null {\n return pathname.match(pattern);\n}\n\nexport function resolveCoolAndLovelyConversationContext(\n pathname: string,\n search: string\n): ConversationContextShape {\n const params = new URLSearchParams(search);\n\n const directMatches: Array<[RegExp, string]> = [\n [/^\\/coolandlovely\\/brands\\/([^/]+)$/, 'COOLANDLOVELY_BRAND'],\n [/^\\/coolandlovely\\/collections\\/([^/]+)$/, 'COOLANDLOVELY_COLLECTION'],\n [/^\\/coolandlovely\\/items\\/([^/]+)$/, 'COOLANDLOVELY_STYLE_ITEM'],\n [/^\\/coolandlovely\\/looks\\/([^/]+)$/, 'COOLANDLOVELY_LOOK'],\n [/^\\/coolandlovely\\/collaborations\\/([^/]+)$/, 'COOLANDLOVELY_COLLABORATION'],\n [/^\\/coolandlovely\\/lookbooks\\/([^/]+)$/, 'COOLANDLOVELY_LOOKBOOK'],\n ];\n\n for (const [pattern, entityType] of directMatches) {\n const match = matchPath(pattern, pathname);\n if (match) {\n return {\n entityType,\n entityId: match[1],\n metadata: { routePattern: pattern.source },\n };\n }\n }\n\n const activeTab =\n params.get('tab') ??\n (() => {\n const segments = pathname.split('/').filter(Boolean);\n return segments.length > 2 ? segments[segments.length - 1] : undefined;\n })();\n\n return {\n activeTab: activeTab ?? undefined,\n metadata: {\n ...(activeTab ? { activeTab } : {}),\n ...(params.get('selectedCollectionId')\n ? { selectedCollectionId: params.get('selectedCollectionId') }\n : {}),\n },\n };\n}\n"],"mappings":";AA2BA,SAAgB,IAAiC;CAC/C,IAAM,IAAM,OAAO,SAAS,MAEtB,IADe,OAAO,SAAS,SAAS,MAAM,IAAI,CAAC,OAAO,QAAQ,CACvC,MAAM,GAAG,EAAE,CAAC,KAAK,IAAI,IAAI,QACpD,IAAS,EACb,OAAO,SAAS,UAChB,OAAO,SAAS,OACjB;AAED,QAAO;EACL,YAAY;EACZ;EACA,YAAY,EAAO;EACnB,UAAU,EAAO;EACjB,UAAU;EACV,SAA6C;EAC7C,cAAc,GAAG,OAAO,WAAW,GAAG,OAAO;EAC7C,WAAW,UAAU;EACrB,4BAAW,IAAI,MAAM,EAAC,aAAa;EACpC;;AAGH,SAAS,EAAU,GAAiB,GAA2C;AAC7E,QAAO,EAAS,MAAM,EAAQ;;AAGhC,SAAgB,EACd,GACA,GAC0B;CAC1B,IAAM,IAAS,IAAI,gBAAgB,EAAO;AAW1C,MAAK,IAAM,CAAC,GAAS,MAT0B;EAC7C,CAAC,sCAAsC,sBAAsB;EAC7D,CAAC,2CAA2C,2BAA2B;EACvE,CAAC,qCAAqC,2BAA2B;EACjE,CAAC,qCAAqC,qBAAqB;EAC3D,CAAC,8CAA8C,8BAA8B;EAC7E,CAAC,yCAAyC,yBAAyB;EACpE,EAEkD;EACjD,IAAM,IAAQ,EAAU,GAAS,EAAS;AAC1C,MAAI,EACF,QAAO;GACL;GACA,UAAU,EAAM;GAChB,UAAU,EAAE,cAAc,EAAQ,QAAQ;GAC3C;;CAIL,IAAM,IACJ,EAAO,IAAI,MAAM,WACV;EACL,IAAM,IAAW,EAAS,MAAM,IAAI,CAAC,OAAO,QAAQ;AACpD,SAAO,EAAS,SAAS,IAAI,EAAS,EAAS,SAAS,KAAK,KAAA;KAC3D;AAEN,QAAO;EACL,WAAW,KAAa,KAAA;EACxB,UAAU;GACR,GAAI,IAAY,EAAE,cAAW,GAAG,EAAE;GAClC,GAAI,EAAO,IAAI,uBAAuB,GAClC,EAAE,sBAAsB,EAAO,IAAI,uBAAuB,EAAE,GAC5D,EAAE;GACP;EACF"}
|