@burtson-labs/bandit-engine 2.0.59 → 2.0.61
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/chat-E23BSMK5.mjs +16 -0
- package/dist/chat-provider.js.map +1 -1
- package/dist/chat-provider.mjs +5 -5
- package/dist/{chunk-KNBWR4DS.mjs → chunk-5WQMMCZQ.mjs} +3 -3
- package/dist/{chunk-QPBG6JQE.mjs → chunk-6QTTNYF2.mjs} +2 -2
- package/dist/{chunk-POTQI33D.mjs → chunk-D55E6ZDV.mjs} +5 -5
- package/dist/{chunk-557E5VZ2.mjs → chunk-EUBVBTB3.mjs} +2 -2
- package/dist/{chunk-V5QRXIIO.mjs → chunk-G7U2FNUK.mjs} +494 -20
- package/dist/chunk-G7U2FNUK.mjs.map +1 -0
- package/dist/{chunk-7ZDS33S2.mjs → chunk-IPMTNREZ.mjs} +2 -2
- package/dist/{chunk-7ZDS33S2.mjs.map → chunk-IPMTNREZ.mjs.map} +1 -1
- package/dist/{chunk-WL7NV4WJ.mjs → chunk-PY7A3J5T.mjs} +4 -4
- package/dist/{chunk-URKUD3OL.mjs → chunk-SKMJ43NN.mjs} +9 -9
- package/dist/{chunk-KM7FUWCM.mjs → chunk-SRCCNBHF.mjs} +7 -3
- package/dist/chunk-SRCCNBHF.mjs.map +1 -0
- package/dist/{chunk-UFSEYVRS.mjs → chunk-VTC6AIWY.mjs} +3 -3
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +522 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +13 -13
- package/dist/management/management.js +522 -16
- package/dist/management/management.js.map +1 -1
- package/dist/management/management.mjs +8 -8
- package/dist/modals/chat-modal/chat-modal.js.map +1 -1
- package/dist/modals/chat-modal/chat-modal.mjs +4 -4
- package/dist/{modelStore-XWFHNTBT.mjs → modelStore-FBPBG7TI.mjs} +2 -2
- package/dist/{public-BzsEWB08.d.mts → public-nrOOzXCZ.d.mts} +10 -0
- package/dist/{public-BzsEWB08.d.ts → public-nrOOzXCZ.d.ts} +10 -0
- package/dist/public-types.d.mts +1 -1
- package/dist/public-types.d.ts +1 -1
- package/package.json +1 -1
- package/dist/chat-3J4GDGWW.mjs +0 -16
- package/dist/chunk-KM7FUWCM.mjs.map +0 -1
- package/dist/chunk-V5QRXIIO.mjs.map +0 -1
- /package/dist/{chat-3J4GDGWW.mjs.map → chat-E23BSMK5.mjs.map} +0 -0
- /package/dist/{chunk-KNBWR4DS.mjs.map → chunk-5WQMMCZQ.mjs.map} +0 -0
- /package/dist/{chunk-QPBG6JQE.mjs.map → chunk-6QTTNYF2.mjs.map} +0 -0
- /package/dist/{chunk-POTQI33D.mjs.map → chunk-D55E6ZDV.mjs.map} +0 -0
- /package/dist/{chunk-557E5VZ2.mjs.map → chunk-EUBVBTB3.mjs.map} +0 -0
- /package/dist/{chunk-WL7NV4WJ.mjs.map → chunk-PY7A3J5T.mjs.map} +0 -0
- /package/dist/{chunk-URKUD3OL.mjs.map → chunk-SKMJ43NN.mjs.map} +0 -0
- /package/dist/{chunk-UFSEYVRS.mjs.map → chunk-VTC6AIWY.mjs.map} +0 -0
- /package/dist/{modelStore-XWFHNTBT.mjs.map → modelStore-FBPBG7TI.mjs.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -14862,7 +14862,11 @@ ${listMarkdown}`;
|
|
|
14862
14862
|
href,
|
|
14863
14863
|
target: "_blank",
|
|
14864
14864
|
rel: "noopener noreferrer",
|
|
14865
|
-
style: {
|
|
14865
|
+
style: {
|
|
14866
|
+
color: theme.palette.info?.main ?? theme.palette.primary.main,
|
|
14867
|
+
textDecoration: "underline",
|
|
14868
|
+
textUnderlineOffset: "2px"
|
|
14869
|
+
},
|
|
14866
14870
|
...props,
|
|
14867
14871
|
children
|
|
14868
14872
|
}
|
|
@@ -19107,6 +19111,388 @@ ${sanitize(
|
|
|
19107
19111
|
}
|
|
19108
19112
|
});
|
|
19109
19113
|
|
|
19114
|
+
// src/services/telemetry/otlpExporter.ts
|
|
19115
|
+
function redactSecretsString(s) {
|
|
19116
|
+
return s.replace(/\b(?:sk|tvly|ghp|gho|pk|rk)[-_][A-Za-z0-9]{8,}\b/gi, "[redacted]").replace(/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g, "[redacted-jwt]").replace(/\bBearer\s+[A-Za-z0-9._-]{8,}/gi, "Bearer [redacted]");
|
|
19117
|
+
}
|
|
19118
|
+
function resolveTelemetryConfig(opts) {
|
|
19119
|
+
if (!opts.telemetry?.enabled) return null;
|
|
19120
|
+
const endpoint = (opts.telemetry.endpoint ?? "https://otlp.burtson.ai").replace(/\/+$/, "");
|
|
19121
|
+
const headers = { ...opts.telemetry.headers ?? {} };
|
|
19122
|
+
const hasAuth = Object.keys(headers).some((k) => k.toLowerCase() === "authorization");
|
|
19123
|
+
if (!hasAuth && opts.banditApiKey) {
|
|
19124
|
+
headers["Authorization"] = `Bearer ${opts.banditApiKey}`;
|
|
19125
|
+
}
|
|
19126
|
+
const mode = opts.telemetry.mode ?? "metrics+traces";
|
|
19127
|
+
return { endpoint, headers, mode, serviceName: opts.telemetry.serviceName ?? "bandit-web" };
|
|
19128
|
+
}
|
|
19129
|
+
function toAttrs(rec) {
|
|
19130
|
+
const out = [];
|
|
19131
|
+
for (const [key, v] of Object.entries(rec)) {
|
|
19132
|
+
if (v === void 0 || v === null || v === "") continue;
|
|
19133
|
+
if (typeof v === "boolean") out.push({ key, value: { boolValue: v } });
|
|
19134
|
+
else if (typeof v === "number")
|
|
19135
|
+
out.push({ key, value: Number.isInteger(v) ? { intValue: String(v) } : { doubleValue: v } });
|
|
19136
|
+
else out.push({ key, value: { stringValue: v } });
|
|
19137
|
+
}
|
|
19138
|
+
return out;
|
|
19139
|
+
}
|
|
19140
|
+
function hex(bytes) {
|
|
19141
|
+
const arr = new Uint8Array(bytes);
|
|
19142
|
+
if (webCrypto?.getRandomValues) webCrypto.getRandomValues(arr);
|
|
19143
|
+
return Array.from(arr, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
19144
|
+
}
|
|
19145
|
+
function histogramPoint(value, bounds, attrs, startMs, endMs) {
|
|
19146
|
+
const counts = new Array(bounds.length + 1).fill(0);
|
|
19147
|
+
let idx = bounds.findIndex((b) => value <= b);
|
|
19148
|
+
if (idx === -1) idx = bounds.length;
|
|
19149
|
+
counts[idx] = 1;
|
|
19150
|
+
return {
|
|
19151
|
+
attributes: toAttrs(attrs),
|
|
19152
|
+
startTimeUnixNano: nano(startMs),
|
|
19153
|
+
timeUnixNano: nano(endMs),
|
|
19154
|
+
count: "1",
|
|
19155
|
+
sum: value,
|
|
19156
|
+
bucketCounts: counts.map(String),
|
|
19157
|
+
explicitBounds: bounds
|
|
19158
|
+
};
|
|
19159
|
+
}
|
|
19160
|
+
function sumPoint(value, attrs, startMs, endMs) {
|
|
19161
|
+
return {
|
|
19162
|
+
attributes: toAttrs(attrs),
|
|
19163
|
+
startTimeUnixNano: nano(startMs),
|
|
19164
|
+
timeUnixNano: nano(endMs),
|
|
19165
|
+
asInt: String(Math.round(value))
|
|
19166
|
+
};
|
|
19167
|
+
}
|
|
19168
|
+
var webCrypto, nano, TTFT_BUCKETS, DURATION_BUCKETS, clip, TelemetryExporter;
|
|
19169
|
+
var init_otlpExporter = __esm({
|
|
19170
|
+
"src/services/telemetry/otlpExporter.ts"() {
|
|
19171
|
+
"use strict";
|
|
19172
|
+
init_debugLogger();
|
|
19173
|
+
webCrypto = globalThis.crypto;
|
|
19174
|
+
nano = (ms) => String(Math.round(ms * 1e6));
|
|
19175
|
+
TTFT_BUCKETS = [0.1, 0.25, 0.5, 1, 2, 5, 10, 30];
|
|
19176
|
+
DURATION_BUCKETS = [0.5, 1, 2, 5, 10, 30, 60, 120, 300];
|
|
19177
|
+
clip = (s, n = 120) => redactSecretsString(s.slice(0, n)).slice(0, n);
|
|
19178
|
+
TelemetryExporter = class {
|
|
19179
|
+
cfg;
|
|
19180
|
+
now;
|
|
19181
|
+
traceId = "";
|
|
19182
|
+
turn = null;
|
|
19183
|
+
llm = null;
|
|
19184
|
+
llmFirstChunkMs = 0;
|
|
19185
|
+
openTools = [];
|
|
19186
|
+
completedSpans = [];
|
|
19187
|
+
model = "";
|
|
19188
|
+
turnChunkChars = 0;
|
|
19189
|
+
turnTokens = 0;
|
|
19190
|
+
ttftSeconds = null;
|
|
19191
|
+
constructor(cfg, opts) {
|
|
19192
|
+
this.cfg = cfg;
|
|
19193
|
+
this.now = opts?.now ?? (() => Date.now());
|
|
19194
|
+
}
|
|
19195
|
+
startTurn(goal, model) {
|
|
19196
|
+
this.traceId = hex(16);
|
|
19197
|
+
this.model = model;
|
|
19198
|
+
this.turnChunkChars = 0;
|
|
19199
|
+
this.turnTokens = 0;
|
|
19200
|
+
this.ttftSeconds = null;
|
|
19201
|
+
this.llm = null;
|
|
19202
|
+
this.llmFirstChunkMs = 0;
|
|
19203
|
+
this.openTools = [];
|
|
19204
|
+
this.completedSpans = [];
|
|
19205
|
+
this.turn = {
|
|
19206
|
+
spanId: hex(8),
|
|
19207
|
+
name: "agent.turn",
|
|
19208
|
+
startMs: this.now(),
|
|
19209
|
+
attrs: { "gen_ai.request.model": model, "bandit.turn.goal": clip(goal, 160) }
|
|
19210
|
+
};
|
|
19211
|
+
}
|
|
19212
|
+
/** Fed from the chat turn lifecycle. Best-effort; swallows bad payloads. */
|
|
19213
|
+
onEvent(type, payload) {
|
|
19214
|
+
if (!this.turn) return;
|
|
19215
|
+
try {
|
|
19216
|
+
const p = payload ?? {};
|
|
19217
|
+
switch (type) {
|
|
19218
|
+
case "tool_loop:llm_start":
|
|
19219
|
+
this.llm = {
|
|
19220
|
+
spanId: hex(8),
|
|
19221
|
+
parentSpanId: this.turn.spanId,
|
|
19222
|
+
name: "llm.generate",
|
|
19223
|
+
startMs: this.now(),
|
|
19224
|
+
attrs: { "gen_ai.request.model": this.model }
|
|
19225
|
+
};
|
|
19226
|
+
this.llmFirstChunkMs = 0;
|
|
19227
|
+
break;
|
|
19228
|
+
case "tool_loop:llm_chunk": {
|
|
19229
|
+
const chunk = typeof p.chunk === "string" ? p.chunk : "";
|
|
19230
|
+
if (this.llm && this.llmFirstChunkMs === 0 && chunk.length > 0) {
|
|
19231
|
+
this.llmFirstChunkMs = this.now();
|
|
19232
|
+
const ttft = (this.llmFirstChunkMs - this.llm.startMs) / 1e3;
|
|
19233
|
+
if (this.ttftSeconds === null) this.ttftSeconds = ttft;
|
|
19234
|
+
this.llm.attrs["bandit.llm.ttft_seconds"] = ttft;
|
|
19235
|
+
}
|
|
19236
|
+
this.turnChunkChars += chunk.length;
|
|
19237
|
+
this.turnTokens = Math.floor(this.turnChunkChars / 4);
|
|
19238
|
+
break;
|
|
19239
|
+
}
|
|
19240
|
+
case "tool_loop:llm_response":
|
|
19241
|
+
if (this.llm) {
|
|
19242
|
+
this.llm.endMs = this.now();
|
|
19243
|
+
if (typeof p.responseLength === "number") this.llm.attrs["bandit.llm.response_chars"] = p.responseLength;
|
|
19244
|
+
this.completedSpans.push(this.llm);
|
|
19245
|
+
this.llm = null;
|
|
19246
|
+
}
|
|
19247
|
+
break;
|
|
19248
|
+
case "tool_loop:tool_execute": {
|
|
19249
|
+
const name = typeof p.name === "string" ? p.name : "tool";
|
|
19250
|
+
const params = p.params ?? {};
|
|
19251
|
+
const primary = params.query ?? params.url ?? params.prompt ?? params.topic ?? "";
|
|
19252
|
+
this.openTools.push({
|
|
19253
|
+
spanId: hex(8),
|
|
19254
|
+
parentSpanId: this.turn.spanId,
|
|
19255
|
+
name: `tool.${name}`,
|
|
19256
|
+
startMs: this.now(),
|
|
19257
|
+
attrs: { "bandit.tool.name": name, "bandit.tool.primary": primary ? clip(primary) : void 0 }
|
|
19258
|
+
});
|
|
19259
|
+
break;
|
|
19260
|
+
}
|
|
19261
|
+
case "tool_loop:tool_result":
|
|
19262
|
+
case "tool_loop:tool_error": {
|
|
19263
|
+
const name = typeof p.name === "string" ? p.name : void 0;
|
|
19264
|
+
const span = this.takeOpenTool(name);
|
|
19265
|
+
if (span) {
|
|
19266
|
+
span.endMs = this.now();
|
|
19267
|
+
if (type === "tool_loop:tool_error" || p.isError === true) span.error = "tool error";
|
|
19268
|
+
this.completedSpans.push(span);
|
|
19269
|
+
}
|
|
19270
|
+
break;
|
|
19271
|
+
}
|
|
19272
|
+
}
|
|
19273
|
+
} catch {
|
|
19274
|
+
}
|
|
19275
|
+
}
|
|
19276
|
+
takeOpenTool(name) {
|
|
19277
|
+
if (name) {
|
|
19278
|
+
for (let i = this.openTools.length - 1; i >= 0; i -= 1) {
|
|
19279
|
+
if (this.openTools[i].name === `tool.${name}`) return this.openTools.splice(i, 1)[0];
|
|
19280
|
+
}
|
|
19281
|
+
}
|
|
19282
|
+
return this.openTools.shift();
|
|
19283
|
+
}
|
|
19284
|
+
/** Close the turn, build OTLP traces + metrics, and flush. Never rejects. */
|
|
19285
|
+
async endTurn(outcome) {
|
|
19286
|
+
const turn = this.turn;
|
|
19287
|
+
if (!turn) return;
|
|
19288
|
+
this.turn = null;
|
|
19289
|
+
const end = this.now();
|
|
19290
|
+
if (this.llm && !this.llm.endMs) {
|
|
19291
|
+
this.llm.endMs = end;
|
|
19292
|
+
this.completedSpans.push(this.llm);
|
|
19293
|
+
this.llm = null;
|
|
19294
|
+
}
|
|
19295
|
+
for (const t of this.openTools.splice(0)) {
|
|
19296
|
+
t.endMs = end;
|
|
19297
|
+
t.error = t.error ?? "incomplete";
|
|
19298
|
+
this.completedSpans.push(t);
|
|
19299
|
+
}
|
|
19300
|
+
turn.endMs = end;
|
|
19301
|
+
if (outcome?.error) turn.error = outcome.error;
|
|
19302
|
+
const traceId = this.traceId;
|
|
19303
|
+
const spans = [turn, ...this.completedSpans];
|
|
19304
|
+
const jobs = [];
|
|
19305
|
+
if (this.cfg.mode === "metrics+traces") jobs.push(this.post("/v1/traces", this.buildTraces(traceId, spans)));
|
|
19306
|
+
jobs.push(this.post("/v1/metrics", this.buildMetrics(turn)));
|
|
19307
|
+
try {
|
|
19308
|
+
await Promise.all(jobs);
|
|
19309
|
+
} catch {
|
|
19310
|
+
}
|
|
19311
|
+
}
|
|
19312
|
+
buildTraces(traceId, spans) {
|
|
19313
|
+
return {
|
|
19314
|
+
resourceSpans: [
|
|
19315
|
+
{
|
|
19316
|
+
resource: { attributes: toAttrs({ "service.name": this.cfg.serviceName }) },
|
|
19317
|
+
scopeSpans: [
|
|
19318
|
+
{
|
|
19319
|
+
scope: { name: this.cfg.serviceName },
|
|
19320
|
+
spans: spans.map((s) => ({
|
|
19321
|
+
traceId,
|
|
19322
|
+
spanId: s.spanId,
|
|
19323
|
+
parentSpanId: s.parentSpanId,
|
|
19324
|
+
name: s.name,
|
|
19325
|
+
kind: 1,
|
|
19326
|
+
startTimeUnixNano: nano(s.startMs),
|
|
19327
|
+
endTimeUnixNano: nano(s.endMs ?? s.startMs),
|
|
19328
|
+
attributes: toAttrs(s.attrs),
|
|
19329
|
+
status: s.error ? { code: 2, message: s.error.slice(0, 200) } : { code: 1 }
|
|
19330
|
+
}))
|
|
19331
|
+
}
|
|
19332
|
+
]
|
|
19333
|
+
}
|
|
19334
|
+
]
|
|
19335
|
+
};
|
|
19336
|
+
}
|
|
19337
|
+
buildMetrics(turn) {
|
|
19338
|
+
const start = turn.startMs;
|
|
19339
|
+
const end = turn.endMs ?? this.now();
|
|
19340
|
+
const metrics = [];
|
|
19341
|
+
if (this.turnTokens > 0) {
|
|
19342
|
+
metrics.push({
|
|
19343
|
+
name: "bandit.llm.tokens",
|
|
19344
|
+
sum: {
|
|
19345
|
+
aggregationTemporality: 1,
|
|
19346
|
+
isMonotonic: true,
|
|
19347
|
+
dataPoints: [sumPoint(this.turnTokens, { type: "output", "gen_ai.request.model": this.model }, start, end)]
|
|
19348
|
+
}
|
|
19349
|
+
});
|
|
19350
|
+
}
|
|
19351
|
+
if (this.ttftSeconds !== null) {
|
|
19352
|
+
metrics.push({
|
|
19353
|
+
name: "bandit.llm.ttft",
|
|
19354
|
+
unit: "s",
|
|
19355
|
+
histogram: {
|
|
19356
|
+
aggregationTemporality: 1,
|
|
19357
|
+
dataPoints: [histogramPoint(this.ttftSeconds, TTFT_BUCKETS, { "gen_ai.request.model": this.model }, start, end)]
|
|
19358
|
+
}
|
|
19359
|
+
});
|
|
19360
|
+
}
|
|
19361
|
+
metrics.push({
|
|
19362
|
+
name: "bandit.turn.duration",
|
|
19363
|
+
unit: "s",
|
|
19364
|
+
histogram: {
|
|
19365
|
+
aggregationTemporality: 1,
|
|
19366
|
+
dataPoints: [histogramPoint((end - start) / 1e3, DURATION_BUCKETS, { "gen_ai.request.model": this.model }, start, end)]
|
|
19367
|
+
}
|
|
19368
|
+
});
|
|
19369
|
+
return {
|
|
19370
|
+
resourceMetrics: [
|
|
19371
|
+
{
|
|
19372
|
+
resource: { attributes: toAttrs({ "service.name": this.cfg.serviceName }) },
|
|
19373
|
+
scopeMetrics: [{ scope: { name: this.cfg.serviceName }, metrics }]
|
|
19374
|
+
}
|
|
19375
|
+
]
|
|
19376
|
+
};
|
|
19377
|
+
}
|
|
19378
|
+
async post(path, body) {
|
|
19379
|
+
const doFetch = globalThis.fetch;
|
|
19380
|
+
if (!doFetch) return;
|
|
19381
|
+
const ctrl = new AbortController();
|
|
19382
|
+
const timer = setTimeout(() => ctrl.abort(), 4e3);
|
|
19383
|
+
try {
|
|
19384
|
+
await doFetch(`${this.cfg.endpoint}${path}`, {
|
|
19385
|
+
method: "POST",
|
|
19386
|
+
headers: { "Content-Type": "application/json", ...this.cfg.headers },
|
|
19387
|
+
body: JSON.stringify(body),
|
|
19388
|
+
signal: ctrl.signal
|
|
19389
|
+
});
|
|
19390
|
+
} catch (e) {
|
|
19391
|
+
debugLogger.debug("[telemetry] OTLP post failed", {
|
|
19392
|
+
path,
|
|
19393
|
+
error: e instanceof Error ? e.message : String(e)
|
|
19394
|
+
});
|
|
19395
|
+
} finally {
|
|
19396
|
+
clearTimeout(timer);
|
|
19397
|
+
}
|
|
19398
|
+
}
|
|
19399
|
+
};
|
|
19400
|
+
}
|
|
19401
|
+
});
|
|
19402
|
+
|
|
19403
|
+
// src/services/telemetry/index.ts
|
|
19404
|
+
function syncTelemetry() {
|
|
19405
|
+
try {
|
|
19406
|
+
const settings = usePackageSettingsStore.getState().settings;
|
|
19407
|
+
const cfg = resolveTelemetryConfig({
|
|
19408
|
+
telemetry: settings?.telemetry,
|
|
19409
|
+
banditApiKey: authenticationService.getToken() ?? void 0
|
|
19410
|
+
});
|
|
19411
|
+
active = cfg ? new TelemetryExporter(cfg) : null;
|
|
19412
|
+
} catch {
|
|
19413
|
+
active = null;
|
|
19414
|
+
}
|
|
19415
|
+
return active !== null;
|
|
19416
|
+
}
|
|
19417
|
+
function telemetryStartTurn(goal, model) {
|
|
19418
|
+
active?.startTurn(goal, model);
|
|
19419
|
+
}
|
|
19420
|
+
function telemetryEvent(type, payload) {
|
|
19421
|
+
active?.onEvent(type, payload);
|
|
19422
|
+
}
|
|
19423
|
+
function telemetryEndTurn(outcome) {
|
|
19424
|
+
void active?.endTurn(outcome);
|
|
19425
|
+
}
|
|
19426
|
+
var active;
|
|
19427
|
+
var init_telemetry = __esm({
|
|
19428
|
+
"src/services/telemetry/index.ts"() {
|
|
19429
|
+
"use strict";
|
|
19430
|
+
init_otlpExporter();
|
|
19431
|
+
init_packageSettingsStore();
|
|
19432
|
+
init_authenticationService();
|
|
19433
|
+
init_otlpExporter();
|
|
19434
|
+
active = null;
|
|
19435
|
+
}
|
|
19436
|
+
});
|
|
19437
|
+
|
|
19438
|
+
// src/store/engineStore.ts
|
|
19439
|
+
var import_zustand15, STORAGE_KEY2, readStored, useEngineStore;
|
|
19440
|
+
var init_engineStore = __esm({
|
|
19441
|
+
"src/store/engineStore.ts"() {
|
|
19442
|
+
"use strict";
|
|
19443
|
+
import_zustand15 = require("zustand");
|
|
19444
|
+
init_packageSettingsStore();
|
|
19445
|
+
init_authenticationService();
|
|
19446
|
+
init_debugLogger();
|
|
19447
|
+
STORAGE_KEY2 = "bandit.selectedEngine";
|
|
19448
|
+
readStored = () => {
|
|
19449
|
+
try {
|
|
19450
|
+
return typeof window !== "undefined" ? window.localStorage.getItem(STORAGE_KEY2) : null;
|
|
19451
|
+
} catch {
|
|
19452
|
+
return null;
|
|
19453
|
+
}
|
|
19454
|
+
};
|
|
19455
|
+
useEngineStore = (0, import_zustand15.create)((set, get) => ({
|
|
19456
|
+
selectedEngine: readStored(),
|
|
19457
|
+
engines: [],
|
|
19458
|
+
loaded: false,
|
|
19459
|
+
setSelectedEngine: (id) => {
|
|
19460
|
+
set({ selectedEngine: id });
|
|
19461
|
+
try {
|
|
19462
|
+
window.localStorage.setItem(STORAGE_KEY2, id);
|
|
19463
|
+
} catch {
|
|
19464
|
+
}
|
|
19465
|
+
},
|
|
19466
|
+
getSelectedEngine: () => get().selectedEngine || usePackageSettingsStore.getState().settings?.defaultModel || "bandit-core",
|
|
19467
|
+
fetchEngines: async () => {
|
|
19468
|
+
const settings = usePackageSettingsStore.getState().settings;
|
|
19469
|
+
const base = settings?.gatewayApiUrl?.replace(/\/$/, "") ?? "";
|
|
19470
|
+
if (!base || settings?.playgroundMode || base.toLowerCase().startsWith("playground://")) {
|
|
19471
|
+
set({ loaded: true });
|
|
19472
|
+
return;
|
|
19473
|
+
}
|
|
19474
|
+
try {
|
|
19475
|
+
const headers = { "Content-Type": "application/json" };
|
|
19476
|
+
const token = authenticationService.getToken();
|
|
19477
|
+
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
19478
|
+
const res = await fetch(`${base}/models`, { headers });
|
|
19479
|
+
const data = await res.json();
|
|
19480
|
+
if (res.ok && Array.isArray(data?.models)) {
|
|
19481
|
+
set({ engines: data.models, loaded: true });
|
|
19482
|
+
} else {
|
|
19483
|
+
set({ loaded: true });
|
|
19484
|
+
}
|
|
19485
|
+
} catch (error) {
|
|
19486
|
+
debugLogger.error("Failed to fetch engines", {
|
|
19487
|
+
error: error instanceof Error ? error.message : String(error)
|
|
19488
|
+
});
|
|
19489
|
+
set({ loaded: true });
|
|
19490
|
+
}
|
|
19491
|
+
}
|
|
19492
|
+
}));
|
|
19493
|
+
}
|
|
19494
|
+
});
|
|
19495
|
+
|
|
19110
19496
|
// src/chat/hooks/useMemoryEnhancer.tsx
|
|
19111
19497
|
var import_rxjs20, MEMORY_LIMIT, MIN_MEMORY_WORDS, MERGE_THRESHOLD, REJECT_ECHO_THRESHOLD, REJECT_DUPLICATE_THRESHOLD, CONTEXTUAL_DIVERGENCE_THRESHOLD, normalizeText, isStructurallyDuplicate, isAboutBandit, hasEngagementValue, isMemoryTooShortOrGeneric, isPersonalText, mergeMemory, isVoiceShifted, sanitizeMemory, sanitizeMemoryText, shouldAcceptMemory, isContextuallyDivergent, useMemoryEnhancer;
|
|
19112
19498
|
var init_useMemoryEnhancer = __esm({
|
|
@@ -19848,12 +20234,13 @@ var init_useAIProvider = __esm({
|
|
|
19848
20234
|
import_react22 = require("react");
|
|
19849
20235
|
init_knowledgeStore();
|
|
19850
20236
|
init_aiProviderStore();
|
|
20237
|
+
init_telemetry();
|
|
20238
|
+
init_engineStore();
|
|
19851
20239
|
init_conversationStore();
|
|
19852
20240
|
init_useMemoryEnhancer();
|
|
19853
20241
|
init_useVectorStore();
|
|
19854
20242
|
init_embeddingService();
|
|
19855
20243
|
init_useMoodEngine();
|
|
19856
|
-
init_packageSettingsStore();
|
|
19857
20244
|
init_prompts();
|
|
19858
20245
|
init_preferencesStore();
|
|
19859
20246
|
init_mcp();
|
|
@@ -20217,7 +20604,7 @@ var init_useAIProvider = __esm({
|
|
|
20217
20604
|
question: pendingQuestion,
|
|
20218
20605
|
images: pendingImages
|
|
20219
20606
|
});
|
|
20220
|
-
const modelName =
|
|
20607
|
+
const modelName = useEngineStore.getState().getSelectedEngine();
|
|
20221
20608
|
const CONFIG = modelConfigs[modelName] ?? modelConfigs["bandit-core:4b-it-qat"];
|
|
20222
20609
|
const base64Images = imageList.map((img) => img.split(",")[1]);
|
|
20223
20610
|
const latestEntries = history.slice(-CONFIG.historyMessages);
|
|
@@ -20612,6 +20999,9 @@ ${protocol}`;
|
|
|
20612
20999
|
setStreamBuffer(latestDisplayMessage);
|
|
20613
21000
|
}, delay);
|
|
20614
21001
|
};
|
|
21002
|
+
syncTelemetry();
|
|
21003
|
+
telemetryStartTurn(question, modelName);
|
|
21004
|
+
telemetryEvent("tool_loop:llm_start");
|
|
20615
21005
|
const stream = provider.chat(request);
|
|
20616
21006
|
const initialPlaceholderQuestion = lastEntry?.question;
|
|
20617
21007
|
lastPartialRef.current = { text: "", images: [...imageList], usedDocs, question };
|
|
@@ -20625,7 +21015,10 @@ ${protocol}`;
|
|
|
20625
21015
|
const sub = stream.subscribe({
|
|
20626
21016
|
next: (data) => {
|
|
20627
21017
|
if (!data?.message?.content && !data?.message?.tool_calls) return;
|
|
20628
|
-
if (data.message.content)
|
|
21018
|
+
if (data.message.content) {
|
|
21019
|
+
fullMessage += data.message.content;
|
|
21020
|
+
telemetryEvent("tool_loop:llm_chunk", { chunk: data.message.content });
|
|
21021
|
+
}
|
|
20629
21022
|
const inThinkBlock = /<think>/.test(fullMessage) && !/<think>[\s\S]*<\/think>/.test(fullMessage);
|
|
20630
21023
|
setIsThinking?.(inThinkBlock);
|
|
20631
21024
|
const visibleMessage = stripThinking(fullMessage);
|
|
@@ -20657,6 +21050,7 @@ ${protocol}`;
|
|
|
20657
21050
|
setIsThinking?.(false);
|
|
20658
21051
|
setPendingMessage(null);
|
|
20659
21052
|
setLogoVisible(false);
|
|
21053
|
+
telemetryEndTurn({ error: err?.message || "stream error" });
|
|
20660
21054
|
if (onError) {
|
|
20661
21055
|
onError(err);
|
|
20662
21056
|
}
|
|
@@ -20664,6 +21058,7 @@ ${protocol}`;
|
|
|
20664
21058
|
complete: async () => {
|
|
20665
21059
|
try {
|
|
20666
21060
|
setIsThinking?.(false);
|
|
21061
|
+
telemetryEvent("tool_loop:llm_response", { responseLength: fullMessage.length });
|
|
20667
21062
|
latestDisplayMessage = stripThinking(fullMessage);
|
|
20668
21063
|
if (!sawToolBlock) {
|
|
20669
21064
|
flushNow();
|
|
@@ -20672,6 +21067,7 @@ ${protocol}`;
|
|
|
20672
21067
|
let enhancedMessage = fullMessage;
|
|
20673
21068
|
const summarizableResults = [];
|
|
20674
21069
|
const inlineImageBlocks = [];
|
|
21070
|
+
const collectedSources = [];
|
|
20675
21071
|
if (toolCallMatches && toolCallMatches.length > 0 && mcpToolsAvailable) {
|
|
20676
21072
|
debugLogger.info("Detected tool calls in AI response", {
|
|
20677
21073
|
toolCallCount: toolCallMatches.length,
|
|
@@ -20706,10 +21102,21 @@ ${protocol}`;
|
|
|
20706
21102
|
});
|
|
20707
21103
|
const placeholderToken = `<<TOOL_LOADING_${functionName}_${Math.random().toString(36).slice(2)}>>`;
|
|
20708
21104
|
enhancedMessage = enhancedMessage.replace(match, placeholderToken);
|
|
21105
|
+
clearFlushTimer();
|
|
21106
|
+
const toolStatus = functionName === "web_search" || functionName === "web-search" ? "Searching the web\u2026" : functionName === "web_fetch" || functionName === "web-fetch" ? "Reading the page\u2026" : functionName === "image_generation" || functionName === "image-generation" ? "Generating the image\u2026" : "Working on it\u2026";
|
|
21107
|
+
const toolPreamble = stripToolBlocks(fullMessage).trim();
|
|
21108
|
+
setStreamBuffer(toolPreamble ? `${toolPreamble}
|
|
21109
|
+
|
|
21110
|
+
_${toolStatus}_` : `_${toolStatus}_`);
|
|
21111
|
+
telemetryEvent("tool_loop:tool_execute", { name: functionName, params: parsedParams });
|
|
20709
21112
|
const result = await executeMCPTool({
|
|
20710
21113
|
toolName: functionName,
|
|
20711
21114
|
parameters: parsedParams
|
|
20712
21115
|
});
|
|
21116
|
+
telemetryEvent(result.success ? "tool_loop:tool_result" : "tool_loop:tool_error", {
|
|
21117
|
+
name: functionName,
|
|
21118
|
+
isError: !result.success
|
|
21119
|
+
});
|
|
20713
21120
|
let resultText = "";
|
|
20714
21121
|
if (result.success) {
|
|
20715
21122
|
if (functionName === "web_search" || functionName === "web-search") {
|
|
@@ -20723,18 +21130,16 @@ ${protocol}`;
|
|
|
20723
21130
|
blocks.push(
|
|
20724
21131
|
items.slice(0, 6).map((item, index) => {
|
|
20725
21132
|
const title = item.title?.trim() || "Untitled";
|
|
20726
|
-
const url = item.url?.trim();
|
|
21133
|
+
const url = item.url?.trim() || "";
|
|
20727
21134
|
const snippet = item.content?.trim();
|
|
20728
|
-
|
|
20729
|
-
|
|
20730
|
-
${url}`;
|
|
21135
|
+
if (url) collectedSources.push({ title, url });
|
|
21136
|
+
let line = url ? `${index + 1}. [${title}](${url})` : `${index + 1}. ${title}`;
|
|
20731
21137
|
if (snippet) {
|
|
20732
21138
|
const truncated = snippet.length > 300 ? `${snippet.slice(0, 300)}\u2026` : snippet;
|
|
20733
|
-
line += `
|
|
20734
|
-
${truncated}`;
|
|
21139
|
+
line += ` \u2014 ${truncated}`;
|
|
20735
21140
|
}
|
|
20736
21141
|
return line;
|
|
20737
|
-
}).join("\n
|
|
21142
|
+
}).join("\n")
|
|
20738
21143
|
);
|
|
20739
21144
|
}
|
|
20740
21145
|
resultText = blocks.length ? blocks.join("\n\n") : `No results found${search.query ? ` for "${search.query}"` : ""}.`;
|
|
@@ -20816,7 +21221,7 @@ ${r.output}`).join("\n\n");
|
|
|
20816
21221
|
|
|
20817
21222
|
${toolResultsText}
|
|
20818
21223
|
|
|
20819
|
-
Using these results together with your own knowledge, answer my original question concisely and in a natural, well-formatted way.
|
|
21224
|
+
Using these results together with your own knowledge, answer my original question concisely and in a natural, well-formatted way. Reference sources by name where relevant, but do NOT paste raw URLs or a list of links \u2014 a clean Sources section is added automatically below your answer. Do NOT output tool_code or call any tools again.`
|
|
20820
21225
|
}
|
|
20821
21226
|
];
|
|
20822
21227
|
const summaryRequest = {
|
|
@@ -20830,7 +21235,7 @@ Using these results together with your own knowledge, answer my original questio
|
|
|
20830
21235
|
setStreamBuffer(
|
|
20831
21236
|
summaryPreamble ? `${summaryPreamble}
|
|
20832
21237
|
|
|
20833
|
-
|
|
21238
|
+
_Writing the answer\u2026_` : "_Writing the answer\u2026_"
|
|
20834
21239
|
);
|
|
20835
21240
|
const summaryText = await new Promise((resolve) => {
|
|
20836
21241
|
let acc = "";
|
|
@@ -20871,7 +21276,18 @@ _Working on it\u2026_` : "_Working on it\u2026_"
|
|
|
20871
21276
|
}, 3e4);
|
|
20872
21277
|
});
|
|
20873
21278
|
if (summaryText.trim()) {
|
|
20874
|
-
|
|
21279
|
+
const sourcesMd = collectedSources.length ? `
|
|
21280
|
+
|
|
21281
|
+
**Sources**
|
|
21282
|
+
${collectedSources.slice(0, 6).map((s) => {
|
|
21283
|
+
let domain = s.url;
|
|
21284
|
+
try {
|
|
21285
|
+
domain = new URL(s.url).hostname.replace(/^www\./, "");
|
|
21286
|
+
} catch {
|
|
21287
|
+
}
|
|
21288
|
+
return `- [${s.title || domain}](${s.url}) \u2014 ${domain}`;
|
|
21289
|
+
}).join("\n")}` : "";
|
|
21290
|
+
enhancedMessage = summaryText + sourcesMd + (inlineImageBlocks.length ? `
|
|
20875
21291
|
|
|
20876
21292
|
${inlineImageBlocks.join("\n\n")}` : "");
|
|
20877
21293
|
}
|
|
@@ -20920,6 +21336,7 @@ ${inlineImageBlocks.join("\n\n")}` : "");
|
|
|
20920
21336
|
}
|
|
20921
21337
|
setInputValue("");
|
|
20922
21338
|
setPastedImages([]);
|
|
21339
|
+
telemetryEndTurn();
|
|
20923
21340
|
setTimeout(() => {
|
|
20924
21341
|
clearFlushTimer();
|
|
20925
21342
|
setPendingMessage(null);
|
|
@@ -20935,6 +21352,7 @@ ${inlineImageBlocks.join("\n\n")}` : "");
|
|
|
20935
21352
|
overrideComponentStatus("Idle");
|
|
20936
21353
|
setIsSubmitting(false);
|
|
20937
21354
|
setIsStreaming(false);
|
|
21355
|
+
telemetryEndTurn({ error: e instanceof Error ? e.message : String(e) });
|
|
20938
21356
|
}
|
|
20939
21357
|
}
|
|
20940
21358
|
});
|
|
@@ -23793,10 +24211,10 @@ var init_enhanced_mobile_conversations_modal = __esm({
|
|
|
23793
24211
|
(0, import_react30.useEffect)(() => {
|
|
23794
24212
|
setDeletedConversationIds((prev) => {
|
|
23795
24213
|
let changed = false;
|
|
23796
|
-
const
|
|
24214
|
+
const active2 = new Set(conversations.map((conv) => conv.id));
|
|
23797
24215
|
const next = /* @__PURE__ */ new Set();
|
|
23798
24216
|
prev.forEach((id) => {
|
|
23799
|
-
if (
|
|
24217
|
+
if (active2.has(id)) {
|
|
23800
24218
|
next.add(id);
|
|
23801
24219
|
} else {
|
|
23802
24220
|
changed = true;
|
|
@@ -24418,6 +24836,7 @@ var init_chat_app_bar = __esm({
|
|
|
24418
24836
|
init_packageSettingsStore();
|
|
24419
24837
|
init_useFeatures();
|
|
24420
24838
|
init_conversationSyncStore();
|
|
24839
|
+
init_engineStore();
|
|
24421
24840
|
import_shallow2 = require("zustand/shallow");
|
|
24422
24841
|
import_jsx_runtime24 = require("react/jsx-runtime");
|
|
24423
24842
|
CDN_BASE = "https://cdn.burtson.ai/";
|
|
@@ -24467,6 +24886,7 @@ var init_chat_app_bar = __esm({
|
|
|
24467
24886
|
menuText
|
|
24468
24887
|
} = theme.palette.chat.appBar;
|
|
24469
24888
|
const [modelAnchorEl, setModelAnchorEl] = (0, import_react31.useState)(null);
|
|
24889
|
+
const [engineAnchorEl, setEngineAnchorEl] = (0, import_react31.useState)(null);
|
|
24470
24890
|
const [voiceAnchorEl, setVoiceAnchorEl] = (0, import_react31.useState)(null);
|
|
24471
24891
|
const [modalOpen, setModalOpen] = (0, import_react31.useState)(false);
|
|
24472
24892
|
const [confirmModelChangeOpen, setConfirmModelChangeOpen] = (0, import_react31.useState)(false);
|
|
@@ -24587,6 +25007,14 @@ var init_chat_app_bar = __esm({
|
|
|
24587
25007
|
const selectedModel = useModelStore((s) => s.selectedModel);
|
|
24588
25008
|
const currentModel = useModelStore((s) => s.availableModels.find((m) => m.name === selectedModel));
|
|
24589
25009
|
const currentAvatar = currentModel?.avatarBase64 || modelAvatars2[selectedModel] || banditHead3;
|
|
25010
|
+
const engines = useEngineStore((s) => s.engines);
|
|
25011
|
+
const selectedEngine = useEngineStore((s) => s.selectedEngine);
|
|
25012
|
+
const effectiveEngineId = selectedEngine || usePackageSettingsStore.getState().settings?.defaultModel || "bandit-core";
|
|
25013
|
+
const currentEngine = engines.find((e) => e.id === effectiveEngineId);
|
|
25014
|
+
const engineLabel = currentEngine?.displayName?.replace(/^Bandit /, "") || "Engine";
|
|
25015
|
+
(0, import_react31.useEffect)(() => {
|
|
25016
|
+
useEngineStore.getState().fetchEngines();
|
|
25017
|
+
}, []);
|
|
24590
25018
|
const pendingModelAvatar = useModelStore.getState().availableModels.find((m) => m.name === pendingModel)?.avatarBase64 || modelAvatars2[pendingModel || ""] || banditHead3;
|
|
24591
25019
|
const resolvedHomeUrl = preferences.homeUrl?.trim() || packageSettings?.homeUrl?.trim() || "";
|
|
24592
25020
|
const homeTooltip = (() => {
|
|
@@ -24899,6 +25327,84 @@ var init_chat_app_bar = __esm({
|
|
|
24899
25327
|
)
|
|
24900
25328
|
}
|
|
24901
25329
|
) }),
|
|
25330
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_material23.Tooltip, { title: `Engine: ${currentEngine?.displayName ?? effectiveEngineId}`, arrow: true, children: /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
|
|
25331
|
+
import_material23.IconButton,
|
|
25332
|
+
{
|
|
25333
|
+
onClick: (e) => setEngineAnchorEl(e.currentTarget),
|
|
25334
|
+
sx: pillButtonStyles,
|
|
25335
|
+
"aria-label": `Change base model (engine). Currently ${effectiveEngineId}`,
|
|
25336
|
+
children: [
|
|
25337
|
+
currentEngine?.cloud ? /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(CloudDoneIcon, { fontSize: "small" }) : /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(CloudOffIcon, { fontSize: "small" }),
|
|
25338
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_material23.Typography, { variant: "caption", sx: { ml: 0.75, fontWeight: 600, whiteSpace: "nowrap" }, children: engineLabel })
|
|
25339
|
+
]
|
|
25340
|
+
}
|
|
25341
|
+
) }),
|
|
25342
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
|
|
25343
|
+
import_material23.Menu,
|
|
25344
|
+
{
|
|
25345
|
+
anchorEl: engineAnchorEl,
|
|
25346
|
+
open: Boolean(engineAnchorEl),
|
|
25347
|
+
onClose: () => setEngineAnchorEl(null),
|
|
25348
|
+
transformOrigin: { horizontal: "right", vertical: "top" },
|
|
25349
|
+
anchorOrigin: { horizontal: "right", vertical: "bottom" },
|
|
25350
|
+
children: [
|
|
25351
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_material23.Typography, { variant: "overline", sx: { px: 2, color: theme.palette.text.secondary }, children: "Engine \xB7 base model" }),
|
|
25352
|
+
engines.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_material23.MenuItem, { disabled: true, children: /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_material23.Typography, { variant: "body2", children: "No engines available" }) }),
|
|
25353
|
+
engines.map((engine) => {
|
|
25354
|
+
const badges = [
|
|
25355
|
+
engine.vision && "vision",
|
|
25356
|
+
engine.tools && "tools",
|
|
25357
|
+
engine.thinking && "thinking",
|
|
25358
|
+
engine.cloud && "cloud"
|
|
25359
|
+
].filter(Boolean);
|
|
25360
|
+
return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
|
|
25361
|
+
import_material23.MenuItem,
|
|
25362
|
+
{
|
|
25363
|
+
selected: engine.id === effectiveEngineId,
|
|
25364
|
+
disabled: !engine.available,
|
|
25365
|
+
onClick: () => {
|
|
25366
|
+
useEngineStore.getState().setSelectedEngine(engine.id);
|
|
25367
|
+
setEngineAnchorEl(null);
|
|
25368
|
+
},
|
|
25369
|
+
sx: {
|
|
25370
|
+
display: "flex",
|
|
25371
|
+
flexDirection: "column",
|
|
25372
|
+
alignItems: "flex-start",
|
|
25373
|
+
gap: 0.5,
|
|
25374
|
+
py: 1,
|
|
25375
|
+
px: 2,
|
|
25376
|
+
maxWidth: 360,
|
|
25377
|
+
whiteSpace: "normal"
|
|
25378
|
+
},
|
|
25379
|
+
children: [
|
|
25380
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_material23.Box, { sx: { display: "flex", alignItems: "center", gap: 1, width: "100%" }, children: [
|
|
25381
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_material23.Typography, { variant: "body2", sx: { fontWeight: 600, flex: 1 }, children: engine.displayName }),
|
|
25382
|
+
engine.id === effectiveEngineId && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_material23.Box, { sx: { width: 8, height: 8, borderRadius: "50%", bgcolor: theme.palette.primary.main } })
|
|
25383
|
+
] }),
|
|
25384
|
+
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_material23.Typography, { variant: "caption", sx: { color: theme.palette.text.secondary }, children: engine.available ? engine.description : engine.unavailableReason || "Unavailable" }),
|
|
25385
|
+
badges.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(import_material23.Box, { sx: { display: "flex", gap: 0.5, flexWrap: "wrap", mt: 0.25 }, children: badges.map((b) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
25386
|
+
import_material23.Box,
|
|
25387
|
+
{
|
|
25388
|
+
sx: {
|
|
25389
|
+
fontSize: "0.65rem",
|
|
25390
|
+
px: 0.75,
|
|
25391
|
+
py: 0.1,
|
|
25392
|
+
borderRadius: 1,
|
|
25393
|
+
bgcolor: theme.palette.primary.main + "22",
|
|
25394
|
+
color: theme.palette.primary.main
|
|
25395
|
+
},
|
|
25396
|
+
children: b
|
|
25397
|
+
},
|
|
25398
|
+
b
|
|
25399
|
+
)) })
|
|
25400
|
+
]
|
|
25401
|
+
},
|
|
25402
|
+
engine.id
|
|
25403
|
+
);
|
|
25404
|
+
})
|
|
25405
|
+
]
|
|
25406
|
+
}
|
|
25407
|
+
),
|
|
24902
25408
|
/* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
|
|
24903
25409
|
import_material23.Menu,
|
|
24904
25410
|
{
|