@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
|
@@ -19448,7 +19448,11 @@ ${listMarkdown}`;
|
|
|
19448
19448
|
href,
|
|
19449
19449
|
target: "_blank",
|
|
19450
19450
|
rel: "noopener noreferrer",
|
|
19451
|
-
style: {
|
|
19451
|
+
style: {
|
|
19452
|
+
color: theme.palette.info?.main ?? theme.palette.primary.main,
|
|
19453
|
+
textDecoration: "underline",
|
|
19454
|
+
textUnderlineOffset: "2px"
|
|
19455
|
+
},
|
|
19452
19456
|
...props,
|
|
19453
19457
|
children
|
|
19454
19458
|
}
|
|
@@ -21346,6 +21350,388 @@ ${sanitize(
|
|
|
21346
21350
|
}
|
|
21347
21351
|
});
|
|
21348
21352
|
|
|
21353
|
+
// src/services/telemetry/otlpExporter.ts
|
|
21354
|
+
function redactSecretsString(s) {
|
|
21355
|
+
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]");
|
|
21356
|
+
}
|
|
21357
|
+
function resolveTelemetryConfig(opts) {
|
|
21358
|
+
if (!opts.telemetry?.enabled) return null;
|
|
21359
|
+
const endpoint = (opts.telemetry.endpoint ?? "https://otlp.burtson.ai").replace(/\/+$/, "");
|
|
21360
|
+
const headers = { ...opts.telemetry.headers ?? {} };
|
|
21361
|
+
const hasAuth = Object.keys(headers).some((k) => k.toLowerCase() === "authorization");
|
|
21362
|
+
if (!hasAuth && opts.banditApiKey) {
|
|
21363
|
+
headers["Authorization"] = `Bearer ${opts.banditApiKey}`;
|
|
21364
|
+
}
|
|
21365
|
+
const mode = opts.telemetry.mode ?? "metrics+traces";
|
|
21366
|
+
return { endpoint, headers, mode, serviceName: opts.telemetry.serviceName ?? "bandit-web" };
|
|
21367
|
+
}
|
|
21368
|
+
function toAttrs(rec) {
|
|
21369
|
+
const out = [];
|
|
21370
|
+
for (const [key, v] of Object.entries(rec)) {
|
|
21371
|
+
if (v === void 0 || v === null || v === "") continue;
|
|
21372
|
+
if (typeof v === "boolean") out.push({ key, value: { boolValue: v } });
|
|
21373
|
+
else if (typeof v === "number")
|
|
21374
|
+
out.push({ key, value: Number.isInteger(v) ? { intValue: String(v) } : { doubleValue: v } });
|
|
21375
|
+
else out.push({ key, value: { stringValue: v } });
|
|
21376
|
+
}
|
|
21377
|
+
return out;
|
|
21378
|
+
}
|
|
21379
|
+
function hex(bytes) {
|
|
21380
|
+
const arr = new Uint8Array(bytes);
|
|
21381
|
+
if (webCrypto?.getRandomValues) webCrypto.getRandomValues(arr);
|
|
21382
|
+
return Array.from(arr, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
21383
|
+
}
|
|
21384
|
+
function histogramPoint(value, bounds, attrs, startMs, endMs) {
|
|
21385
|
+
const counts = new Array(bounds.length + 1).fill(0);
|
|
21386
|
+
let idx = bounds.findIndex((b) => value <= b);
|
|
21387
|
+
if (idx === -1) idx = bounds.length;
|
|
21388
|
+
counts[idx] = 1;
|
|
21389
|
+
return {
|
|
21390
|
+
attributes: toAttrs(attrs),
|
|
21391
|
+
startTimeUnixNano: nano(startMs),
|
|
21392
|
+
timeUnixNano: nano(endMs),
|
|
21393
|
+
count: "1",
|
|
21394
|
+
sum: value,
|
|
21395
|
+
bucketCounts: counts.map(String),
|
|
21396
|
+
explicitBounds: bounds
|
|
21397
|
+
};
|
|
21398
|
+
}
|
|
21399
|
+
function sumPoint(value, attrs, startMs, endMs) {
|
|
21400
|
+
return {
|
|
21401
|
+
attributes: toAttrs(attrs),
|
|
21402
|
+
startTimeUnixNano: nano(startMs),
|
|
21403
|
+
timeUnixNano: nano(endMs),
|
|
21404
|
+
asInt: String(Math.round(value))
|
|
21405
|
+
};
|
|
21406
|
+
}
|
|
21407
|
+
var webCrypto, nano, TTFT_BUCKETS, DURATION_BUCKETS, clip, TelemetryExporter;
|
|
21408
|
+
var init_otlpExporter = __esm({
|
|
21409
|
+
"src/services/telemetry/otlpExporter.ts"() {
|
|
21410
|
+
"use strict";
|
|
21411
|
+
init_debugLogger();
|
|
21412
|
+
webCrypto = globalThis.crypto;
|
|
21413
|
+
nano = (ms) => String(Math.round(ms * 1e6));
|
|
21414
|
+
TTFT_BUCKETS = [0.1, 0.25, 0.5, 1, 2, 5, 10, 30];
|
|
21415
|
+
DURATION_BUCKETS = [0.5, 1, 2, 5, 10, 30, 60, 120, 300];
|
|
21416
|
+
clip = (s, n = 120) => redactSecretsString(s.slice(0, n)).slice(0, n);
|
|
21417
|
+
TelemetryExporter = class {
|
|
21418
|
+
cfg;
|
|
21419
|
+
now;
|
|
21420
|
+
traceId = "";
|
|
21421
|
+
turn = null;
|
|
21422
|
+
llm = null;
|
|
21423
|
+
llmFirstChunkMs = 0;
|
|
21424
|
+
openTools = [];
|
|
21425
|
+
completedSpans = [];
|
|
21426
|
+
model = "";
|
|
21427
|
+
turnChunkChars = 0;
|
|
21428
|
+
turnTokens = 0;
|
|
21429
|
+
ttftSeconds = null;
|
|
21430
|
+
constructor(cfg, opts) {
|
|
21431
|
+
this.cfg = cfg;
|
|
21432
|
+
this.now = opts?.now ?? (() => Date.now());
|
|
21433
|
+
}
|
|
21434
|
+
startTurn(goal, model) {
|
|
21435
|
+
this.traceId = hex(16);
|
|
21436
|
+
this.model = model;
|
|
21437
|
+
this.turnChunkChars = 0;
|
|
21438
|
+
this.turnTokens = 0;
|
|
21439
|
+
this.ttftSeconds = null;
|
|
21440
|
+
this.llm = null;
|
|
21441
|
+
this.llmFirstChunkMs = 0;
|
|
21442
|
+
this.openTools = [];
|
|
21443
|
+
this.completedSpans = [];
|
|
21444
|
+
this.turn = {
|
|
21445
|
+
spanId: hex(8),
|
|
21446
|
+
name: "agent.turn",
|
|
21447
|
+
startMs: this.now(),
|
|
21448
|
+
attrs: { "gen_ai.request.model": model, "bandit.turn.goal": clip(goal, 160) }
|
|
21449
|
+
};
|
|
21450
|
+
}
|
|
21451
|
+
/** Fed from the chat turn lifecycle. Best-effort; swallows bad payloads. */
|
|
21452
|
+
onEvent(type, payload) {
|
|
21453
|
+
if (!this.turn) return;
|
|
21454
|
+
try {
|
|
21455
|
+
const p = payload ?? {};
|
|
21456
|
+
switch (type) {
|
|
21457
|
+
case "tool_loop:llm_start":
|
|
21458
|
+
this.llm = {
|
|
21459
|
+
spanId: hex(8),
|
|
21460
|
+
parentSpanId: this.turn.spanId,
|
|
21461
|
+
name: "llm.generate",
|
|
21462
|
+
startMs: this.now(),
|
|
21463
|
+
attrs: { "gen_ai.request.model": this.model }
|
|
21464
|
+
};
|
|
21465
|
+
this.llmFirstChunkMs = 0;
|
|
21466
|
+
break;
|
|
21467
|
+
case "tool_loop:llm_chunk": {
|
|
21468
|
+
const chunk = typeof p.chunk === "string" ? p.chunk : "";
|
|
21469
|
+
if (this.llm && this.llmFirstChunkMs === 0 && chunk.length > 0) {
|
|
21470
|
+
this.llmFirstChunkMs = this.now();
|
|
21471
|
+
const ttft = (this.llmFirstChunkMs - this.llm.startMs) / 1e3;
|
|
21472
|
+
if (this.ttftSeconds === null) this.ttftSeconds = ttft;
|
|
21473
|
+
this.llm.attrs["bandit.llm.ttft_seconds"] = ttft;
|
|
21474
|
+
}
|
|
21475
|
+
this.turnChunkChars += chunk.length;
|
|
21476
|
+
this.turnTokens = Math.floor(this.turnChunkChars / 4);
|
|
21477
|
+
break;
|
|
21478
|
+
}
|
|
21479
|
+
case "tool_loop:llm_response":
|
|
21480
|
+
if (this.llm) {
|
|
21481
|
+
this.llm.endMs = this.now();
|
|
21482
|
+
if (typeof p.responseLength === "number") this.llm.attrs["bandit.llm.response_chars"] = p.responseLength;
|
|
21483
|
+
this.completedSpans.push(this.llm);
|
|
21484
|
+
this.llm = null;
|
|
21485
|
+
}
|
|
21486
|
+
break;
|
|
21487
|
+
case "tool_loop:tool_execute": {
|
|
21488
|
+
const name = typeof p.name === "string" ? p.name : "tool";
|
|
21489
|
+
const params = p.params ?? {};
|
|
21490
|
+
const primary = params.query ?? params.url ?? params.prompt ?? params.topic ?? "";
|
|
21491
|
+
this.openTools.push({
|
|
21492
|
+
spanId: hex(8),
|
|
21493
|
+
parentSpanId: this.turn.spanId,
|
|
21494
|
+
name: `tool.${name}`,
|
|
21495
|
+
startMs: this.now(),
|
|
21496
|
+
attrs: { "bandit.tool.name": name, "bandit.tool.primary": primary ? clip(primary) : void 0 }
|
|
21497
|
+
});
|
|
21498
|
+
break;
|
|
21499
|
+
}
|
|
21500
|
+
case "tool_loop:tool_result":
|
|
21501
|
+
case "tool_loop:tool_error": {
|
|
21502
|
+
const name = typeof p.name === "string" ? p.name : void 0;
|
|
21503
|
+
const span = this.takeOpenTool(name);
|
|
21504
|
+
if (span) {
|
|
21505
|
+
span.endMs = this.now();
|
|
21506
|
+
if (type === "tool_loop:tool_error" || p.isError === true) span.error = "tool error";
|
|
21507
|
+
this.completedSpans.push(span);
|
|
21508
|
+
}
|
|
21509
|
+
break;
|
|
21510
|
+
}
|
|
21511
|
+
}
|
|
21512
|
+
} catch {
|
|
21513
|
+
}
|
|
21514
|
+
}
|
|
21515
|
+
takeOpenTool(name) {
|
|
21516
|
+
if (name) {
|
|
21517
|
+
for (let i = this.openTools.length - 1; i >= 0; i -= 1) {
|
|
21518
|
+
if (this.openTools[i].name === `tool.${name}`) return this.openTools.splice(i, 1)[0];
|
|
21519
|
+
}
|
|
21520
|
+
}
|
|
21521
|
+
return this.openTools.shift();
|
|
21522
|
+
}
|
|
21523
|
+
/** Close the turn, build OTLP traces + metrics, and flush. Never rejects. */
|
|
21524
|
+
async endTurn(outcome) {
|
|
21525
|
+
const turn = this.turn;
|
|
21526
|
+
if (!turn) return;
|
|
21527
|
+
this.turn = null;
|
|
21528
|
+
const end = this.now();
|
|
21529
|
+
if (this.llm && !this.llm.endMs) {
|
|
21530
|
+
this.llm.endMs = end;
|
|
21531
|
+
this.completedSpans.push(this.llm);
|
|
21532
|
+
this.llm = null;
|
|
21533
|
+
}
|
|
21534
|
+
for (const t of this.openTools.splice(0)) {
|
|
21535
|
+
t.endMs = end;
|
|
21536
|
+
t.error = t.error ?? "incomplete";
|
|
21537
|
+
this.completedSpans.push(t);
|
|
21538
|
+
}
|
|
21539
|
+
turn.endMs = end;
|
|
21540
|
+
if (outcome?.error) turn.error = outcome.error;
|
|
21541
|
+
const traceId = this.traceId;
|
|
21542
|
+
const spans = [turn, ...this.completedSpans];
|
|
21543
|
+
const jobs = [];
|
|
21544
|
+
if (this.cfg.mode === "metrics+traces") jobs.push(this.post("/v1/traces", this.buildTraces(traceId, spans)));
|
|
21545
|
+
jobs.push(this.post("/v1/metrics", this.buildMetrics(turn)));
|
|
21546
|
+
try {
|
|
21547
|
+
await Promise.all(jobs);
|
|
21548
|
+
} catch {
|
|
21549
|
+
}
|
|
21550
|
+
}
|
|
21551
|
+
buildTraces(traceId, spans) {
|
|
21552
|
+
return {
|
|
21553
|
+
resourceSpans: [
|
|
21554
|
+
{
|
|
21555
|
+
resource: { attributes: toAttrs({ "service.name": this.cfg.serviceName }) },
|
|
21556
|
+
scopeSpans: [
|
|
21557
|
+
{
|
|
21558
|
+
scope: { name: this.cfg.serviceName },
|
|
21559
|
+
spans: spans.map((s) => ({
|
|
21560
|
+
traceId,
|
|
21561
|
+
spanId: s.spanId,
|
|
21562
|
+
parentSpanId: s.parentSpanId,
|
|
21563
|
+
name: s.name,
|
|
21564
|
+
kind: 1,
|
|
21565
|
+
startTimeUnixNano: nano(s.startMs),
|
|
21566
|
+
endTimeUnixNano: nano(s.endMs ?? s.startMs),
|
|
21567
|
+
attributes: toAttrs(s.attrs),
|
|
21568
|
+
status: s.error ? { code: 2, message: s.error.slice(0, 200) } : { code: 1 }
|
|
21569
|
+
}))
|
|
21570
|
+
}
|
|
21571
|
+
]
|
|
21572
|
+
}
|
|
21573
|
+
]
|
|
21574
|
+
};
|
|
21575
|
+
}
|
|
21576
|
+
buildMetrics(turn) {
|
|
21577
|
+
const start = turn.startMs;
|
|
21578
|
+
const end = turn.endMs ?? this.now();
|
|
21579
|
+
const metrics = [];
|
|
21580
|
+
if (this.turnTokens > 0) {
|
|
21581
|
+
metrics.push({
|
|
21582
|
+
name: "bandit.llm.tokens",
|
|
21583
|
+
sum: {
|
|
21584
|
+
aggregationTemporality: 1,
|
|
21585
|
+
isMonotonic: true,
|
|
21586
|
+
dataPoints: [sumPoint(this.turnTokens, { type: "output", "gen_ai.request.model": this.model }, start, end)]
|
|
21587
|
+
}
|
|
21588
|
+
});
|
|
21589
|
+
}
|
|
21590
|
+
if (this.ttftSeconds !== null) {
|
|
21591
|
+
metrics.push({
|
|
21592
|
+
name: "bandit.llm.ttft",
|
|
21593
|
+
unit: "s",
|
|
21594
|
+
histogram: {
|
|
21595
|
+
aggregationTemporality: 1,
|
|
21596
|
+
dataPoints: [histogramPoint(this.ttftSeconds, TTFT_BUCKETS, { "gen_ai.request.model": this.model }, start, end)]
|
|
21597
|
+
}
|
|
21598
|
+
});
|
|
21599
|
+
}
|
|
21600
|
+
metrics.push({
|
|
21601
|
+
name: "bandit.turn.duration",
|
|
21602
|
+
unit: "s",
|
|
21603
|
+
histogram: {
|
|
21604
|
+
aggregationTemporality: 1,
|
|
21605
|
+
dataPoints: [histogramPoint((end - start) / 1e3, DURATION_BUCKETS, { "gen_ai.request.model": this.model }, start, end)]
|
|
21606
|
+
}
|
|
21607
|
+
});
|
|
21608
|
+
return {
|
|
21609
|
+
resourceMetrics: [
|
|
21610
|
+
{
|
|
21611
|
+
resource: { attributes: toAttrs({ "service.name": this.cfg.serviceName }) },
|
|
21612
|
+
scopeMetrics: [{ scope: { name: this.cfg.serviceName }, metrics }]
|
|
21613
|
+
}
|
|
21614
|
+
]
|
|
21615
|
+
};
|
|
21616
|
+
}
|
|
21617
|
+
async post(path, body) {
|
|
21618
|
+
const doFetch = globalThis.fetch;
|
|
21619
|
+
if (!doFetch) return;
|
|
21620
|
+
const ctrl = new AbortController();
|
|
21621
|
+
const timer = setTimeout(() => ctrl.abort(), 4e3);
|
|
21622
|
+
try {
|
|
21623
|
+
await doFetch(`${this.cfg.endpoint}${path}`, {
|
|
21624
|
+
method: "POST",
|
|
21625
|
+
headers: { "Content-Type": "application/json", ...this.cfg.headers },
|
|
21626
|
+
body: JSON.stringify(body),
|
|
21627
|
+
signal: ctrl.signal
|
|
21628
|
+
});
|
|
21629
|
+
} catch (e) {
|
|
21630
|
+
debugLogger.debug("[telemetry] OTLP post failed", {
|
|
21631
|
+
path,
|
|
21632
|
+
error: e instanceof Error ? e.message : String(e)
|
|
21633
|
+
});
|
|
21634
|
+
} finally {
|
|
21635
|
+
clearTimeout(timer);
|
|
21636
|
+
}
|
|
21637
|
+
}
|
|
21638
|
+
};
|
|
21639
|
+
}
|
|
21640
|
+
});
|
|
21641
|
+
|
|
21642
|
+
// src/services/telemetry/index.ts
|
|
21643
|
+
function syncTelemetry() {
|
|
21644
|
+
try {
|
|
21645
|
+
const settings = usePackageSettingsStore.getState().settings;
|
|
21646
|
+
const cfg = resolveTelemetryConfig({
|
|
21647
|
+
telemetry: settings?.telemetry,
|
|
21648
|
+
banditApiKey: authenticationService.getToken() ?? void 0
|
|
21649
|
+
});
|
|
21650
|
+
active = cfg ? new TelemetryExporter(cfg) : null;
|
|
21651
|
+
} catch {
|
|
21652
|
+
active = null;
|
|
21653
|
+
}
|
|
21654
|
+
return active !== null;
|
|
21655
|
+
}
|
|
21656
|
+
function telemetryStartTurn(goal, model) {
|
|
21657
|
+
active?.startTurn(goal, model);
|
|
21658
|
+
}
|
|
21659
|
+
function telemetryEvent(type, payload) {
|
|
21660
|
+
active?.onEvent(type, payload);
|
|
21661
|
+
}
|
|
21662
|
+
function telemetryEndTurn(outcome) {
|
|
21663
|
+
void active?.endTurn(outcome);
|
|
21664
|
+
}
|
|
21665
|
+
var active;
|
|
21666
|
+
var init_telemetry = __esm({
|
|
21667
|
+
"src/services/telemetry/index.ts"() {
|
|
21668
|
+
"use strict";
|
|
21669
|
+
init_otlpExporter();
|
|
21670
|
+
init_packageSettingsStore();
|
|
21671
|
+
init_authenticationService();
|
|
21672
|
+
init_otlpExporter();
|
|
21673
|
+
active = null;
|
|
21674
|
+
}
|
|
21675
|
+
});
|
|
21676
|
+
|
|
21677
|
+
// src/store/engineStore.ts
|
|
21678
|
+
var import_zustand15, STORAGE_KEY2, readStored, useEngineStore;
|
|
21679
|
+
var init_engineStore = __esm({
|
|
21680
|
+
"src/store/engineStore.ts"() {
|
|
21681
|
+
"use strict";
|
|
21682
|
+
import_zustand15 = require("zustand");
|
|
21683
|
+
init_packageSettingsStore();
|
|
21684
|
+
init_authenticationService();
|
|
21685
|
+
init_debugLogger();
|
|
21686
|
+
STORAGE_KEY2 = "bandit.selectedEngine";
|
|
21687
|
+
readStored = () => {
|
|
21688
|
+
try {
|
|
21689
|
+
return typeof window !== "undefined" ? window.localStorage.getItem(STORAGE_KEY2) : null;
|
|
21690
|
+
} catch {
|
|
21691
|
+
return null;
|
|
21692
|
+
}
|
|
21693
|
+
};
|
|
21694
|
+
useEngineStore = (0, import_zustand15.create)((set, get) => ({
|
|
21695
|
+
selectedEngine: readStored(),
|
|
21696
|
+
engines: [],
|
|
21697
|
+
loaded: false,
|
|
21698
|
+
setSelectedEngine: (id) => {
|
|
21699
|
+
set({ selectedEngine: id });
|
|
21700
|
+
try {
|
|
21701
|
+
window.localStorage.setItem(STORAGE_KEY2, id);
|
|
21702
|
+
} catch {
|
|
21703
|
+
}
|
|
21704
|
+
},
|
|
21705
|
+
getSelectedEngine: () => get().selectedEngine || usePackageSettingsStore.getState().settings?.defaultModel || "bandit-core",
|
|
21706
|
+
fetchEngines: async () => {
|
|
21707
|
+
const settings = usePackageSettingsStore.getState().settings;
|
|
21708
|
+
const base = settings?.gatewayApiUrl?.replace(/\/$/, "") ?? "";
|
|
21709
|
+
if (!base || settings?.playgroundMode || base.toLowerCase().startsWith("playground://")) {
|
|
21710
|
+
set({ loaded: true });
|
|
21711
|
+
return;
|
|
21712
|
+
}
|
|
21713
|
+
try {
|
|
21714
|
+
const headers = { "Content-Type": "application/json" };
|
|
21715
|
+
const token = authenticationService.getToken();
|
|
21716
|
+
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
21717
|
+
const res = await fetch(`${base}/models`, { headers });
|
|
21718
|
+
const data = await res.json();
|
|
21719
|
+
if (res.ok && Array.isArray(data?.models)) {
|
|
21720
|
+
set({ engines: data.models, loaded: true });
|
|
21721
|
+
} else {
|
|
21722
|
+
set({ loaded: true });
|
|
21723
|
+
}
|
|
21724
|
+
} catch (error) {
|
|
21725
|
+
debugLogger.error("Failed to fetch engines", {
|
|
21726
|
+
error: error instanceof Error ? error.message : String(error)
|
|
21727
|
+
});
|
|
21728
|
+
set({ loaded: true });
|
|
21729
|
+
}
|
|
21730
|
+
}
|
|
21731
|
+
}));
|
|
21732
|
+
}
|
|
21733
|
+
});
|
|
21734
|
+
|
|
21349
21735
|
// src/chat/hooks/useMemoryEnhancer.tsx
|
|
21350
21736
|
var import_rxjs23, 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;
|
|
21351
21737
|
var init_useMemoryEnhancer = __esm({
|
|
@@ -22087,12 +22473,13 @@ var init_useAIProvider = __esm({
|
|
|
22087
22473
|
import_react44 = require("react");
|
|
22088
22474
|
init_knowledgeStore();
|
|
22089
22475
|
init_aiProviderStore();
|
|
22476
|
+
init_telemetry();
|
|
22477
|
+
init_engineStore();
|
|
22090
22478
|
init_conversationStore();
|
|
22091
22479
|
init_useMemoryEnhancer();
|
|
22092
22480
|
init_useVectorStore();
|
|
22093
22481
|
init_embeddingService();
|
|
22094
22482
|
init_useMoodEngine();
|
|
22095
|
-
init_packageSettingsStore();
|
|
22096
22483
|
init_prompts();
|
|
22097
22484
|
init_preferencesStore();
|
|
22098
22485
|
init_mcp();
|
|
@@ -22456,7 +22843,7 @@ var init_useAIProvider = __esm({
|
|
|
22456
22843
|
question: pendingQuestion,
|
|
22457
22844
|
images: pendingImages
|
|
22458
22845
|
});
|
|
22459
|
-
const modelName =
|
|
22846
|
+
const modelName = useEngineStore.getState().getSelectedEngine();
|
|
22460
22847
|
const CONFIG = modelConfigs[modelName] ?? modelConfigs["bandit-core:4b-it-qat"];
|
|
22461
22848
|
const base64Images = imageList.map((img) => img.split(",")[1]);
|
|
22462
22849
|
const latestEntries = history.slice(-CONFIG.historyMessages);
|
|
@@ -22851,6 +23238,9 @@ ${protocol}`;
|
|
|
22851
23238
|
setStreamBuffer(latestDisplayMessage);
|
|
22852
23239
|
}, delay);
|
|
22853
23240
|
};
|
|
23241
|
+
syncTelemetry();
|
|
23242
|
+
telemetryStartTurn(question, modelName);
|
|
23243
|
+
telemetryEvent("tool_loop:llm_start");
|
|
22854
23244
|
const stream = provider.chat(request);
|
|
22855
23245
|
const initialPlaceholderQuestion = lastEntry?.question;
|
|
22856
23246
|
lastPartialRef.current = { text: "", images: [...imageList], usedDocs, question };
|
|
@@ -22864,7 +23254,10 @@ ${protocol}`;
|
|
|
22864
23254
|
const sub = stream.subscribe({
|
|
22865
23255
|
next: (data) => {
|
|
22866
23256
|
if (!data?.message?.content && !data?.message?.tool_calls) return;
|
|
22867
|
-
if (data.message.content)
|
|
23257
|
+
if (data.message.content) {
|
|
23258
|
+
fullMessage += data.message.content;
|
|
23259
|
+
telemetryEvent("tool_loop:llm_chunk", { chunk: data.message.content });
|
|
23260
|
+
}
|
|
22868
23261
|
const inThinkBlock = /<think>/.test(fullMessage) && !/<think>[\s\S]*<\/think>/.test(fullMessage);
|
|
22869
23262
|
setIsThinking?.(inThinkBlock);
|
|
22870
23263
|
const visibleMessage = stripThinking(fullMessage);
|
|
@@ -22896,6 +23289,7 @@ ${protocol}`;
|
|
|
22896
23289
|
setIsThinking?.(false);
|
|
22897
23290
|
setPendingMessage(null);
|
|
22898
23291
|
setLogoVisible(false);
|
|
23292
|
+
telemetryEndTurn({ error: err?.message || "stream error" });
|
|
22899
23293
|
if (onError) {
|
|
22900
23294
|
onError(err);
|
|
22901
23295
|
}
|
|
@@ -22903,6 +23297,7 @@ ${protocol}`;
|
|
|
22903
23297
|
complete: async () => {
|
|
22904
23298
|
try {
|
|
22905
23299
|
setIsThinking?.(false);
|
|
23300
|
+
telemetryEvent("tool_loop:llm_response", { responseLength: fullMessage.length });
|
|
22906
23301
|
latestDisplayMessage = stripThinking(fullMessage);
|
|
22907
23302
|
if (!sawToolBlock) {
|
|
22908
23303
|
flushNow();
|
|
@@ -22911,6 +23306,7 @@ ${protocol}`;
|
|
|
22911
23306
|
let enhancedMessage = fullMessage;
|
|
22912
23307
|
const summarizableResults = [];
|
|
22913
23308
|
const inlineImageBlocks = [];
|
|
23309
|
+
const collectedSources = [];
|
|
22914
23310
|
if (toolCallMatches && toolCallMatches.length > 0 && mcpToolsAvailable) {
|
|
22915
23311
|
debugLogger.info("Detected tool calls in AI response", {
|
|
22916
23312
|
toolCallCount: toolCallMatches.length,
|
|
@@ -22945,10 +23341,21 @@ ${protocol}`;
|
|
|
22945
23341
|
});
|
|
22946
23342
|
const placeholderToken = `<<TOOL_LOADING_${functionName}_${Math.random().toString(36).slice(2)}>>`;
|
|
22947
23343
|
enhancedMessage = enhancedMessage.replace(match, placeholderToken);
|
|
23344
|
+
clearFlushTimer();
|
|
23345
|
+
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";
|
|
23346
|
+
const toolPreamble = stripToolBlocks(fullMessage).trim();
|
|
23347
|
+
setStreamBuffer(toolPreamble ? `${toolPreamble}
|
|
23348
|
+
|
|
23349
|
+
_${toolStatus}_` : `_${toolStatus}_`);
|
|
23350
|
+
telemetryEvent("tool_loop:tool_execute", { name: functionName, params: parsedParams });
|
|
22948
23351
|
const result = await executeMCPTool({
|
|
22949
23352
|
toolName: functionName,
|
|
22950
23353
|
parameters: parsedParams
|
|
22951
23354
|
});
|
|
23355
|
+
telemetryEvent(result.success ? "tool_loop:tool_result" : "tool_loop:tool_error", {
|
|
23356
|
+
name: functionName,
|
|
23357
|
+
isError: !result.success
|
|
23358
|
+
});
|
|
22952
23359
|
let resultText = "";
|
|
22953
23360
|
if (result.success) {
|
|
22954
23361
|
if (functionName === "web_search" || functionName === "web-search") {
|
|
@@ -22962,18 +23369,16 @@ ${protocol}`;
|
|
|
22962
23369
|
blocks.push(
|
|
22963
23370
|
items.slice(0, 6).map((item, index) => {
|
|
22964
23371
|
const title = item.title?.trim() || "Untitled";
|
|
22965
|
-
const url = item.url?.trim();
|
|
23372
|
+
const url = item.url?.trim() || "";
|
|
22966
23373
|
const snippet = item.content?.trim();
|
|
22967
|
-
|
|
22968
|
-
|
|
22969
|
-
${url}`;
|
|
23374
|
+
if (url) collectedSources.push({ title, url });
|
|
23375
|
+
let line = url ? `${index + 1}. [${title}](${url})` : `${index + 1}. ${title}`;
|
|
22970
23376
|
if (snippet) {
|
|
22971
23377
|
const truncated = snippet.length > 300 ? `${snippet.slice(0, 300)}\u2026` : snippet;
|
|
22972
|
-
line += `
|
|
22973
|
-
${truncated}`;
|
|
23378
|
+
line += ` \u2014 ${truncated}`;
|
|
22974
23379
|
}
|
|
22975
23380
|
return line;
|
|
22976
|
-
}).join("\n
|
|
23381
|
+
}).join("\n")
|
|
22977
23382
|
);
|
|
22978
23383
|
}
|
|
22979
23384
|
resultText = blocks.length ? blocks.join("\n\n") : `No results found${search.query ? ` for "${search.query}"` : ""}.`;
|
|
@@ -23055,7 +23460,7 @@ ${r.output}`).join("\n\n");
|
|
|
23055
23460
|
|
|
23056
23461
|
${toolResultsText}
|
|
23057
23462
|
|
|
23058
|
-
Using these results together with your own knowledge, answer my original question concisely and in a natural, well-formatted way.
|
|
23463
|
+
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.`
|
|
23059
23464
|
}
|
|
23060
23465
|
];
|
|
23061
23466
|
const summaryRequest = {
|
|
@@ -23069,7 +23474,7 @@ Using these results together with your own knowledge, answer my original questio
|
|
|
23069
23474
|
setStreamBuffer(
|
|
23070
23475
|
summaryPreamble ? `${summaryPreamble}
|
|
23071
23476
|
|
|
23072
|
-
|
|
23477
|
+
_Writing the answer\u2026_` : "_Writing the answer\u2026_"
|
|
23073
23478
|
);
|
|
23074
23479
|
const summaryText = await new Promise((resolve) => {
|
|
23075
23480
|
let acc = "";
|
|
@@ -23110,7 +23515,18 @@ _Working on it\u2026_` : "_Working on it\u2026_"
|
|
|
23110
23515
|
}, 3e4);
|
|
23111
23516
|
});
|
|
23112
23517
|
if (summaryText.trim()) {
|
|
23113
|
-
|
|
23518
|
+
const sourcesMd = collectedSources.length ? `
|
|
23519
|
+
|
|
23520
|
+
**Sources**
|
|
23521
|
+
${collectedSources.slice(0, 6).map((s) => {
|
|
23522
|
+
let domain = s.url;
|
|
23523
|
+
try {
|
|
23524
|
+
domain = new URL(s.url).hostname.replace(/^www\./, "");
|
|
23525
|
+
} catch {
|
|
23526
|
+
}
|
|
23527
|
+
return `- [${s.title || domain}](${s.url}) \u2014 ${domain}`;
|
|
23528
|
+
}).join("\n")}` : "";
|
|
23529
|
+
enhancedMessage = summaryText + sourcesMd + (inlineImageBlocks.length ? `
|
|
23114
23530
|
|
|
23115
23531
|
${inlineImageBlocks.join("\n\n")}` : "");
|
|
23116
23532
|
}
|
|
@@ -23159,6 +23575,7 @@ ${inlineImageBlocks.join("\n\n")}` : "");
|
|
|
23159
23575
|
}
|
|
23160
23576
|
setInputValue("");
|
|
23161
23577
|
setPastedImages([]);
|
|
23578
|
+
telemetryEndTurn();
|
|
23162
23579
|
setTimeout(() => {
|
|
23163
23580
|
clearFlushTimer();
|
|
23164
23581
|
setPendingMessage(null);
|
|
@@ -23174,6 +23591,7 @@ ${inlineImageBlocks.join("\n\n")}` : "");
|
|
|
23174
23591
|
overrideComponentStatus("Idle");
|
|
23175
23592
|
setIsSubmitting(false);
|
|
23176
23593
|
setIsStreaming(false);
|
|
23594
|
+
telemetryEndTurn({ error: e instanceof Error ? e.message : String(e) });
|
|
23177
23595
|
}
|
|
23178
23596
|
}
|
|
23179
23597
|
});
|
|
@@ -26032,10 +26450,10 @@ var init_enhanced_mobile_conversations_modal = __esm({
|
|
|
26032
26450
|
(0, import_react52.useEffect)(() => {
|
|
26033
26451
|
setDeletedConversationIds((prev) => {
|
|
26034
26452
|
let changed = false;
|
|
26035
|
-
const
|
|
26453
|
+
const active2 = new Set(conversations.map((conv) => conv.id));
|
|
26036
26454
|
const next = /* @__PURE__ */ new Set();
|
|
26037
26455
|
prev.forEach((id) => {
|
|
26038
|
-
if (
|
|
26456
|
+
if (active2.has(id)) {
|
|
26039
26457
|
next.add(id);
|
|
26040
26458
|
} else {
|
|
26041
26459
|
changed = true;
|
|
@@ -26657,6 +27075,7 @@ var init_chat_app_bar = __esm({
|
|
|
26657
27075
|
init_packageSettingsStore();
|
|
26658
27076
|
init_useFeatures();
|
|
26659
27077
|
init_conversationSyncStore();
|
|
27078
|
+
init_engineStore();
|
|
26660
27079
|
import_shallow2 = require("zustand/shallow");
|
|
26661
27080
|
import_jsx_runtime42 = require("react/jsx-runtime");
|
|
26662
27081
|
CDN_BASE2 = "https://cdn.burtson.ai/";
|
|
@@ -26706,6 +27125,7 @@ var init_chat_app_bar = __esm({
|
|
|
26706
27125
|
menuText
|
|
26707
27126
|
} = theme.palette.chat.appBar;
|
|
26708
27127
|
const [modelAnchorEl, setModelAnchorEl] = (0, import_react53.useState)(null);
|
|
27128
|
+
const [engineAnchorEl, setEngineAnchorEl] = (0, import_react53.useState)(null);
|
|
26709
27129
|
const [voiceAnchorEl, setVoiceAnchorEl] = (0, import_react53.useState)(null);
|
|
26710
27130
|
const [modalOpen, setModalOpen] = (0, import_react53.useState)(false);
|
|
26711
27131
|
const [confirmModelChangeOpen, setConfirmModelChangeOpen] = (0, import_react53.useState)(false);
|
|
@@ -26826,6 +27246,14 @@ var init_chat_app_bar = __esm({
|
|
|
26826
27246
|
const selectedModel = useModelStore((s) => s.selectedModel);
|
|
26827
27247
|
const currentModel = useModelStore((s) => s.availableModels.find((m) => m.name === selectedModel));
|
|
26828
27248
|
const currentAvatar = currentModel?.avatarBase64 || modelAvatars3[selectedModel] || banditHead5;
|
|
27249
|
+
const engines = useEngineStore((s) => s.engines);
|
|
27250
|
+
const selectedEngine = useEngineStore((s) => s.selectedEngine);
|
|
27251
|
+
const effectiveEngineId = selectedEngine || usePackageSettingsStore.getState().settings?.defaultModel || "bandit-core";
|
|
27252
|
+
const currentEngine = engines.find((e) => e.id === effectiveEngineId);
|
|
27253
|
+
const engineLabel = currentEngine?.displayName?.replace(/^Bandit /, "") || "Engine";
|
|
27254
|
+
(0, import_react53.useEffect)(() => {
|
|
27255
|
+
useEngineStore.getState().fetchEngines();
|
|
27256
|
+
}, []);
|
|
26829
27257
|
const pendingModelAvatar = useModelStore.getState().availableModels.find((m) => m.name === pendingModel)?.avatarBase64 || modelAvatars3[pendingModel || ""] || banditHead5;
|
|
26830
27258
|
const resolvedHomeUrl = preferences.homeUrl?.trim() || packageSettings?.homeUrl?.trim() || "";
|
|
26831
27259
|
const homeTooltip = (() => {
|
|
@@ -27138,6 +27566,84 @@ var init_chat_app_bar = __esm({
|
|
|
27138
27566
|
)
|
|
27139
27567
|
}
|
|
27140
27568
|
) }),
|
|
27569
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_material43.Tooltip, { title: `Engine: ${currentEngine?.displayName ?? effectiveEngineId}`, arrow: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
|
|
27570
|
+
import_material43.IconButton,
|
|
27571
|
+
{
|
|
27572
|
+
onClick: (e) => setEngineAnchorEl(e.currentTarget),
|
|
27573
|
+
sx: pillButtonStyles,
|
|
27574
|
+
"aria-label": `Change base model (engine). Currently ${effectiveEngineId}`,
|
|
27575
|
+
children: [
|
|
27576
|
+
currentEngine?.cloud ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(CloudDoneIcon, { fontSize: "small" }) : /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(CloudOffIcon, { fontSize: "small" }),
|
|
27577
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_material43.Typography, { variant: "caption", sx: { ml: 0.75, fontWeight: 600, whiteSpace: "nowrap" }, children: engineLabel })
|
|
27578
|
+
]
|
|
27579
|
+
}
|
|
27580
|
+
) }),
|
|
27581
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
|
|
27582
|
+
import_material43.Menu,
|
|
27583
|
+
{
|
|
27584
|
+
anchorEl: engineAnchorEl,
|
|
27585
|
+
open: Boolean(engineAnchorEl),
|
|
27586
|
+
onClose: () => setEngineAnchorEl(null),
|
|
27587
|
+
transformOrigin: { horizontal: "right", vertical: "top" },
|
|
27588
|
+
anchorOrigin: { horizontal: "right", vertical: "bottom" },
|
|
27589
|
+
children: [
|
|
27590
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_material43.Typography, { variant: "overline", sx: { px: 2, color: theme.palette.text.secondary }, children: "Engine \xB7 base model" }),
|
|
27591
|
+
engines.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_material43.MenuItem, { disabled: true, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_material43.Typography, { variant: "body2", children: "No engines available" }) }),
|
|
27592
|
+
engines.map((engine) => {
|
|
27593
|
+
const badges = [
|
|
27594
|
+
engine.vision && "vision",
|
|
27595
|
+
engine.tools && "tools",
|
|
27596
|
+
engine.thinking && "thinking",
|
|
27597
|
+
engine.cloud && "cloud"
|
|
27598
|
+
].filter(Boolean);
|
|
27599
|
+
return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(
|
|
27600
|
+
import_material43.MenuItem,
|
|
27601
|
+
{
|
|
27602
|
+
selected: engine.id === effectiveEngineId,
|
|
27603
|
+
disabled: !engine.available,
|
|
27604
|
+
onClick: () => {
|
|
27605
|
+
useEngineStore.getState().setSelectedEngine(engine.id);
|
|
27606
|
+
setEngineAnchorEl(null);
|
|
27607
|
+
},
|
|
27608
|
+
sx: {
|
|
27609
|
+
display: "flex",
|
|
27610
|
+
flexDirection: "column",
|
|
27611
|
+
alignItems: "flex-start",
|
|
27612
|
+
gap: 0.5,
|
|
27613
|
+
py: 1,
|
|
27614
|
+
px: 2,
|
|
27615
|
+
maxWidth: 360,
|
|
27616
|
+
whiteSpace: "normal"
|
|
27617
|
+
},
|
|
27618
|
+
children: [
|
|
27619
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_material43.Box, { sx: { display: "flex", alignItems: "center", gap: 1, width: "100%" }, children: [
|
|
27620
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_material43.Typography, { variant: "body2", sx: { fontWeight: 600, flex: 1 }, children: engine.displayName }),
|
|
27621
|
+
engine.id === effectiveEngineId && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_material43.Box, { sx: { width: 8, height: 8, borderRadius: "50%", bgcolor: theme.palette.primary.main } })
|
|
27622
|
+
] }),
|
|
27623
|
+
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_material43.Typography, { variant: "caption", sx: { color: theme.palette.text.secondary }, children: engine.available ? engine.description : engine.unavailableReason || "Unavailable" }),
|
|
27624
|
+
badges.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_material43.Box, { sx: { display: "flex", gap: 0.5, flexWrap: "wrap", mt: 0.25 }, children: badges.map((b) => /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
27625
|
+
import_material43.Box,
|
|
27626
|
+
{
|
|
27627
|
+
sx: {
|
|
27628
|
+
fontSize: "0.65rem",
|
|
27629
|
+
px: 0.75,
|
|
27630
|
+
py: 0.1,
|
|
27631
|
+
borderRadius: 1,
|
|
27632
|
+
bgcolor: theme.palette.primary.main + "22",
|
|
27633
|
+
color: theme.palette.primary.main
|
|
27634
|
+
},
|
|
27635
|
+
children: b
|
|
27636
|
+
},
|
|
27637
|
+
b
|
|
27638
|
+
)) })
|
|
27639
|
+
]
|
|
27640
|
+
},
|
|
27641
|
+
engine.id
|
|
27642
|
+
);
|
|
27643
|
+
})
|
|
27644
|
+
]
|
|
27645
|
+
}
|
|
27646
|
+
),
|
|
27141
27647
|
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
|
|
27142
27648
|
import_material43.Menu,
|
|
27143
27649
|
{
|