@burtson-labs/bandit-engine 2.1.5 → 2.1.7

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.
Files changed (36) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/{ProjectsPane-BSVOVNK7.mjs → ProjectsPane-E5D72PUG.mjs} +4 -4
  3. package/dist/{SchedulesTab-TCUOAKLH.mjs → SchedulesTab-AKJ6WFZV.mjs} +3 -3
  4. package/dist/{chat-Y7KPN54G.mjs → chat-MI4IL3WK.mjs} +7 -6
  5. package/dist/{chunk-4257FVLK.mjs → chunk-2HFSBPOE.mjs} +6 -1
  6. package/dist/chunk-2HFSBPOE.mjs.map +1 -0
  7. package/dist/{chunk-XGIQNHUO.mjs → chunk-4NHPLHXU.mjs} +816 -394
  8. package/dist/chunk-4NHPLHXU.mjs.map +1 -0
  9. package/dist/{chunk-SK7FSLCY.mjs → chunk-4NUQYUZH.mjs} +7 -2
  10. package/dist/chunk-4NUQYUZH.mjs.map +1 -0
  11. package/dist/{chunk-ZG66NJC3.mjs → chunk-5URP2E5V.mjs} +3 -6
  12. package/dist/chunk-5URP2E5V.mjs.map +1 -0
  13. package/dist/{chunk-IXD2TDA5.mjs → chunk-SQJ4OKWM.mjs} +2 -2
  14. package/dist/chunk-WBKTQJNX.mjs +21 -0
  15. package/dist/chunk-WBKTQJNX.mjs.map +1 -0
  16. package/dist/image-compare-NW5VCP4Q.mjs +74 -0
  17. package/dist/image-compare-NW5VCP4Q.mjs.map +1 -0
  18. package/dist/image-edit-acknowledgement-RZPQYKYJ.mjs +54 -0
  19. package/dist/image-edit-acknowledgement-RZPQYKYJ.mjs.map +1 -0
  20. package/dist/index.d.mts +16 -1
  21. package/dist/index.d.ts +16 -1
  22. package/dist/index.js +5758 -5120
  23. package/dist/index.js.map +1 -1
  24. package/dist/index.mjs +365 -340
  25. package/dist/index.mjs.map +1 -1
  26. package/dist/{schedule-confirm-card-7CMATYSP.mjs → schedule-confirm-card-D4K76HEA.mjs} +5 -5
  27. package/package.json +1 -1
  28. package/dist/chunk-4257FVLK.mjs.map +0 -1
  29. package/dist/chunk-SK7FSLCY.mjs.map +0 -1
  30. package/dist/chunk-XGIQNHUO.mjs.map +0 -1
  31. package/dist/chunk-ZG66NJC3.mjs.map +0 -1
  32. /package/dist/{ProjectsPane-BSVOVNK7.mjs.map → ProjectsPane-E5D72PUG.mjs.map} +0 -0
  33. /package/dist/{SchedulesTab-TCUOAKLH.mjs.map → SchedulesTab-AKJ6WFZV.mjs.map} +0 -0
  34. /package/dist/{chat-Y7KPN54G.mjs.map → chat-MI4IL3WK.mjs.map} +0 -0
  35. /package/dist/{chunk-IXD2TDA5.mjs.map → chunk-SQJ4OKWM.mjs.map} +0 -0
  36. /package/dist/{schedule-confirm-card-7CMATYSP.mjs.map → schedule-confirm-card-D4K76HEA.mjs.map} +0 -0
@@ -1,3 +1,48 @@
1
+ import {
2
+ AlertCircle as AlertCircle2,
3
+ AlertTriangle,
4
+ ArrowDown,
5
+ ArrowUp,
6
+ Braces,
7
+ Check as Check2,
8
+ CloudOff,
9
+ CloudSync,
10
+ Code,
11
+ Copy as Copy2,
12
+ Download as Download2,
13
+ EarOff,
14
+ EditSquare,
15
+ FilePdf,
16
+ FileText as FileText2,
17
+ Ghost as Ghost2,
18
+ Info,
19
+ Lock,
20
+ MarkdownImage,
21
+ Maximize,
22
+ MemoryLayers,
23
+ Mic as Mic2,
24
+ Minimize,
25
+ PanelLeft,
26
+ Pin,
27
+ PinOff,
28
+ Plus as Plus2,
29
+ Search as Search2,
30
+ Sparkles,
31
+ Square as Square2,
32
+ StreamingMarkdown_default,
33
+ Trash as Trash2,
34
+ Type,
35
+ Waveform as Waveform2,
36
+ Wifi,
37
+ WifiOff,
38
+ X as X2,
39
+ ZoomIn,
40
+ ZoomOut,
41
+ getHighlightTree,
42
+ markdownSanitizeSchema,
43
+ maybeHandleFileLinkClick,
44
+ renderLowlightChildren
45
+ } from "./chunk-4NUQYUZH.mjs";
1
46
  import {
2
47
  GatewayService,
3
48
  readContextSourcesField,
@@ -56,15 +101,23 @@ import {
56
101
  useSectionsLayout,
57
102
  useSidebarStore,
58
103
  writePersistedCollapsed
59
- } from "./chunk-IXD2TDA5.mjs";
104
+ } from "./chunk-SQJ4OKWM.mjs";
60
105
  import {
61
106
  indexedDBService_default,
62
107
  useModelStore,
63
108
  usePreferencesStore
64
109
  } from "./chunk-KWOUXBSF.mjs";
110
+ import {
111
+ useImageEditAckStore
112
+ } from "./chunk-WBKTQJNX.mjs";
65
113
  import {
66
114
  useScheduleConfirmStore
67
115
  } from "./chunk-HS62C62A.mjs";
116
+ import {
117
+ browserTimeZone,
118
+ formatInZone,
119
+ normalizeScheduleArgs
120
+ } from "./chunk-MAKXJD7A.mjs";
68
121
  import {
69
122
  Activity,
70
123
  Agent,
@@ -123,57 +176,7 @@ import {
123
176
  Waveform,
124
177
  Wrench,
125
178
  X
126
- } from "./chunk-ZG66NJC3.mjs";
127
- import {
128
- AlertCircle as AlertCircle2,
129
- AlertTriangle,
130
- ArrowDown,
131
- ArrowUp,
132
- Braces,
133
- Check as Check2,
134
- CloudOff,
135
- CloudSync,
136
- Code,
137
- Copy as Copy2,
138
- Download as Download2,
139
- EarOff,
140
- EditSquare,
141
- FilePdf,
142
- FileText as FileText2,
143
- Ghost as Ghost2,
144
- Info,
145
- Lock,
146
- MarkdownImage,
147
- Maximize,
148
- MemoryLayers,
149
- Mic as Mic2,
150
- Minimize,
151
- PanelLeft,
152
- Pin,
153
- PinOff,
154
- Plus as Plus2,
155
- Search as Search2,
156
- Sparkles,
157
- Square as Square2,
158
- StreamingMarkdown_default,
159
- Trash as Trash2,
160
- Type,
161
- Waveform as Waveform2,
162
- Wifi,
163
- WifiOff,
164
- X as X2,
165
- ZoomIn,
166
- ZoomOut,
167
- getHighlightTree,
168
- markdownSanitizeSchema,
169
- maybeHandleFileLinkClick,
170
- renderLowlightChildren
171
- } from "./chunk-SK7FSLCY.mjs";
172
- import {
173
- browserTimeZone,
174
- formatInZone,
175
- normalizeScheduleArgs
176
- } from "./chunk-MAKXJD7A.mjs";
179
+ } from "./chunk-5URP2E5V.mjs";
177
180
  import {
178
181
  usePackageSettingsStore
179
182
  } from "./chunk-K7SQWDNF.mjs";
@@ -187,7 +190,8 @@ import {
187
190
  } from "./chunk-NI7BKLC7.mjs";
188
191
 
189
192
  // src/chat/chat.tsx
190
- import { lazy as lazy2, Suspense as Suspense2, useCallback as useCallback13, useEffect as useEffect26, useLayoutEffect as useLayoutEffect2, useRef as useRef20, useState as useState29 } from "react";
193
+ import React25 from "react";
194
+ import { lazy as lazy2, Suspense as Suspense3, useCallback as useCallback13, useEffect as useEffect26, useLayoutEffect as useLayoutEffect2, useRef as useRef20, useState as useState29 } from "react";
191
195
 
192
196
  // src/chat/custom-logo.tsx
193
197
  import React, { useEffect } from "react";
@@ -358,8 +362,8 @@ import {
358
362
  Box as Box32,
359
363
  ThemeProvider as ThemeProvider2,
360
364
  CssBaseline,
361
- Typography as Typography26,
362
- Button as Button15,
365
+ Typography as Typography25,
366
+ Button as Button14,
363
367
  IconButton as IconButton21
364
368
  } from "@mui/material";
365
369
  import { alpha as alpha18 } from "@mui/material/styles";
@@ -3481,6 +3485,23 @@ var hostOf = (url) => {
3481
3485
  }
3482
3486
  };
3483
3487
  var str2 = (value) => typeof value === "string" && value.trim() ? value.trim() : void 0;
3488
+ var quotaNote = (data) => {
3489
+ const quota = data.quota;
3490
+ if (!quota || typeof quota.limit !== "number" || typeof quota.remaining !== "number") return "";
3491
+ return ` \u2014 ${quota.remaining} of ${quota.limit} left today`;
3492
+ };
3493
+ var planRequired = (data) => str2(data.code) === "image_plan_required";
3494
+ var cloudNote = (data) => str2(data.backend) === "cloud" ? " in the cloud to skip the queue" : "";
3495
+ var failedLabel = (base, result) => {
3496
+ const data = result?.data ?? {};
3497
+ const code = str2(data.code);
3498
+ if (code === "image_quota_exceeded") return "Daily image allowance used";
3499
+ if (code === "image_plan_required") return "Image generation is on paid plans";
3500
+ if (code === "content_refused") return "Image request refused by safety check";
3501
+ if (code === "acknowledgement_required") return "Photo edit needs your acknowledgement";
3502
+ if (code === "chat_images_disabled") return "Image tools are not enabled";
3503
+ return base;
3504
+ };
3484
3505
  var kindFor = (name) => {
3485
3506
  switch (canonicalToolName(name)) {
3486
3507
  case "web_search":
@@ -3489,6 +3510,8 @@ var kindFor = (name) => {
3489
3510
  return "web_fetch";
3490
3511
  case "image_generation":
3491
3512
  return "image_generation";
3513
+ case "image_edit":
3514
+ return "image_edit";
3492
3515
  case "create_file":
3493
3516
  return "create_file";
3494
3517
  case "ask_user":
@@ -3529,14 +3552,29 @@ var trailItemForTool = (name, params, result) => {
3529
3552
  label: `${failed ? "Couldn't read" : "Read"}${url ? `: ${hostOf(url)}` : ""}`
3530
3553
  };
3531
3554
  }
3532
- case "image_generation":
3555
+ case "image_generation": {
3556
+ const quota = quotaNote(data);
3533
3557
  return {
3534
3558
  kind,
3535
3559
  tool: name,
3536
3560
  ok,
3537
3561
  detail: str2(params.prompt),
3538
- label: ok ? "Generated an image" : "Image generation failed"
3562
+ label: planRequired(data) ? "Image generation is on paid plans" : ok ? `Generated an image${cloudNote(data)}${quota}` : failedLabel("Image generation failed", result)
3563
+ };
3564
+ }
3565
+ case "image_edit": {
3566
+ const quota = quotaNote(data);
3567
+ const index = typeof params.image === "number" && params.image >= 1 ? Math.floor(params.image) : void 0;
3568
+ return {
3569
+ kind,
3570
+ tool: name,
3571
+ ok,
3572
+ detail: str2(params.instruction) ?? str2(params.prompt),
3573
+ afterUrl: ok && !planRequired(data) ? str2(data.imageUrl) : void 0,
3574
+ attachmentIndex: index,
3575
+ label: planRequired(data) ? "Photo editing is on paid plans" : ok ? `Edited the photo${cloudNote(data)}${quota}` : failedLabel("Photo edit failed", result)
3539
3576
  };
3577
+ }
3540
3578
  case "create_file": {
3541
3579
  const filename = str2(data.filename) ?? str2(params.filename);
3542
3580
  const stash = data.stash && typeof data.stash === "object" ? data.stash : {};
@@ -3590,25 +3628,24 @@ var pendingTrailItem = (name, params) => {
3590
3628
  const query = str2(params.query);
3591
3629
  const url = str2(params.url);
3592
3630
  const filename = str2(params.filename);
3593
- const label = kind === "web_search" ? `Searching${query ? `: ${clip(query)}` : " the web"}` : kind === "web_fetch" ? `Reading${url ? `: ${hostOf(url)}` : " the page"}` : kind === "image_generation" ? "Generating an image" : kind === "create_file" ? `Creating file${filename ? `: ${clip(filename)}` : ""}` : kind === "ask_user" ? "Waiting for your answer" : kind === "create_schedule" ? "Setting up a schedule" : kind === "health_check" ? "Checking service health" : `Running tool: ${name}`;
3631
+ const label = kind === "web_search" ? `Searching${query ? `: ${clip(query)}` : " the web"}` : kind === "web_fetch" ? `Reading${url ? `: ${hostOf(url)}` : " the page"}` : kind === "image_generation" ? "Generating an image" : kind === "image_edit" ? "Editing the photo" : kind === "create_file" ? `Creating file${filename ? `: ${clip(filename)}` : ""}` : kind === "ask_user" ? "Waiting for your answer" : kind === "create_schedule" ? "Setting up a schedule" : kind === "health_check" ? "Checking service health" : `Running tool: ${name}`;
3594
3632
  return {
3595
3633
  kind,
3596
3634
  tool: name,
3597
3635
  ok: true,
3598
3636
  pending: true,
3599
3637
  label,
3600
- detail: query ?? url ?? filename,
3638
+ detail: query ?? url ?? filename ?? str2(params.prompt) ?? str2(params.instruction),
3601
3639
  url: kind === "web_fetch" ? url : void 0
3602
3640
  };
3603
3641
  };
3642
+ var pendingTrailItemWithStatus = (name, params, status) => ({ ...pendingTrailItem(name, params), label: status });
3604
3643
  var upsertTrailItem = (trail, item) => {
3605
- if (!item.pending) {
3606
- const idx = trail.findIndex((t) => t.pending && t.tool === item.tool);
3607
- if (idx !== -1) {
3608
- const next = [...trail];
3609
- next[idx] = item;
3610
- return next;
3611
- }
3644
+ const idx = trail.findIndex((t) => t.pending && t.tool === item.tool);
3645
+ if (idx !== -1) {
3646
+ const next = [...trail];
3647
+ next[idx] = item;
3648
+ return next;
3612
3649
  }
3613
3650
  return appendTrailItem(trail, item);
3614
3651
  };
@@ -3763,6 +3800,7 @@ var KNOWN_KINDS = /* @__PURE__ */ new Set([
3763
3800
  "web_search",
3764
3801
  "web_fetch",
3765
3802
  "image_generation",
3803
+ "image_edit",
3766
3804
  "create_file",
3767
3805
  "ask_user",
3768
3806
  "health_check",
@@ -3783,13 +3821,16 @@ var normalizeToolTrail = (raw) => {
3783
3821
  const kind = typeof r.kind === "string" && KNOWN_KINDS.has(r.kind) ? r.kind : "tool";
3784
3822
  const url = str2(r.url);
3785
3823
  const stillPending = r.pending === true;
3824
+ const afterUrl = str2(r.afterUrl);
3786
3825
  out.push({
3787
3826
  kind,
3788
3827
  tool,
3789
3828
  label,
3790
3829
  detail: str2(r.detail),
3791
3830
  url: url && /^https?:\/\//i.test(url) ? url : void 0,
3792
- ok: stillPending ? false : r.ok !== false
3831
+ ok: stillPending ? false : r.ok !== false,
3832
+ afterUrl: afterUrl && /^https?:\/\//i.test(afterUrl) ? afterUrl : void 0,
3833
+ attachmentIndex: typeof r.attachmentIndex === "number" && r.attachmentIndex >= 1 ? Math.floor(r.attachmentIndex) : void 0
3793
3834
  });
3794
3835
  }
3795
3836
  return out.length ? out : void 0;
@@ -9577,6 +9618,7 @@ var SourceChips = ({ content }) => {
9577
9618
  var source_chips_default = SourceChips;
9578
9619
 
9579
9620
  // src/chat/tool-trail-view.tsx
9621
+ import React6, { Suspense } from "react";
9580
9622
  import { Box as Box9 } from "@mui/material";
9581
9623
  import { alpha as alpha4 } from "@mui/material/styles";
9582
9624
  import { Fragment as Fragment6, jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
@@ -9584,6 +9626,7 @@ var ICONS = {
9584
9626
  web_search: Search,
9585
9627
  web_fetch: Globe,
9586
9628
  image_generation: Image,
9629
+ image_edit: Edit,
9587
9630
  create_file: FileText,
9588
9631
  ask_user: HelpCircle,
9589
9632
  create_schedule: Clock,
@@ -9593,96 +9636,126 @@ var ICONS = {
9593
9636
  stash: Archive,
9594
9637
  tool: Wrench
9595
9638
  };
9596
- var ToolTrail = ({ items }) => {
9639
+ var ImageCompare = React6.lazy(() => import("./image-compare-NW5VCP4Q.mjs"));
9640
+ var imageComparePairs = (items, attachments) => {
9641
+ if (!items || !attachments || attachments.length === 0) return [];
9642
+ const pairs = [];
9643
+ for (const item of items) {
9644
+ if (item.kind !== "image_edit" || !item.ok || !item.afterUrl) continue;
9645
+ const index = item.attachmentIndex && item.attachmentIndex <= attachments.length ? item.attachmentIndex - 1 : attachments.length - 1;
9646
+ const beforeUrl = attachments[index];
9647
+ if (beforeUrl) pairs.push({ beforeUrl, afterUrl: item.afterUrl });
9648
+ }
9649
+ return pairs;
9650
+ };
9651
+ var ToolTrail = ({
9652
+ items,
9653
+ attachments
9654
+ }) => {
9597
9655
  if (!items || items.length === 0) return null;
9598
- return /* @__PURE__ */ jsx11(
9599
- Box9,
9600
- {
9601
- component: "ul",
9602
- "aria-label": "Tools used for this answer",
9603
- "data-testid": "tool-trail",
9604
- sx: {
9605
- listStyle: "none",
9606
- m: 0,
9607
- mb: 1,
9608
- p: 0,
9609
- display: "flex",
9610
- flexWrap: "wrap",
9611
- gap: 0.75
9656
+ const compares = imageComparePairs(items, attachments);
9657
+ return /* @__PURE__ */ jsxs8(Fragment6, { children: [
9658
+ compares.length > 0 && /* @__PURE__ */ jsx11(Suspense, { fallback: null, children: /* @__PURE__ */ jsx11(Box9, { sx: { display: "flex", flexDirection: "column", gap: 1.5, mb: 1.5 }, children: compares.map((pair, i) => /* @__PURE__ */ jsx11(
9659
+ ImageCompare,
9660
+ {
9661
+ beforeUrl: pair.beforeUrl,
9662
+ afterUrl: pair.afterUrl
9612
9663
  },
9613
- children: items.map((item, i) => {
9614
- const Icon = item.ok ? ICONS[item.kind] ?? Wrench : AlertCircle;
9615
- const pending = item.pending === true;
9616
- const title = item.detail && item.detail !== item.label ? item.detail : void 0;
9617
- const body = /* @__PURE__ */ jsxs8(Fragment6, { children: [
9618
- /* @__PURE__ */ jsx11(
9619
- Box9,
9620
- {
9621
- component: "span",
9622
- sx: { display: "inline-flex", flexShrink: 0, opacity: 0.85 },
9623
- "aria-hidden": true,
9624
- children: /* @__PURE__ */ jsx11(Icon, { size: 13 })
9625
- }
9626
- ),
9627
- /* @__PURE__ */ jsxs8(
9664
+ `${pair.afterUrl}-${i}`
9665
+ )) }) }),
9666
+ /* @__PURE__ */ jsx11(
9667
+ Box9,
9668
+ {
9669
+ component: "ul",
9670
+ "aria-label": "Tools used for this answer",
9671
+ "data-testid": "tool-trail",
9672
+ sx: {
9673
+ listStyle: "none",
9674
+ m: 0,
9675
+ mb: 1,
9676
+ p: 0,
9677
+ display: "flex",
9678
+ flexWrap: "wrap",
9679
+ gap: 0.75
9680
+ },
9681
+ children: items.map((item, i) => {
9682
+ const Icon = item.ok ? ICONS[item.kind] ?? Wrench : AlertCircle;
9683
+ const pending = item.pending === true;
9684
+ const title = item.detail && item.detail !== item.label ? item.detail : void 0;
9685
+ const body = /* @__PURE__ */ jsxs8(Fragment6, { children: [
9686
+ /* @__PURE__ */ jsx11(
9687
+ Box9,
9688
+ {
9689
+ component: "span",
9690
+ sx: { display: "inline-flex", flexShrink: 0, opacity: 0.85 },
9691
+ "aria-hidden": true,
9692
+ children: /* @__PURE__ */ jsx11(Icon, { size: 13 })
9693
+ }
9694
+ ),
9695
+ /* @__PURE__ */ jsxs8(
9696
+ Box9,
9697
+ {
9698
+ component: "span",
9699
+ sx: {
9700
+ overflow: "hidden",
9701
+ textOverflow: "ellipsis",
9702
+ whiteSpace: "nowrap"
9703
+ },
9704
+ children: [
9705
+ item.label,
9706
+ pending ? "\u2026" : ""
9707
+ ]
9708
+ }
9709
+ )
9710
+ ] });
9711
+ return /* @__PURE__ */ jsx11(
9628
9712
  Box9,
9629
9713
  {
9630
- component: "span",
9631
- sx: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
9632
- children: [
9633
- item.label,
9634
- pending ? "\u2026" : ""
9635
- ]
9636
- }
9637
- )
9638
- ] });
9639
- return /* @__PURE__ */ jsx11(
9640
- Box9,
9641
- {
9642
- component: "li",
9643
- "data-kind": item.kind,
9644
- "data-ok": item.ok ? "true" : "false",
9645
- "data-pending": pending ? "true" : void 0,
9646
- "aria-busy": pending || void 0,
9647
- title,
9648
- sx: (theme) => ({
9649
- display: "inline-flex",
9650
- alignItems: "center",
9651
- gap: 0.6,
9652
- maxWidth: "100%",
9653
- px: 1,
9654
- py: 0.35,
9655
- borderRadius: 1.5,
9656
- fontSize: "0.78rem",
9657
- lineHeight: 1.4,
9658
- color: item.ok ? theme.palette.text.secondary : theme.palette.error.main,
9659
- bgcolor: alpha4(theme.palette.text.primary, 0.04),
9660
- border: `1px solid ${alpha4(theme.palette.text.primary, 0.1)}`,
9661
- "& a": {
9662
- color: "inherit",
9663
- textDecoration: "none",
9714
+ component: "li",
9715
+ "data-kind": item.kind,
9716
+ "data-ok": item.ok ? "true" : "false",
9717
+ "data-pending": pending ? "true" : void 0,
9718
+ "aria-busy": pending || void 0,
9719
+ title,
9720
+ sx: (theme) => ({
9664
9721
  display: "inline-flex",
9665
9722
  alignItems: "center",
9666
9723
  gap: 0.6,
9667
- minWidth: 0
9668
- },
9669
- "& a:hover span:last-of-type": { textDecoration: "underline" },
9670
- ...pending ? {
9671
- animation: "toolTrailPulse 1.4s ease-in-out infinite",
9672
- "@keyframes toolTrailPulse": {
9673
- "0%, 100%": { opacity: 1 },
9674
- "50%": { opacity: 0.55 }
9724
+ maxWidth: "100%",
9725
+ px: 1,
9726
+ py: 0.35,
9727
+ borderRadius: 1.5,
9728
+ fontSize: "0.78rem",
9729
+ lineHeight: 1.4,
9730
+ color: item.ok ? theme.palette.text.secondary : theme.palette.error.main,
9731
+ bgcolor: alpha4(theme.palette.text.primary, 0.04),
9732
+ border: `1px solid ${alpha4(theme.palette.text.primary, 0.1)}`,
9733
+ "& a": {
9734
+ color: "inherit",
9735
+ textDecoration: "none",
9736
+ display: "inline-flex",
9737
+ alignItems: "center",
9738
+ gap: 0.6,
9739
+ minWidth: 0
9675
9740
  },
9676
- "@media (prefers-reduced-motion: reduce)": { animation: "none" }
9677
- } : {}
9678
- }),
9679
- children: item.url && item.ok && !pending ? /* @__PURE__ */ jsx11("a", { href: item.url, target: "_blank", rel: "noopener noreferrer", children: body }) : body
9680
- },
9681
- `${item.tool}-${i}`
9682
- );
9683
- })
9684
- }
9685
- );
9741
+ "& a:hover span:last-of-type": { textDecoration: "underline" },
9742
+ ...pending ? {
9743
+ animation: "toolTrailPulse 1.4s ease-in-out infinite",
9744
+ "@keyframes toolTrailPulse": {
9745
+ "0%, 100%": { opacity: 1 },
9746
+ "50%": { opacity: 0.55 }
9747
+ },
9748
+ "@media (prefers-reduced-motion: reduce)": { animation: "none" }
9749
+ } : {}
9750
+ }),
9751
+ children: item.url && item.ok && !pending ? /* @__PURE__ */ jsx11("a", { href: item.url, target: "_blank", rel: "noopener noreferrer", children: body }) : body
9752
+ },
9753
+ `${item.tool}-${i}`
9754
+ );
9755
+ })
9756
+ }
9757
+ )
9758
+ ] });
9686
9759
  };
9687
9760
  var tool_trail_view_default = ToolTrail;
9688
9761
 
@@ -9792,7 +9865,7 @@ var ChatMessages = ({
9792
9865
  transition: "min-height 0.25s cubic-bezier(0.4, 0, 0.2, 1)"
9793
9866
  },
9794
9867
  children: [
9795
- /* @__PURE__ */ jsx12(tool_trail_view_default, { items: entry.toolsUsed }),
9868
+ /* @__PURE__ */ jsx12(tool_trail_view_default, { items: entry.toolsUsed, attachments: entry.images }),
9796
9869
  /* @__PURE__ */ jsx12(
9797
9870
  Box10,
9798
9871
  {
@@ -9900,7 +9973,7 @@ var ChatMessages = ({
9900
9973
  var chat_messages_default = ChatMessages;
9901
9974
 
9902
9975
  // src/chat/chat-input.tsx
9903
- import { useEffect as useEffect11, useRef as useRef6, useState as useState14 } from "react";
9976
+ import React9, { useEffect as useEffect11, useRef as useRef6, useState as useState14 } from "react";
9904
9977
  import {
9905
9978
  Box as Box14,
9906
9979
  Button as Button4,
@@ -10462,7 +10535,7 @@ var Transcriber = ({ onTranscriptionCompleted }) => {
10462
10535
  var transcriber_default = Transcriber;
10463
10536
 
10464
10537
  // src/chat/memory-modal.tsx
10465
- import React7, { useState as useState13, useEffect as useEffect10 } from "react";
10538
+ import React8, { useState as useState13, useEffect as useEffect10 } from "react";
10466
10539
  import { useTheme as useTheme9, alpha as alpha7, keyframes } from "@mui/material/styles";
10467
10540
  import {
10468
10541
  Box as Box13,
@@ -11396,7 +11469,7 @@ var MemoryModal = ({ open, onClose }) => {
11396
11469
  }
11397
11470
  ) : filteredMemories.map((memory, memoryIndex) => {
11398
11471
  const isSelected = selectedIds.includes(memory.id);
11399
- return /* @__PURE__ */ jsxs12(React7.Fragment, { children: [
11472
+ return /* @__PURE__ */ jsxs12(React8.Fragment, { children: [
11400
11473
  /* @__PURE__ */ jsx15(List, { sx: { listStyle: "none", pl: 0, m: 0 }, children: /* @__PURE__ */ jsxs12(
11401
11474
  ListItem,
11402
11475
  {
@@ -12678,6 +12751,273 @@ function markAiReminderSeen(now = Date.now(), storage = defaultStorage()) {
12678
12751
  // src/chat/chat-input.tsx
12679
12752
  import { useTheme as useTheme10, alpha as alpha8 } from "@mui/material/styles";
12680
12753
 
12754
+ // src/services/images/imageTools.ts
12755
+ var IMAGE_TOOL_NAMES = /* @__PURE__ */ new Set(["image_generation", "image_edit"]);
12756
+ var IMAGE_TOOL_TIMEOUT_MS = 12e4;
12757
+ var IMAGE_JOB_MAX_WAIT_MS = 40 * 6e4;
12758
+ var IMAGE_JOB_POLL_MS = 2500;
12759
+ var MAX_IMAGE_UPLOAD_BYTES = 20 * 1024 * 1024;
12760
+ var MAX_IMAGES_PER_MESSAGE = 4;
12761
+ var MAX_UPLOAD_SIDE = 2048;
12762
+ var canonicalImageTool = (name) => name.replace(/-/g, "_");
12763
+ var isImageTool = (name) => IMAGE_TOOL_NAMES.has(canonicalImageTool(name));
12764
+ var isImageEditTool = (name) => canonicalImageTool(name) === "image_edit";
12765
+ var IMAGE_PLAN_REQUIRED = "image_plan_required";
12766
+ var PLAN_REQUIRED_SENTENCE = "Image generation and photo editing are on paid plans.";
12767
+ var PLAN_REQUIRED_NO_LINK = "Image generation and photo editing aren't included with this account.";
12768
+ var PLANS_PATH = IS_STORE_BUILD ? "" : "/plans";
12769
+ var PLANS_LINK_TEXT = IS_STORE_BUILD ? "" : "See plans";
12770
+ var CLOUD_RUNNING_LABEL = "Running in the cloud to skip the queue";
12771
+ var isPlanRequired = (data) => !!data && data.code === IMAGE_PLAN_REQUIRED;
12772
+ var isTerminalImageStatus = (status) => status === "completed" || status === "failed" || status === "refused" || status === "cancelled";
12773
+ var minutesOf = (seconds) => typeof seconds === "number" && seconds > 0 ? Math.max(1, Math.ceil(seconds / 60)) : void 0;
12774
+ var imageQueueLabel = (base, progress) => {
12775
+ if (!progress) return base;
12776
+ if (progress.status === "running") {
12777
+ return progress.backend === "cloud" ? CLOUD_RUNNING_LABEL : `${base} \u2014 working on it now`;
12778
+ }
12779
+ if (progress.status !== "queued") return base;
12780
+ const minutes = minutesOf(progress.etaSeconds);
12781
+ const wait = minutes === void 0 ? "" : minutes === 1 ? ", about a minute" : `, about ${minutes} min`;
12782
+ const position = typeof progress.position === "number" && progress.position > 0 ? ` at position ${progress.position}` : "";
12783
+ return `${base} \u2014 queued${position}${wait}`;
12784
+ };
12785
+ var quotaLine = (quota) => {
12786
+ if (!quota) return void 0;
12787
+ if (quota.unlimited || quota.limit === null || quota.limit === void 0)
12788
+ return void 0;
12789
+ const remaining = typeof quota.remaining === "number" ? quota.remaining : Math.max(0, quota.limit - (quota.used ?? 0));
12790
+ return `${remaining} of ${quota.limit} image generations left today.`;
12791
+ };
12792
+ var formatImageResult = (data, kind, options = {}) => {
12793
+ if (isPlanRequired(data)) {
12794
+ const forModel = "Image tools aren't on the user's plan and they were told so. Acknowledge it in one short sentence. Don't retry, don't describe an image and don't suggest workarounds.";
12795
+ if (IS_STORE_BUILD || options.noUpgrade) {
12796
+ return { inline: PLAN_REQUIRED_NO_LINK, forModel };
12797
+ }
12798
+ const sentence = (data.error ?? data.message ?? PLAN_REQUIRED_SENTENCE).trim();
12799
+ return {
12800
+ inline: `${sentence} [${PLANS_LINK_TEXT}](${data.upgradeUrl || PLANS_PATH})`,
12801
+ forModel
12802
+ };
12803
+ }
12804
+ const url = data.imageUrl ?? void 0;
12805
+ if (!url) {
12806
+ return {
12807
+ inline: "",
12808
+ forModel: kind === "edit" ? "The photo edit finished but returned no image." : "Image generated successfully."
12809
+ };
12810
+ }
12811
+ const alt = kind === "edit" ? "Edited photo" : "Generated image";
12812
+ const notes = [];
12813
+ if (data.backend === "cloud") notes.push("Ran in the cloud to skip the queue.");
12814
+ if (data.stashUrl) notes.push("Saved to your Stash.");
12815
+ notes.push("This link expires in about 2 hours.");
12816
+ const quota = quotaLine(data.quota);
12817
+ if (quota) notes.push(quota);
12818
+ const inline = `![${alt}](${url})
12819
+
12820
+ _${notes.join(" ")}_`;
12821
+ return {
12822
+ inline,
12823
+ forModel: kind === "edit" ? "The edited photo is shown to the user with a before/after view. Briefly say what was changed; do not describe the image link." : "The image is shown to the user. Briefly describe what you made; do not repeat the link."
12824
+ };
12825
+ };
12826
+ var authHeaders = (token) => token ? { Authorization: `Bearer ${token}` } : {};
12827
+ var baseUrl = (gatewayUrl) => gatewayUrl.replace(/\/+$/, "");
12828
+ var readJsonSafe = async (response) => {
12829
+ try {
12830
+ return await response.json();
12831
+ } catch {
12832
+ return {};
12833
+ }
12834
+ };
12835
+ var pollImageJob = async (jobId, http, options = {}) => {
12836
+ const fetchImpl = http.fetchImpl ?? fetch;
12837
+ const sleep = options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
12838
+ const deadline = Date.now() + (options.maxWaitMs ?? IMAGE_JOB_MAX_WAIT_MS);
12839
+ const pollMs = options.pollMs ?? IMAGE_JOB_POLL_MS;
12840
+ let last = { status: "queued", jobId };
12841
+ let failures = 0;
12842
+ while (Date.now() < deadline) {
12843
+ if (options.signal?.aborted) break;
12844
+ await sleep(pollMs);
12845
+ try {
12846
+ const response = await fetchImpl(
12847
+ `${baseUrl(http.gatewayUrl)}/api/mcp/image-jobs/${encodeURIComponent(jobId)}`,
12848
+ {
12849
+ headers: authHeaders(http.token),
12850
+ signal: options.signal
12851
+ }
12852
+ );
12853
+ const data = await readJsonSafe(response);
12854
+ if (response.status === 404) {
12855
+ return {
12856
+ ...last,
12857
+ status: "failed",
12858
+ error: "The image job was lost. Please try again."
12859
+ };
12860
+ }
12861
+ failures = 0;
12862
+ last = { ...last, ...data };
12863
+ options.onProgress?.({
12864
+ status: data.status ?? "queued",
12865
+ position: data.position,
12866
+ etaSeconds: data.etaSeconds,
12867
+ message: data.message,
12868
+ backend: data.backend
12869
+ });
12870
+ if (isTerminalImageStatus(data.status)) return last;
12871
+ } catch (error) {
12872
+ if (options.signal?.aborted) break;
12873
+ failures += 1;
12874
+ if (failures >= 5) {
12875
+ return {
12876
+ ...last,
12877
+ status: "failed",
12878
+ error: error instanceof Error ? error.message : "The image service stopped answering."
12879
+ };
12880
+ }
12881
+ }
12882
+ }
12883
+ return {
12884
+ ...last,
12885
+ status: last.status && isTerminalImageStatus(last.status) ? last.status : "failed",
12886
+ error: last.error ?? "Timed out waiting for the image. It may still finish; check your Stash later."
12887
+ };
12888
+ };
12889
+ var ImageEditAcknowledgementRequired = class extends Error {
12890
+ constructor(text) {
12891
+ super(text);
12892
+ this.text = text;
12893
+ this.name = "ImageEditAcknowledgementRequired";
12894
+ }
12895
+ };
12896
+ var ACKNOWLEDGEMENT_TEXT = "Only upload photos of yourself or people who agreed. Don't use this to create images that deceive or harass.";
12897
+ var dataUrlToBlob = (dataUrl) => {
12898
+ const match = /^data:([^;,]+)?(;base64)?,(.*)$/s.exec(dataUrl);
12899
+ if (!match) throw new Error("Not a data URL.");
12900
+ const type = match[1] || "application/octet-stream";
12901
+ const payload = match[3];
12902
+ if (match[2]) {
12903
+ const binary = atob(payload);
12904
+ const bytes = new Uint8Array(binary.length);
12905
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
12906
+ return new Blob([bytes], { type });
12907
+ }
12908
+ return new Blob([decodeURIComponent(payload)], { type });
12909
+ };
12910
+ var extensionFor = (type) => type === "image/png" ? "png" : type === "image/webp" ? "webp" : "jpg";
12911
+ var submitImageEdit = async (request) => {
12912
+ const fetchImpl = request.fetchImpl ?? fetch;
12913
+ const blob = dataUrlToBlob(request.imageDataUrl);
12914
+ const form = new FormData();
12915
+ form.append("image", blob, `photo.${extensionFor(blob.type)}`);
12916
+ form.append("prompt", request.instruction);
12917
+ if (typeof request.strength === "number")
12918
+ form.append("strength", String(request.strength));
12919
+ const response = await fetchImpl(`${baseUrl(request.gatewayUrl)}/api/mcp/edit-image`, {
12920
+ method: "POST",
12921
+ headers: authHeaders(request.token),
12922
+ body: form,
12923
+ signal: request.signal
12924
+ });
12925
+ const data = await readJsonSafe(response);
12926
+ if (response.status === 428) {
12927
+ throw new ImageEditAcknowledgementRequired(data.error ?? ACKNOWLEDGEMENT_TEXT);
12928
+ }
12929
+ return { status: response.status, data };
12930
+ };
12931
+ var acknowledgeImageEdit = async (http) => {
12932
+ const fetchImpl = http.fetchImpl ?? fetch;
12933
+ const response = await fetchImpl(
12934
+ `${baseUrl(http.gatewayUrl)}/api/mcp/image-edit/acknowledgement`,
12935
+ {
12936
+ method: "POST",
12937
+ headers: authHeaders(http.token)
12938
+ }
12939
+ );
12940
+ return response.ok;
12941
+ };
12942
+ var isHeicFile = (file) => {
12943
+ const type = (file.type ?? "").toLowerCase();
12944
+ if (type === "image/heic" || type === "image/heif") return true;
12945
+ return /\.(heic|heif)$/i.test(file.name ?? "");
12946
+ };
12947
+ var isImageFile = (file) => (file.type ?? "").toLowerCase().startsWith("image/") || isHeicFile(file);
12948
+ var defaultReadAsDataUrl = (file) => new Promise((resolve, reject) => {
12949
+ const reader = new FileReader();
12950
+ reader.onload = () => resolve(String(reader.result ?? ""));
12951
+ reader.onerror = () => reject(reader.error ?? new Error("Could not read the file."));
12952
+ reader.readAsDataURL(file);
12953
+ });
12954
+ var fitWithin = (width, height, maxSide) => {
12955
+ const longest = Math.max(width, height);
12956
+ if (longest <= maxSide) return { width, height };
12957
+ const scale = maxSide / longest;
12958
+ return {
12959
+ width: Math.max(1, Math.round(width * scale)),
12960
+ height: Math.max(1, Math.round(height * scale))
12961
+ };
12962
+ };
12963
+ var prepareImageFile = async (file, deps = {}) => {
12964
+ if (file.size > MAX_IMAGE_UPLOAD_BYTES) {
12965
+ return {
12966
+ ok: false,
12967
+ reason: `${file.name || "That photo"} is larger than 20 MB. Please use a smaller copy.`
12968
+ };
12969
+ }
12970
+ const heic = isHeicFile(file);
12971
+ const readAsDataUrl = deps.readAsDataUrl ?? defaultReadAsDataUrl;
12972
+ const createBitmap = deps.createBitmap ?? (typeof createImageBitmap === "function" ? (blob) => createImageBitmap(blob) : void 0);
12973
+ const createCanvas = deps.createCanvas ?? ((width, height) => {
12974
+ const canvas2 = document.createElement("canvas");
12975
+ canvas2.width = width;
12976
+ canvas2.height = height;
12977
+ return canvas2;
12978
+ });
12979
+ let bitmap;
12980
+ if (createBitmap) {
12981
+ try {
12982
+ bitmap = await createBitmap(file);
12983
+ } catch {
12984
+ bitmap = void 0;
12985
+ }
12986
+ }
12987
+ if (heic) {
12988
+ if (!bitmap) {
12989
+ return {
12990
+ ok: false,
12991
+ reason: `${file.name || "That photo"} is HEIC and this browser can't decode it. Export it as JPEG (or use Bandit on your phone) and try again.`
12992
+ };
12993
+ }
12994
+ } else if (!bitmap || Math.max(bitmap.width, bitmap.height) <= MAX_UPLOAD_SIDE) {
12995
+ bitmap?.close?.();
12996
+ try {
12997
+ return { ok: true, dataUrl: await readAsDataUrl(file), converted: false };
12998
+ } catch (error) {
12999
+ return {
13000
+ ok: false,
13001
+ reason: error instanceof Error ? error.message : "Could not read the photo."
13002
+ };
13003
+ }
13004
+ }
13005
+ const size = fitWithin(bitmap.width, bitmap.height, MAX_UPLOAD_SIDE);
13006
+ const canvas = createCanvas(size.width, size.height);
13007
+ const context = canvas.getContext("2d");
13008
+ if (!context) {
13009
+ bitmap.close?.();
13010
+ return { ok: false, reason: "This browser can't prepare the photo for upload." };
13011
+ }
13012
+ context.drawImage(bitmap, 0, 0, size.width, size.height);
13013
+ bitmap.close?.();
13014
+ const dataUrl = canvas.toDataURL("image/jpeg", 0.92);
13015
+ if (!dataUrl.startsWith("data:image/")) {
13016
+ return { ok: false, reason: "This browser can't prepare the photo for upload." };
13017
+ }
13018
+ return { ok: true, dataUrl, converted: true };
13019
+ };
13020
+
12681
13021
  // src/store/voiceModeStore.ts
12682
13022
  import { create as create7 } from "zustand";
12683
13023
  var useVoiceModeStore = create7((set) => ({
@@ -12716,7 +13056,7 @@ var useVoiceModeStore = create7((set) => ({
12716
13056
  // src/chat/chat-input.tsx
12717
13057
  import { useShallow } from "zustand/react/shallow";
12718
13058
  import { Fragment as Fragment10, jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
12719
- var DOCUMENT_ACCEPT = ".txt,.docx,.pdf,.md,.json,.js,.ts,.tsx,.py,.java,.c,.cpp,.cs,image/*";
13059
+ var DOCUMENT_ACCEPT = ".txt,.docx,.pdf,.md,.json,.js,.ts,.tsx,.py,.java,.c,.cpp,.cs,image/*,.heic,.heif";
12720
13060
  var MEETING_ACCEPT = `${DOCUMENT_ACCEPT},.vtt,.srt,audio/*,video/*`;
12721
13061
  var isTextDocument = (file) => /\.(txt|md|docx)$/i.test(file.name) || file.type.startsWith("text/");
12722
13062
  var ChatInput = (props) => {
@@ -12827,30 +13167,43 @@ var ChatInput = (props) => {
12827
13167
  };
12828
13168
  loadBrandingText();
12829
13169
  }, []);
13170
+ const [attachmentNotice, setAttachmentNotice] = useState14(null);
13171
+ const attachImages = React9.useCallback(
13172
+ async (images) => {
13173
+ if (images.length === 0) return;
13174
+ let notice = null;
13175
+ const accepted = [];
13176
+ for (const image of images) {
13177
+ if (pastedImages.length + accepted.length >= MAX_IMAGES_PER_MESSAGE) {
13178
+ notice = `Up to ${MAX_IMAGES_PER_MESSAGE} photos per message.`;
13179
+ break;
13180
+ }
13181
+ const prepared = await prepareImageFile(image);
13182
+ if (prepared.ok) accepted.push(prepared.dataUrl);
13183
+ else notice = prepared.reason;
13184
+ }
13185
+ if (accepted.length) setPastedImages((prev) => [...prev, ...accepted]);
13186
+ setAttachmentNotice(notice);
13187
+ if (notice) debugLogger.info("Photo not attached", { reason: notice });
13188
+ },
13189
+ [pastedImages.length, setPastedImages]
13190
+ );
12830
13191
  useEffect11(() => {
12831
13192
  const handlePaste = (e) => {
12832
13193
  const items = e.clipboardData?.items;
12833
- if (items) {
12834
- for (const item of items) {
12835
- if (item.type.startsWith("image/")) {
12836
- const file = item.getAsFile();
12837
- if (file) {
12838
- const reader = new FileReader();
12839
- reader.onload = (event) => {
12840
- const result = event.target?.result;
12841
- if (result) {
12842
- setPastedImages((prev) => [...prev, result]);
12843
- }
12844
- };
12845
- reader.readAsDataURL(file);
12846
- }
12847
- }
13194
+ if (!items) return;
13195
+ const files = [];
13196
+ for (const item of items) {
13197
+ if (item.type.startsWith("image/")) {
13198
+ const file = item.getAsFile();
13199
+ if (file) files.push(file);
12848
13200
  }
12849
13201
  }
13202
+ if (files.length) void attachImages(files);
12850
13203
  };
12851
13204
  document.addEventListener("paste", handlePaste);
12852
13205
  return () => document.removeEventListener("paste", handlePaste);
12853
- }, [setPastedImages]);
13206
+ }, [attachImages]);
12854
13207
  const meetingFileProblem = (file) => {
12855
13208
  if (!isRecordingFile(file)) return null;
12856
13209
  if (!canTranscribeRecordings) {
@@ -12871,8 +13224,8 @@ var ChatInput = (props) => {
12871
13224
  });
12872
13225
  };
12873
13226
  const acceptFiles = (selected) => {
12874
- const images = selected.filter((file) => file.type.startsWith("image/"));
12875
- const others = selected.filter((file) => !file.type.startsWith("image/"));
13227
+ const images = selected.filter((file) => isImageFile(file));
13228
+ const others = selected.filter((file) => !isImageFile(file));
12876
13229
  const meetingFiles = meetingNotesEnabled ? others.filter(isMeetingFile) : [];
12877
13230
  const nonImages = others.filter((file) => !meetingFiles.includes(file));
12878
13231
  if (meetingFiles.length > 0) {
@@ -12902,16 +13255,7 @@ var ChatInput = (props) => {
12902
13255
  const safeNonImages = nonImages.filter((file) => file.size <= 1e6);
12903
13256
  setFileInputs((prev) => [...prev, ...safeNonImages]);
12904
13257
  safeNonImages.forEach(checkForTranscript);
12905
- images.forEach((image) => {
12906
- const reader = new FileReader();
12907
- reader.onload = (event) => {
12908
- const result = event.target?.result;
12909
- if (result) {
12910
- setPastedImages((prev) => [...prev, result]);
12911
- }
12912
- };
12913
- reader.readAsDataURL(image);
12914
- });
13258
+ void attachImages(images);
12915
13259
  };
12916
13260
  const [dragging, setDragging] = useState14(false);
12917
13261
  const dropHandlers = !isPlaygroundMode2 && isDocumentUploadEnabled ? {
@@ -13091,19 +13435,26 @@ var ChatInput = (props) => {
13091
13435
  const showSendButton = streamingActive || hasComposeContent || !isVoiceModeEligible;
13092
13436
  const renderAttachmentButton = (key) => {
13093
13437
  if (!hasAttachmentAction) return null;
13094
- return /* @__PURE__ */ jsx16(Tooltip8, { title: "Attach files or images", children: /* @__PURE__ */ jsx16(
13095
- ComposerControl,
13438
+ return /* @__PURE__ */ jsx16(
13439
+ Tooltip8,
13096
13440
  {
13097
- "aria-label": "Attach files or images",
13098
- onClick: () => fileInputRef.current?.click(),
13099
- visualSize: primaryIconSize,
13100
- hitSize: controlHitSize,
13101
- surface: tokens.control.surface,
13102
- surfaceHover: tokens.control.surfaceHover,
13103
- foreground: tokens.control.foreground,
13104
- children: /* @__PURE__ */ jsx16(Plus2, { fontSize: "small" })
13105
- }
13106
- ) }, key ?? "attach");
13441
+ title: "Attach files or photos (JPEG, PNG, WebP, HEIC up to 20 MB)",
13442
+ children: /* @__PURE__ */ jsx16(
13443
+ ComposerControl,
13444
+ {
13445
+ "aria-label": "Attach files or images",
13446
+ onClick: () => fileInputRef.current?.click(),
13447
+ visualSize: primaryIconSize,
13448
+ hitSize: controlHitSize,
13449
+ surface: tokens.control.surface,
13450
+ surfaceHover: tokens.control.surfaceHover,
13451
+ foreground: tokens.control.foreground,
13452
+ children: /* @__PURE__ */ jsx16(Plus2, { fontSize: "small" })
13453
+ }
13454
+ )
13455
+ },
13456
+ key ?? "attach"
13457
+ );
13107
13458
  };
13108
13459
  const renderMemoryButton = (key) => {
13109
13460
  if (!hasMemoryAction) return null;
@@ -13203,6 +13554,15 @@ var ChatInput = (props) => {
13203
13554
  gap: isMobile ? compactMobile ? 0.75 : 1 : 0.5
13204
13555
  },
13205
13556
  children: [
13557
+ attachmentNotice && !sendError && /* @__PURE__ */ jsx16(
13558
+ Box14,
13559
+ {
13560
+ role: "status",
13561
+ sx: { px: 0.5, pt: isMobile ? 0 : 0.5 },
13562
+ "data-testid": "attachment-notice",
13563
+ children: /* @__PURE__ */ jsx16(Typography8, { variant: "caption", sx: { color: tokens.status.error }, children: attachmentNotice })
13564
+ }
13565
+ ),
13206
13566
  sendError && /* @__PURE__ */ jsxs13(
13207
13567
  Box14,
13208
13568
  {
@@ -15184,7 +15544,7 @@ var gatewayBase = () => {
15184
15544
  if (!url) throw new Error("Gateway API is not configured.");
15185
15545
  return url;
15186
15546
  };
15187
- var authHeaders = () => {
15547
+ var authHeaders2 = () => {
15188
15548
  const headers = { "Content-Type": "application/json" };
15189
15549
  const token = authenticationService.getToken();
15190
15550
  if (token) headers["Authorization"] = `Bearer ${token}`;
@@ -15199,25 +15559,25 @@ var parse = async (res) => {
15199
15559
  }
15200
15560
  return body;
15201
15561
  };
15202
- var listMcpServers = async () => parse(await fetch(`${gatewayBase()}/mcp/servers`, { headers: authHeaders() }));
15562
+ var listMcpServers = async () => parse(await fetch(`${gatewayBase()}/mcp/servers`, { headers: authHeaders2() }));
15203
15563
  var addMcpServer = async (input) => parse(
15204
15564
  await fetch(`${gatewayBase()}/mcp/servers`, {
15205
15565
  method: "POST",
15206
- headers: authHeaders(),
15566
+ headers: authHeaders2(),
15207
15567
  body: JSON.stringify(input)
15208
15568
  })
15209
15569
  );
15210
15570
  var updateMcpServer = async (id, input) => parse(
15211
15571
  await fetch(`${gatewayBase()}/mcp/servers/${encodeURIComponent(id)}`, {
15212
15572
  method: "PUT",
15213
- headers: authHeaders(),
15573
+ headers: authHeaders2(),
15214
15574
  body: JSON.stringify(input)
15215
15575
  })
15216
15576
  );
15217
15577
  var deleteMcpServer = async (id) => {
15218
15578
  const res = await fetch(`${gatewayBase()}/mcp/servers/${encodeURIComponent(id)}`, {
15219
15579
  method: "DELETE",
15220
- headers: authHeaders()
15580
+ headers: authHeaders2()
15221
15581
  });
15222
15582
  if (!res.ok && res.status !== 204) {
15223
15583
  throw new Error(`Could not remove the server (${res.status}).`);
@@ -15225,7 +15585,7 @@ var deleteMcpServer = async (id) => {
15225
15585
  };
15226
15586
  var discoverMcpTools = async (id) => parse(
15227
15587
  await fetch(`${gatewayBase()}/mcp/servers/${encodeURIComponent(id)}/tools`, {
15228
- headers: authHeaders()
15588
+ headers: authHeaders2()
15229
15589
  })
15230
15590
  );
15231
15591
 
@@ -15342,6 +15702,38 @@ var imageGenerationTool = {
15342
15702
  method: "POST",
15343
15703
  isBuiltIn: true
15344
15704
  };
15705
+ var imageEditTool = {
15706
+ id: "image-edit",
15707
+ name: "image_edit",
15708
+ description: "Edit a photo the user attached to this message (lighting, style, background, remove or add something)",
15709
+ enabled: true,
15710
+ type: "function",
15711
+ function: {
15712
+ name: "image_edit",
15713
+ description: "Edit a photo the user attached to this message. Use when they ask to change, touch up, restyle or clean up their own photo.",
15714
+ parameters: {
15715
+ type: "object",
15716
+ properties: {
15717
+ instruction: {
15718
+ type: "string",
15719
+ description: "What to change, in plain words (e.g. 'brighten the lighting', 'make it a watercolor', 'remove the background')"
15720
+ },
15721
+ image: {
15722
+ type: "integer",
15723
+ description: "Which attached photo to edit, 1-based. Omit for the most recent one."
15724
+ },
15725
+ strength: {
15726
+ type: "number",
15727
+ description: "How far to depart from the original, 0.05 (subtle) to 1 (free). Omit for the model default."
15728
+ }
15729
+ },
15730
+ required: ["instruction"]
15731
+ }
15732
+ },
15733
+ endpoint: "/mcp/edit-image",
15734
+ method: "POST",
15735
+ isBuiltIn: true
15736
+ };
15345
15737
  var CREATE_FILE_FORMATS = [
15346
15738
  "md",
15347
15739
  "txt",
@@ -15442,6 +15834,7 @@ var defaultTools = [
15442
15834
  webSearchTool,
15443
15835
  webFetchTool,
15444
15836
  imageGenerationTool,
15837
+ imageEditTool,
15445
15838
  createFileTool,
15446
15839
  createScheduleTool
15447
15840
  ];
@@ -15744,6 +16137,9 @@ var executeMCPTool = async (toolCall) => {
15744
16137
  endpoint: tool.endpoint,
15745
16138
  parameters: toolCall.parameters
15746
16139
  });
16140
+ if (isImageEditTool(tool.function.name)) {
16141
+ return executeImageEdit(toolCall, settings.gatewayApiUrl);
16142
+ }
15747
16143
  const requestOptions = {
15748
16144
  method,
15749
16145
  headers: {
@@ -15776,7 +16172,8 @@ var executeMCPTool = async (toolCall) => {
15776
16172
  }
15777
16173
  }
15778
16174
  const controller = new AbortController();
15779
- const timeoutId = setTimeout(() => controller.abort(), 3e4);
16175
+ const timeoutMs = isImageTool(tool.function.name) ? IMAGE_TOOL_TIMEOUT_MS : 3e4;
16176
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
15780
16177
  let response;
15781
16178
  try {
15782
16179
  response = await fetch(url, { ...requestOptions, signal: controller.signal });
@@ -15793,10 +16190,13 @@ var executeMCPTool = async (toolCall) => {
15793
16190
  });
15794
16191
  return {
15795
16192
  success: false,
15796
- error: `API call failed: ${response.status} ${response.statusText}`,
16193
+ error: toolErrorMessage(response, data),
15797
16194
  data
15798
16195
  };
15799
16196
  }
16197
+ if (response.status === 202 && isImageTool(tool.function.name)) {
16198
+ return followImageJob(data, toolCall, settings.gatewayApiUrl);
16199
+ }
15800
16200
  debugLogger.info("MCP tool executed successfully", {
15801
16201
  toolName: toolCall.toolName,
15802
16202
  resultPreview: JSON.stringify(data).substring(0, 200) + "..."
@@ -15832,6 +16232,98 @@ var isMCPAvailable = () => {
15832
16232
  const { tools } = useMCPToolsStore.getState();
15833
16233
  return !!(settings?.gatewayApiUrl && tools.some((t) => t.enabled));
15834
16234
  };
16235
+ var toolErrorMessage = (response, data) => {
16236
+ const body = data ?? {};
16237
+ if (typeof body.code === "string") {
16238
+ const text = [body.error, body.message].find(
16239
+ (v) => typeof v === "string" && v.trim()
16240
+ );
16241
+ if (typeof text === "string") return text;
16242
+ }
16243
+ return `API call failed: ${response.status} ${response.statusText}`;
16244
+ };
16245
+ var gatewayBase2 = (gatewayUrl) => gatewayUrl.replace(/\/+$/, "").replace(/\/api$/, "");
16246
+ var followImageJob = async (first2, toolCall, gatewayUrl) => {
16247
+ if (!first2.jobId) {
16248
+ return {
16249
+ success: false,
16250
+ error: "The image service accepted the job but gave no job id.",
16251
+ data: first2
16252
+ };
16253
+ }
16254
+ toolCall.onProgress?.({
16255
+ status: first2.status ?? "queued",
16256
+ position: first2.position,
16257
+ etaSeconds: first2.etaSeconds,
16258
+ message: first2.message,
16259
+ backend: first2.backend
16260
+ });
16261
+ const final = await pollImageJob(
16262
+ first2.jobId,
16263
+ {
16264
+ gatewayUrl: gatewayBase2(gatewayUrl),
16265
+ token: authenticationService.getToken()
16266
+ },
16267
+ { onProgress: toolCall.onProgress }
16268
+ );
16269
+ const merged = {
16270
+ ...first2,
16271
+ ...final,
16272
+ quota: final.quota ?? first2.quota
16273
+ };
16274
+ if (merged.status === "completed") return { success: true, data: merged };
16275
+ return {
16276
+ success: false,
16277
+ error: merged.error ?? merged.message ?? `The image job ended as ${merged.status ?? "unknown"}.`,
16278
+ data: merged
16279
+ };
16280
+ };
16281
+ var executeImageEdit = async (toolCall, gatewayUrl) => {
16282
+ const params = toolCall.parameters ?? {};
16283
+ const attachments = toolCall.attachments ?? [];
16284
+ if (attachments.length === 0) {
16285
+ return {
16286
+ success: false,
16287
+ error: "No photo is attached to this message. Attach the photo you want edited and ask again.",
16288
+ data: { code: "image_required" }
16289
+ };
16290
+ }
16291
+ const raw = params.image;
16292
+ const index = typeof raw === "number" && Number.isFinite(raw) && raw >= 1 && raw <= attachments.length ? Math.floor(raw) - 1 : attachments.length - 1;
16293
+ const instruction = typeof params.instruction === "string" ? params.instruction : typeof params.prompt === "string" ? params.prompt : "";
16294
+ const strength = typeof params.strength === "number" ? params.strength : void 0;
16295
+ const http = {
16296
+ gatewayUrl: gatewayBase2(gatewayUrl),
16297
+ token: authenticationService.getToken()
16298
+ };
16299
+ let status;
16300
+ let data;
16301
+ try {
16302
+ ({ status, data } = await submitImageEdit({
16303
+ ...http,
16304
+ imageDataUrl: attachments[index],
16305
+ instruction,
16306
+ strength
16307
+ }));
16308
+ } catch (error) {
16309
+ if (error instanceof ImageEditAcknowledgementRequired) {
16310
+ return {
16311
+ success: false,
16312
+ error: error.text,
16313
+ data: { code: "acknowledgement_required", error: error.text }
16314
+ };
16315
+ }
16316
+ throw error;
16317
+ }
16318
+ if (status === 202) return followImageJob(data, toolCall, gatewayUrl);
16319
+ if (status >= 200 && status < 300 && data.status === "completed")
16320
+ return { success: true, data };
16321
+ return {
16322
+ success: false,
16323
+ error: data.error ?? data.message ?? `Photo edit failed (${status}).`,
16324
+ data
16325
+ };
16326
+ };
15835
16327
 
15836
16328
  // src/services/schedules/scheduleTool.ts
15837
16329
  var requestScheduleConfirmation = async (args) => {
@@ -16800,7 +17292,8 @@ ${TOOL_JUDGMENT_RULES}
16800
17292
  - Available tools and when to use them:
16801
17293
  * web_search() - when asked about recent/current events, breaking news, live information (weather, prices, sports scores), or when you need to look up documentation, libraries, APIs, error messages, or verify a specific fact
16802
17294
  * web_fetch() - to read the FULL contents of a specific URL you already have. Reach for this when the user wants to "tell me more", "go deeper", "read/open that article", or asks for details about a specific source, link, or article from an EARLIER answer: take that item's URL from the previous Sources list in this conversation and fetch it, then answer from the page's actual content (not just the prior summary)
16803
- * image_generation() - ONLY when explicitly asked to create or generate an image. Runs on Burtson's local GPU; it can take a couple of minutes when the GPU is cold, and is limited to admin and partner accounts
17295
+ * image_generation({"prompt": "...", "size": "1024x1024"}) - ONLY when explicitly asked to create or generate an image. Paid plans only, within a daily allowance per plan (a free account is told image generation is on paid plans \u2014 relay that with the plans link, don't retry). Runs on Burtson's own GPU; when the queue is long a paid job may run in the cloud instead to skip it
17296
+ * image_edit({"instruction": "..."}) - ONLY when the user attached a photo to THIS message and asks to change it (touch up lighting, restyle, remove the background, remove or add something). The attached photo is sent automatically; never ask for a link or describe the photo back. Same daily allowance as image_generation. Nothing sexual involving real people, nothing involving minors in revealing contexts, nothing meant to deceive (fake documents, compromising scenes, impersonation) \u2014 refuse those yourself
16804
17297
  * create_file({"content": "...", "filename": "report.docx", "format": "docx"}) - when the user asks for a downloadable FILE (a document, spreadsheet, slides, markdown, code, etc.) or to "export"/"download"/"save" something. Formats: md, txt, csv, json, html, xml, yaml, docx, pptx, xlsx, pdf. For docx/pptx write well-structured Markdown (use "## " headings to start each slide for pptx). For pdf, prefer complete HTML with inline CSS. For a SPREADSHEET use format "xlsx" and put the data in a "spreadsheet" object, e.g. create_file({"content": "FY26 budget by region", "filename": "budget.xlsx", "format": "xlsx", "spreadsheet": {"title": "FY26 Budget", "sheets": [{"name": "Summary", "columns": [{"header": "Region", "type": "text"}, {"header": "Revenue", "type": "currency", "currency": "USD"}, {"header": "Growth", "type": "percent"}, {"header": "Closed", "type": "date"}], "rows": [["North", 1200.5, 0.12, "2026-01-31"], ["South", 800, 0.08, "2026-02-28"]], "totals": true}]}}) \u2014 numbers as JSON numbers (never "1,200" strings), dates as ISO strings, percents as fractions, {"formula": "B2*C2"} for a formula, one entry in "sheets" per tab; never CSV text for xlsx. The tool result says whether the file was saved to the user's Stash (lasting link \u2014 say so) or is a temporary link (~1 hour \u2014 tell the user it expires). If it is unclear whether they want it shown inline vs. as a downloadable file, use ask_user first.
16805
17298
  * ask_user({"questions": [{"question": "...", "header": "Format", "options": [{"label": "Inline (Recommended)"}, {"label": "Download a file"}]}]}) - when the request is AMBIGUOUS or missing a detail you need to do a good job (unclear scope, audience, format, tone, which of several interpretations they meant), or you are blocked on a decision that is the user's to make and cannot resolve from the request, context, or sensible defaults. Renders clickable options the user answers in one step \u2014 ALWAYS prefer this over asking a clarifying question in prose and ending your turn. Give 1-4 questions, each with 2-4 short options; if one is clearly best, list it first and append " (Recommended)". The user may also type their own answer; act on it directly. Don't overuse it \u2014 when a sensible default exists, take it and note the assumption instead.
16806
17299
  * create_schedule({"name": "Morning AI brief", "prompt": "Summarise the most important AI news of the past day with sources", "preset": "weekdays", "time": "08:00"}) - when the user wants something done on a RECURRING schedule or at a set time each day or week ("every weekday at 8", "each morning", "daily", "every Monday", "send me \u2026 every \u2026"). Put the complete task in "prompt" (self-contained: the run has no chat context). preset: daily | weekdays | weekly (add "weekday") | custom (add "cron"). Omit time_zone to use the user's own. This opens a confirmation card the user must approve and may edit; wait for the tool result and never say a schedule exists until it reports it was created.
@@ -16859,6 +17352,45 @@ ${protocol}`;
16859
17352
  let sawToolBlock = false;
16860
17353
  const nativeToolCalls = [];
16861
17354
  let toolTrail = [];
17355
+ const runImageAwareTool = async (fn, params, attachments, pendingLabel) => {
17356
+ if (!isImageTool(fn)) {
17357
+ return executeMCPTool({ toolName: fn, parameters: params });
17358
+ }
17359
+ const onProgress = (progress) => {
17360
+ const label = imageQueueLabel(
17361
+ isImageEditTool(fn) ? "Editing the photo" : "Generating an image",
17362
+ progress
17363
+ );
17364
+ pushTrail(pendingTrailItemWithStatus(fn, params, label));
17365
+ startWorking(
17366
+ progress.status === "queued" ? label.replace(/^[^—]+— /, "Queued ") : progress.backend === "cloud" ? label : pendingLabel
17367
+ );
17368
+ };
17369
+ const call = () => executeMCPTool({ toolName: fn, parameters: params, attachments, onProgress });
17370
+ let result = await call();
17371
+ const code = (result.data ?? {}).code;
17372
+ if (!result.success && isPlanRequired(result.data)) {
17373
+ return { success: true, data: result.data };
17374
+ }
17375
+ if (!result.success && code === "acknowledgement_required") {
17376
+ const text = (result.data ?? {}).error ?? result.error ?? "";
17377
+ const accepted = await useImageEditAckStore.getState().ask(text);
17378
+ if (!accepted) {
17379
+ return {
17380
+ success: false,
17381
+ error: "The photo edit was not started: the acknowledgement was declined.",
17382
+ data: { code: "acknowledgement_declined" }
17383
+ };
17384
+ }
17385
+ const settings = usePackageSettingsStore.getState().settings;
17386
+ const ok = await acknowledgeImageEdit({
17387
+ gatewayUrl: (settings?.gatewayApiUrl ?? "").replace(/\/+$/, "").replace(/\/api$/, ""),
17388
+ token: authenticationService.getToken()
17389
+ });
17390
+ if (ok) result = await call();
17391
+ }
17392
+ return result;
17393
+ };
16862
17394
  const pushTrail = (item) => {
16863
17395
  const next = upsertTrailItem(toolTrail, item);
16864
17396
  if (next === toolTrail) return;
@@ -16876,6 +17408,7 @@ ${protocol}`;
16876
17408
  "web_search",
16877
17409
  "web_fetch",
16878
17410
  "image_generation",
17411
+ "image_edit",
16879
17412
  "create_file",
16880
17413
  "ask_user",
16881
17414
  "ask-user",
@@ -16898,11 +17431,11 @@ ${protocol}`;
16898
17431
  const fenceIdx = t.search(/```(?:tool_code|TOOL_CODE)/);
16899
17432
  if (fenceIdx !== -1) t = t.slice(0, fenceIdx);
16900
17433
  const callIdx = t.search(
16901
- /\b(?:web_search|web_fetch|image_generation|create_file|ask_user|create_schedule)\s*\(/
17434
+ /\b(?:web_search|web_fetch|image_generation|image_edit|create_file|ask_user|create_schedule)\s*\(/
16902
17435
  );
16903
17436
  if (callIdx !== -1) t = t.slice(0, callIdx);
16904
17437
  const jsonIdx = t.search(
16905
- /\{\s*"name"\s*:\s*"(?:web_search|web_fetch|image_generation|create_file|ask_user|create_schedule)"/
17438
+ /\{\s*"name"\s*:\s*"(?:web_search|web_fetch|image_generation|image_edit|create_file|ask_user|create_schedule)"/
16906
17439
  );
16907
17440
  if (jsonIdx !== -1) t = t.slice(0, jsonIdx);
16908
17441
  t = t.replace(/```[\w-]*\s*$/, "");
@@ -16976,17 +17509,17 @@ ${protocol}`;
16976
17509
  setIsThinking?.(inThinkBlock);
16977
17510
  const visibleMessage = stripThinking2(fullMessage);
16978
17511
  const bareToolMatch = visibleMessage.match(
16979
- /\b(web_search|web_fetch|image_generation|create_file|ask_user|create_schedule)\s*\(/
17512
+ /\b(web_search|web_fetch|image_generation|image_edit|create_file|ask_user|create_schedule)\s*\(/
16980
17513
  ) || // JSON-form calls from cloud/native-tool engines.
16981
17514
  visibleMessage.match(
16982
- /\{\s*"name"\s*:\s*"(web_search|web_fetch|image_generation|create_file|ask_user|create_schedule)"/
17515
+ /\{\s*"name"\s*:\s*"(web_search|web_fetch|image_generation|image_edit|create_file|ask_user|create_schedule)"/
16983
17516
  );
16984
17517
  if (/```(?:tool_code|TOOL_CODE)/.test(visibleMessage) || bareToolMatch) {
16985
17518
  sawToolBlock = true;
16986
17519
  clearFlushTimer();
16987
17520
  const fn = bareToolMatch?.[1];
16988
17521
  startWorking(
16989
- fn === "create_schedule" ? "Setting up your schedule" : fn === "create_file" ? "Creating your file" : fn === "web_search" ? "Searching the web" : fn === "web_fetch" ? "Reading the page" : fn === "image_generation" ? "Generating the image" : "Working on it"
17522
+ fn === "create_schedule" ? "Setting up your schedule" : fn === "create_file" ? "Creating your file" : fn === "web_search" ? "Searching the web" : fn === "web_fetch" ? "Reading the page" : fn === "image_generation" ? "Generating the image" : fn === "image_edit" ? "Editing the photo" : "Working on it"
16990
17523
  );
16991
17524
  }
16992
17525
  latestDisplayMessage = visibleMessage;
@@ -17317,17 +17850,14 @@ A: ${(answers[q.id] || "").trim() || "(no answer)"}`
17317
17850
  const placeholderToken = `<<TOOL_LOADING_${functionName}_${Math.random().toString(36).slice(2)}>>`;
17318
17851
  enhancedMessage = enhancedMessage.replace(match, placeholderToken);
17319
17852
  clearFlushTimer();
17320
- const toolStatus = functionName === "web_search" || functionName === "web-search" ? "Searching the web" : functionName === "web_fetch" || functionName === "web-fetch" ? "Reading the page" : functionName === "image_generation" || functionName === "image-generation" ? "Generating the image" : functionName === "create_file" || functionName === "create-file" ? "Creating your file" : "Working on it";
17853
+ const toolStatus = functionName === "web_search" || functionName === "web-search" ? "Searching the web" : functionName === "web_fetch" || functionName === "web-fetch" ? "Reading the page" : functionName === "image_generation" || functionName === "image-generation" ? "Generating the image" : isImageEditTool(functionName) ? "Editing the photo" : functionName === "create_file" || functionName === "create-file" ? "Creating your file" : "Working on it";
17321
17854
  startWorking(toolStatus);
17322
17855
  pushTrail(pendingTrailItem(functionName, parsedParams));
17323
17856
  telemetryEvent("tool_loop:tool_execute", {
17324
17857
  name: functionName,
17325
17858
  params: parsedParams
17326
17859
  });
17327
- const result = await executeMCPTool({
17328
- toolName: functionName,
17329
- parameters: parsedParams
17330
- });
17860
+ const result = await runImageAwareTool(functionName, parsedParams, imageList, toolStatus);
17331
17861
  telemetryEvent(
17332
17862
  result.success ? "tool_loop:tool_result" : "tool_loop:tool_error",
17333
17863
  {
@@ -17346,15 +17876,13 @@ A: ${(answers[q.id] || "").trim() || "(no answer)"}`
17346
17876
  result,
17347
17877
  collectedSources
17348
17878
  );
17349
- } else if (functionName === "image_generation" || functionName === "image-generation") {
17350
- const imageData = result.data ?? {};
17351
- const { imageUrl, revisedPrompt } = imageData;
17352
- resultText = imageUrl ? `Here you go!
17353
-
17354
- ![Generated image](${imageUrl})
17355
-
17356
- ${revisedPrompt ? `Prompt refinement: \u201C${revisedPrompt}\u201D
17357
- ` : ""}Note: the image link may expire in ~2 hours.` : "Image generated successfully.";
17879
+ } else if (isImageTool(functionName)) {
17880
+ const image = formatImageResult(
17881
+ result.data ?? {},
17882
+ isImageEditTool(functionName) ? "edit" : "generate",
17883
+ { noUpgrade: usePackageSettingsStore.getState().settings?.hideUpgradePrompts === true }
17884
+ );
17885
+ resultText = image.inline || image.forModel;
17358
17886
  } else if (functionName === "create_file" || functionName === "create-file") {
17359
17887
  resultText = describeCreatedFile(result.data).markdown;
17360
17888
  } else if (typeof result.data === "string") {
@@ -17378,7 +17906,7 @@ ${revisedPrompt ? `Prompt refinement: \u201C${revisedPrompt}\u201D
17378
17906
  }
17379
17907
  enhancedMessage = enhancedMessage.replace(placeholderToken, resultText);
17380
17908
  if (result.success) {
17381
- if (functionName === "image_generation" || functionName === "image-generation" || functionName === "create_file" || functionName === "create-file") {
17909
+ if (isImageTool(functionName) || functionName === "create_file" || functionName === "create-file") {
17382
17910
  inlineImageBlocks.push(resultText);
17383
17911
  } else if (functionName !== "check_gateway_health") {
17384
17912
  summarizableResults.push({
@@ -17533,14 +18061,11 @@ A: ${(ans[q.id] || "").trim() || "(no answer)"}`
17533
18061
  );
17534
18062
  return scheduleOutcomeForModel(outcome);
17535
18063
  }
17536
- const status = fn === "create_file" || fn === "create-file" ? "Creating your file" : fn === "web_search" || fn === "web-search" ? "Searching the web" : fn === "web_fetch" || fn === "web-fetch" ? "Reading the page" : fn === "image_generation" || fn === "image-generation" ? "Generating the image" : "Working on it";
18064
+ const status = fn === "create_file" || fn === "create-file" ? "Creating your file" : fn === "web_search" || fn === "web-search" ? "Searching the web" : fn === "web_fetch" || fn === "web-fetch" ? "Reading the page" : fn === "image_generation" || fn === "image-generation" ? "Generating the image" : isImageEditTool(fn) ? "Editing the photo" : "Working on it";
17537
18065
  startWorking(status);
17538
18066
  pushTrail(pendingTrailItem(fn, params));
17539
18067
  telemetryEvent("tool_loop:tool_execute", { name: fn, params });
17540
- const result = await executeMCPTool({
17541
- toolName: fn,
17542
- parameters: params
17543
- });
18068
+ const result = await runImageAwareTool(fn, params, imageList, status);
17544
18069
  record(trailItemForTool(fn, params, result));
17545
18070
  if (!result.success)
17546
18071
  return `That step failed: ${result.error || "unknown error"}.`;
@@ -17553,11 +18078,15 @@ A: ${(ans[q.id] || "").trim() || "(no answer)"}`
17553
18078
  if (created.url) inlineImageBlocks.push(created.markdown);
17554
18079
  return created.modelNote;
17555
18080
  }
17556
- if (fn === "image_generation" || fn === "image-generation") {
17557
- const img = result.data ?? {};
17558
- if (img.imageUrl) {
17559
- inlineImageBlocks.push(`![Generated image](${img.imageUrl})`);
17560
- return "Image generated and shown to the user.";
18081
+ if (isImageTool(fn)) {
18082
+ const image = formatImageResult(
18083
+ result.data ?? {},
18084
+ isImageEditTool(fn) ? "edit" : "generate",
18085
+ { noUpgrade: usePackageSettingsStore.getState().settings?.hideUpgradePrompts === true }
18086
+ );
18087
+ if (image.inline) {
18088
+ inlineImageBlocks.push(image.inline);
18089
+ return image.forModel;
17561
18090
  }
17562
18091
  }
17563
18092
  return formatToolResultForModel(fn, result, collectedSources);
@@ -18033,21 +18562,8 @@ var useNetworkStatus = () => {
18033
18562
  };
18034
18563
 
18035
18564
  // src/chat/chat-app-bar.tsx
18036
- import { Avatar as Avatar10 } from "@mui/material";
18037
18565
  import { useEffect as useEffect21, useRef as useRef16, useState as useState25 } from "react";
18038
- import {
18039
- Box as Box26,
18040
- IconButton as IconButton19,
18041
- Tooltip as Tooltip13,
18042
- useMediaQuery as useMediaQuery10,
18043
- useTheme as useTheme23,
18044
- Dialog as Dialog9,
18045
- DialogTitle as DialogTitle6,
18046
- DialogContent as DialogContent6,
18047
- DialogActions as DialogActions6,
18048
- Typography as Typography21,
18049
- Button as Button12
18050
- } from "@mui/material";
18566
+ import { Box as Box26, IconButton as IconButton19, Tooltip as Tooltip13, useMediaQuery as useMediaQuery10, useTheme as useTheme23 } from "@mui/material";
18051
18567
  import { useNavigate as useNavigate2 } from "react-router-dom";
18052
18568
 
18053
18569
  // src/chat/conversation-drawer.tsx
@@ -22733,16 +23249,17 @@ var SidebarBody = ({ variant, onRequestClose }) => {
22733
23249
  anchorOrigin: { vertical: "bottom", horizontal: "right" },
22734
23250
  transformOrigin: { vertical: "top", horizontal: "right" },
22735
23251
  children: [
22736
- syncEnabled && /* @__PURE__ */ jsxs22(
23252
+ syncEnabled && (syncStatus === "syncing" || syncStatus === "error") && /* @__PURE__ */ jsxs22(
22737
23253
  MenuItem6,
22738
23254
  {
23255
+ disabled: syncStatus === "syncing",
22739
23256
  onClick: () => {
22740
23257
  setMenuAnchor(null);
22741
- if (syncStatus !== "syncing") void runSync({ force: true });
23258
+ void runSync({ force: true });
22742
23259
  },
22743
23260
  children: [
22744
23261
  /* @__PURE__ */ jsx25(ListItemIcon3, { children: syncStatus === "error" ? /* @__PURE__ */ jsx25(AlertCircle, { size: 16, color: theme.palette.error.main }) : /* @__PURE__ */ jsx25(Refresh, { size: 16 }) }),
22745
- /* @__PURE__ */ jsx25(ListItemText3, { children: syncStatus === "error" ? "Sync needs attention. Retry" : syncStatus === "syncing" ? "Syncing\u2026" : "Sync conversations" })
23262
+ /* @__PURE__ */ jsx25(ListItemText3, { children: syncStatus === "error" ? "Sync needs attention. Retry" : "Syncing\u2026" })
22746
23263
  ]
22747
23264
  }
22748
23265
  ),
@@ -23021,12 +23538,12 @@ var MobileSidebar = ({ open, onClose, drag }) => {
23021
23538
  };
23022
23539
 
23023
23540
  // src/chat/sidebar/ProjectsPaneLazy.tsx
23024
- import { Suspense, lazy } from "react";
23541
+ import { Suspense as Suspense2, lazy } from "react";
23025
23542
  import { jsx as jsx28 } from "react/jsx-runtime";
23026
23543
  var Pane = lazy(
23027
- () => import("./ProjectsPane-BSVOVNK7.mjs").then((module) => ({ default: module.ProjectsPane }))
23544
+ () => import("./ProjectsPane-E5D72PUG.mjs").then((module) => ({ default: module.ProjectsPane }))
23028
23545
  );
23029
- var ProjectsPane = (props) => /* @__PURE__ */ jsx28(Suspense, { fallback: null, children: /* @__PURE__ */ jsx28(Pane, { ...props }) });
23546
+ var ProjectsPane = (props) => /* @__PURE__ */ jsx28(Suspense2, { fallback: null, children: /* @__PURE__ */ jsx28(Pane, { ...props }) });
23030
23547
 
23031
23548
  // src/chat/sidebar/useSidebarShortcuts.ts
23032
23549
  import { useEffect as useEffect20 } from "react";
@@ -23107,18 +23624,8 @@ var MobileViewTitle = () => {
23107
23624
  import { useShallow as useShallow2 } from "zustand/react/shallow";
23108
23625
  import { alpha as alpha16 } from "@mui/material/styles";
23109
23626
  import { Fragment as Fragment16, jsx as jsx30, jsxs as jsxs25 } from "react/jsx-runtime";
23110
- var CDN_BASE = "https://cdn.burtson.ai/";
23111
- var banditHead2 = `${CDN_BASE}/images/bandit-head.png`;
23112
- var modelAvatars2 = {
23113
- "Bandit-Core": `${CDN_BASE}/avatars/core-avatar.png`,
23114
- "Bandit-Muse": `${CDN_BASE}/avatars/muse-avatar.png`,
23115
- "Bandit-Logic": `${CDN_BASE}/avatars/logic-avatar.png`,
23116
- "Bandit-D1VA": `${CDN_BASE}/avatars/d1va-avatar.png`,
23117
- "Bandit-Exec": `${CDN_BASE}/avatars/exec-avatar.png`
23118
- };
23119
23627
  var ChatAppBar = ({
23120
23628
  availableModels,
23121
- handleModelChange,
23122
23629
  selectedVoice,
23123
23630
  availableVoices,
23124
23631
  handleVoiceChange,
@@ -23165,8 +23672,6 @@ var ChatAppBar = ({
23165
23672
  setInternalModalOpen(open);
23166
23673
  onMobileConversationsOpenChange?.(open);
23167
23674
  };
23168
- const [confirmModelChangeOpen, setConfirmModelChangeOpen] = useState25(false);
23169
- const [pendingModel, setPendingModel] = useState25(null);
23170
23675
  const {
23171
23676
  conversations,
23172
23677
  currentId,
@@ -23254,10 +23759,6 @@ var ChatAppBar = ({
23254
23759
  );
23255
23760
  };
23256
23761
  const selectedModel = useModelStore((s) => s.selectedModel);
23257
- const currentModel = useModelStore(
23258
- (s) => s.availableModels.find((m) => m.name === selectedModel)
23259
- );
23260
- const currentAvatar = currentModel?.avatarBase64 || modelAvatars2[selectedModel] || banditHead2;
23261
23762
  const engines = useEngineStore((s) => s.engines);
23262
23763
  const selectedEngine = useEngineStore((s) => s.selectedEngine);
23263
23764
  const effectiveEngineId = selectedEngine || usePackageSettingsStore.getState().settings?.defaultModel || "bandit-core";
@@ -23268,7 +23769,6 @@ var ChatAppBar = ({
23268
23769
  useEffect21(() => {
23269
23770
  useEngineStore.getState().fetchEngines();
23270
23771
  }, []);
23271
- const pendingModelAvatar = useModelStore.getState().availableModels.find((m) => m.name === pendingModel)?.avatarBase64 || modelAvatars2[pendingModel || ""] || banditHead2;
23272
23772
  return /* @__PURE__ */ jsxs25(Fragment16, { children: [
23273
23773
  /* @__PURE__ */ jsxs25(
23274
23774
  Box26,
@@ -23415,7 +23915,7 @@ var ChatAppBar = ({
23415
23915
  "aria-pressed": drawerOpen,
23416
23916
  children: [
23417
23917
  /* @__PURE__ */ jsx30(PanelLeft, {}),
23418
- conversations.length > 0 && /* @__PURE__ */ jsx30(
23918
+ conversations.length > 0 && !sectionsLayout && /* @__PURE__ */ jsx30(
23419
23919
  Box26,
23420
23920
  {
23421
23921
  sx: {
@@ -23533,86 +24033,7 @@ var ChatAppBar = ({
23533
24033
  drag: mobileConversationsDrag
23534
24034
  }
23535
24035
  )
23536
- ] }),
23537
- /* @__PURE__ */ jsxs25(
23538
- Dialog9,
23539
- {
23540
- open: confirmModelChangeOpen,
23541
- onClose: () => setConfirmModelChangeOpen(false),
23542
- children: [
23543
- /* @__PURE__ */ jsx30(DialogTitle6, { children: "Change personality and start new conversation?" }),
23544
- /* @__PURE__ */ jsx30(DialogContent6, { children: /* @__PURE__ */ jsxs25(Box26, { display: "flex", alignItems: "center", gap: 2, mt: 1, justifyContent: "center", children: [
23545
- /* @__PURE__ */ jsx30(
23546
- Avatar10,
23547
- {
23548
- src: pendingModelAvatar,
23549
- alt: pendingModel || "Personality Avatar",
23550
- sx: { width: 40, height: 40, filter: "brightness(1.7)" }
23551
- }
23552
- ),
23553
- /* @__PURE__ */ jsxs25(Typography21, { variant: "body2", children: [
23554
- "Your current conversation will be saved, and a new one will begin with",
23555
- " ",
23556
- /* @__PURE__ */ jsx30("strong", { children: pendingModel }),
23557
- "."
23558
- ] })
23559
- ] }) }),
23560
- /* @__PURE__ */ jsxs25(DialogActions6, { children: [
23561
- /* @__PURE__ */ jsx30(Button12, { onClick: () => setConfirmModelChangeOpen(false), children: "Cancel" }),
23562
- /* @__PURE__ */ jsx30(
23563
- Button12,
23564
- {
23565
- onClick: () => {
23566
- if (pendingModel) {
23567
- createNewConversation();
23568
- handleModelChange(pendingModel);
23569
- useModelStore.getState().setSelectedModel(pendingModel);
23570
- (async () => {
23571
- try {
23572
- const storeConfigs2 = [{ name: "config", keyPath: "id" }];
23573
- const current = await indexedDBService_default.get(
23574
- "banditConfig",
23575
- 1,
23576
- "config",
23577
- "main",
23578
- storeConfigs2
23579
- );
23580
- const updated = {
23581
- ...current ?? {},
23582
- id: "main",
23583
- model: {
23584
- ...current?.model || {},
23585
- name: pendingModel,
23586
- selectedModel: pendingModel
23587
- }
23588
- };
23589
- await indexedDBService_default.put(
23590
- "banditConfig",
23591
- 1,
23592
- "config",
23593
- updated,
23594
- storeConfigs2
23595
- );
23596
- } catch (err) {
23597
- debugLogger.error("Failed to persist selectedModel to IndexedDB", {
23598
- error: err
23599
- });
23600
- }
23601
- })();
23602
- const { conversations: conversations2, currentId: currentId2 } = useConversationStore.getState();
23603
- const conv = conversations2.find((c) => c.id === currentId2);
23604
- if (conv) conv.model = pendingModel;
23605
- }
23606
- setConfirmModelChangeOpen(false);
23607
- },
23608
- color: "error",
23609
- children: "Change Personality"
23610
- }
23611
- )
23612
- ] })
23613
- ]
23614
- }
23615
- )
24036
+ ] })
23616
24037
  ] });
23617
24038
  };
23618
24039
  var chat_app_bar_default = ChatAppBar;
@@ -23632,7 +24053,7 @@ var stashBaseUrl = () => {
23632
24053
  return /\/api$/i.test(trimmed) ? trimmed : `${trimmed}/api`;
23633
24054
  };
23634
24055
  var stashAvailable = () => stashBaseUrl() !== null && Boolean(authenticationService.getToken());
23635
- var authHeaders2 = () => {
24056
+ var authHeaders3 = () => {
23636
24057
  const token = authenticationService.getToken();
23637
24058
  if (!token) throw new StashError("Sign in to save to Stash.", 401);
23638
24059
  return { Authorization: `Bearer ${token}` };
@@ -23657,7 +24078,7 @@ var saveToStash = async (args) => {
23657
24078
  const query = args.scope === "team" ? "?scope=team" : "";
23658
24079
  const response = await fetch(`${base}/artifact${query}`, {
23659
24080
  method: "POST",
23660
- headers: authHeaders2(),
24081
+ headers: authHeaders3(),
23661
24082
  body,
23662
24083
  signal: args.signal
23663
24084
  });
@@ -23684,7 +24105,7 @@ var createStashShareLink = async (args) => {
23684
24105
  if (!base) throw new StashError("Stash isn't configured here.");
23685
24106
  const response = await fetch(`${base}/artifact/share`, {
23686
24107
  method: "POST",
23687
- headers: { ...authHeaders2(), "Content-Type": "application/json" },
24108
+ headers: { ...authHeaders3(), "Content-Type": "application/json" },
23688
24109
  body: JSON.stringify({ key: args.key, expiryMinutes: args.expiryMinutes ?? null }),
23689
24110
  signal: args.signal
23690
24111
  });
@@ -24560,7 +24981,7 @@ function startConversationWithBackgroundTitle(question, generateName) {
24560
24981
 
24561
24982
  // src/chat/query-suggestion-picker.tsx
24562
24983
  import { useEffect as useEffect22, useRef as useRef17, useState as useState26 } from "react";
24563
- import { Box as Box27, Button as Button13, Typography as Typography22 } from "@mui/material";
24984
+ import { Box as Box27, Button as Button12, Typography as Typography21 } from "@mui/material";
24564
24985
  import { jsx as jsx31, jsxs as jsxs26 } from "react/jsx-runtime";
24565
24986
  var STARTERS = [
24566
24987
  {
@@ -24649,9 +25070,9 @@ function QuerySuggestionPicker({ onSend, onSelect }) {
24649
25070
  mb: 1.5
24650
25071
  },
24651
25072
  children: [
24652
- /* @__PURE__ */ jsx31(Typography22, { variant: "body2", sx: { fontWeight: 600 }, children: "What would move your work forward?" }),
25073
+ /* @__PURE__ */ jsx31(Typography21, { variant: "body2", sx: { fontWeight: 600 }, children: "What would move your work forward?" }),
24653
25074
  /* @__PURE__ */ jsx31(
24654
- Button13,
25075
+ Button12,
24655
25076
  {
24656
25077
  size: "small",
24657
25078
  startIcon: /* @__PURE__ */ jsx31(Refresh, { size: 14 }),
@@ -24678,7 +25099,7 @@ function QuerySuggestionPicker({ onSend, onSelect }) {
24678
25099
  icon: ArrowRight,
24679
25100
  prompt
24680
25101
  })) : STARTERS).map(({ title, detail, icon: Icon, prompt }) => /* @__PURE__ */ jsxs26(
24681
- Button13,
25102
+ Button12,
24682
25103
  {
24683
25104
  variant: "outlined",
24684
25105
  onClick: () => select(prompt),
@@ -24705,7 +25126,7 @@ function QuerySuggestionPicker({ onSend, onSelect }) {
24705
25126
  ),
24706
25127
  /* @__PURE__ */ jsxs26(Box27, { component: "span", children: [
24707
25128
  /* @__PURE__ */ jsx31(
24708
- Typography22,
25129
+ Typography21,
24709
25130
  {
24710
25131
  component: "span",
24711
25132
  variant: "body2",
@@ -24714,7 +25135,7 @@ function QuerySuggestionPicker({ onSend, onSelect }) {
24714
25135
  }
24715
25136
  ),
24716
25137
  detail && /* @__PURE__ */ jsx31(
24717
- Typography22,
25138
+ Typography21,
24718
25139
  {
24719
25140
  component: "span",
24720
25141
  variant: "caption",
@@ -24735,7 +25156,7 @@ function QuerySuggestionPicker({ onSend, onSelect }) {
24735
25156
  }
24736
25157
  ),
24737
25158
  /* @__PURE__ */ jsx31(
24738
- Typography22,
25159
+ Typography21,
24739
25160
  {
24740
25161
  variant: "caption",
24741
25162
  sx: { display: "block", color: "text.secondary", mt: 1.25 },
@@ -24743,7 +25164,7 @@ function QuerySuggestionPicker({ onSend, onSelect }) {
24743
25164
  }
24744
25165
  ),
24745
25166
  message && /* @__PURE__ */ jsx31(
24746
- Typography22,
25167
+ Typography21,
24747
25168
  {
24748
25169
  role: "status",
24749
25170
  variant: "caption",
@@ -25322,7 +25743,7 @@ function createBanditTheme(config = {}, ...overrides) {
25322
25743
 
25323
25744
  // ../bandit-themes/src/provider.tsx
25324
25745
  import { ThemeProvider } from "@mui/material/styles";
25325
- import * as React20 from "react";
25746
+ import * as React21 from "react";
25326
25747
  import { jsx as jsx32 } from "react/jsx-runtime";
25327
25748
  function BanditThemeProvider({
25328
25749
  theme,
@@ -25332,9 +25753,9 @@ function BanditThemeProvider({
25332
25753
  style,
25333
25754
  children
25334
25755
  }) {
25335
- const resolved = React20.useMemo(() => theme ?? createBanditTheme(config), [theme, config]);
25756
+ const resolved = React21.useMemo(() => theme ?? createBanditTheme(config), [theme, config]);
25336
25757
  const { cssVars, mode } = resolved.bandit;
25337
- React20.useLayoutEffect(() => {
25758
+ React21.useLayoutEffect(() => {
25338
25759
  if (scope !== "document" || typeof document === "undefined") return;
25339
25760
  const root = document.documentElement;
25340
25761
  const previous = /* @__PURE__ */ new Map();
@@ -25475,14 +25896,14 @@ var volcanicDarkTheme = skinThemes["volcanic-dark"];
25475
25896
  var volcanicLightTheme = skinThemes["volcanic-light"];
25476
25897
 
25477
25898
  // ../bandit-themes/src/TenantPreview.tsx
25478
- import Avatar11 from "@mui/material/Avatar";
25899
+ import Avatar10 from "@mui/material/Avatar";
25479
25900
  import Box28 from "@mui/material/Box";
25480
- import Button14 from "@mui/material/Button";
25901
+ import Button13 from "@mui/material/Button";
25481
25902
  import Paper5 from "@mui/material/Paper";
25482
25903
  import Stack2 from "@mui/material/Stack";
25483
- import Typography23 from "@mui/material/Typography";
25904
+ import Typography22 from "@mui/material/Typography";
25484
25905
  import { useTheme as useTheme24 } from "@mui/material/styles";
25485
- import * as React21 from "react";
25906
+ import * as React22 from "react";
25486
25907
  import { jsx as jsx33, jsxs as jsxs27 } from "react/jsx-runtime";
25487
25908
  function TenantPreview({
25488
25909
  accent,
@@ -25498,7 +25919,7 @@ function TenantPreview({
25498
25919
  const pageMode = useTheme24().palette.mode;
25499
25920
  const shown = modes ?? (pageMode === "light" ? ["light", "dark"] : ["dark", "light"]);
25500
25921
  return /* @__PURE__ */ jsxs27(Stack2, { spacing: 1.5, role: "group", "aria-label": "Branding preview", children: [
25501
- title !== null && /* @__PURE__ */ jsx33(Typography23, { component: "h3", variant: "subtitle2", sx: { fontWeight: 600 }, children: title }),
25922
+ title !== null && /* @__PURE__ */ jsx33(Typography22, { component: "h3", variant: "subtitle2", sx: { fontWeight: 600 }, children: title }),
25502
25923
  /* @__PURE__ */ jsx33(
25503
25924
  Box28,
25504
25925
  {
@@ -25535,7 +25956,7 @@ function PreviewPane({
25535
25956
  density,
25536
25957
  renderBody
25537
25958
  }) {
25538
- const theme = React21.useMemo(
25959
+ const theme = React22.useMemo(
25539
25960
  () => createBanditTheme({
25540
25961
  mode,
25541
25962
  skin,
@@ -25555,21 +25976,21 @@ function PreviewPane({
25555
25976
  children: [
25556
25977
  /* @__PURE__ */ jsxs27(Stack2, { direction: "row", spacing: 1.25, alignItems: "center", sx: { minWidth: 0 }, children: [
25557
25978
  logoUrl ? /* @__PURE__ */ jsx33(Box28, { component: "img", src: logoUrl, alt: "", sx: { width: 28, height: 28, objectFit: "contain", flexShrink: 0 } }) : /* @__PURE__ */ jsx33(
25558
- Avatar11,
25979
+ Avatar10,
25559
25980
  {
25560
25981
  "aria-hidden": true,
25561
25982
  sx: { width: 28, height: 28, fontSize: 13, fontWeight: 600, bgcolor: "primary.main", color: "primary.contrastText" },
25562
25983
  children: label.charAt(0).toUpperCase()
25563
25984
  }
25564
25985
  ),
25565
- /* @__PURE__ */ jsx33(Typography23, { sx: { fontWeight: 600, minWidth: 0, flex: 1, overflowWrap: "anywhere" }, children: label }),
25566
- /* @__PURE__ */ jsx33(Typography23, { variant: "caption", color: "text.secondary", sx: { textTransform: "capitalize", flexShrink: 0 }, children: mode })
25986
+ /* @__PURE__ */ jsx33(Typography22, { sx: { fontWeight: 600, minWidth: 0, flex: 1, overflowWrap: "anywhere" }, children: label }),
25987
+ /* @__PURE__ */ jsx33(Typography22, { variant: "caption", color: "text.secondary", sx: { textTransform: "capitalize", flexShrink: 0 }, children: mode })
25567
25988
  ] }),
25568
- tagline ? /* @__PURE__ */ jsx33(Typography23, { variant: "body2", color: "text.secondary", sx: { mt: 0.5 }, children: tagline }) : null,
25989
+ tagline ? /* @__PURE__ */ jsx33(Typography22, { variant: "body2", color: "text.secondary", sx: { mt: 0.5 }, children: tagline }) : null,
25569
25990
  renderBody ? /* @__PURE__ */ jsx33(Box28, { sx: { mt: 2 }, children: renderBody(mode) }) : /* @__PURE__ */ jsxs27(Stack2, { direction: "row", spacing: 1.5, alignItems: "center", sx: { mt: 2 }, children: [
25570
- /* @__PURE__ */ jsx33(Button14, { variant: "contained", size: "small", tabIndex: -1, children: "New chat" }),
25991
+ /* @__PURE__ */ jsx33(Button13, { variant: "contained", size: "small", tabIndex: -1, children: "New chat" }),
25571
25992
  /* @__PURE__ */ jsx33(
25572
- Typography23,
25993
+ Typography22,
25573
25994
  {
25574
25995
  variant: "body2",
25575
25996
  sx: (t) => ({ color: t.bandit.tokens.brand, textDecoration: "underline", textUnderlineOffset: 3 }),
@@ -25577,7 +25998,7 @@ function PreviewPane({
25577
25998
  }
25578
25999
  )
25579
26000
  ] }),
25580
- adjusted && /* @__PURE__ */ jsxs27(Typography23, { variant: "caption", color: "text.secondary", sx: { display: "block", mt: 1.5 }, children: [
26001
+ adjusted && /* @__PURE__ */ jsxs27(Typography22, { variant: "caption", color: "text.secondary", sx: { display: "block", mt: 1.5 }, children: [
25581
26002
  "Adjusted in ",
25582
26003
  mode,
25583
26004
  " mode to keep text readable."
@@ -25588,7 +26009,7 @@ function PreviewPane({
25588
26009
  }
25589
26010
 
25590
26011
  // src/components/UnderReview.tsx
25591
- import { Box as Box29, Typography as Typography24, useTheme as useTheme25 } from "@mui/material";
26012
+ import { Box as Box29, Typography as Typography23, useTheme as useTheme25 } from "@mui/material";
25592
26013
  import { useNavigate as useNavigate3 } from "react-router-dom";
25593
26014
  import { jsx as jsx34, jsxs as jsxs28 } from "react/jsx-runtime";
25594
26015
  var UnderReview = () => {
@@ -25641,14 +26062,14 @@ var UnderReview = () => {
25641
26062
  }
25642
26063
  ),
25643
26064
  /* @__PURE__ */ jsx34(
25644
- Typography24,
26065
+ Typography23,
25645
26066
  {
25646
26067
  variant: "h4",
25647
26068
  sx: { mb: 2, fontWeight: 700, color: theme.palette.error.main },
25648
26069
  children: "Under Review"
25649
26070
  }
25650
26071
  ),
25651
- /* @__PURE__ */ jsxs28(Typography24, { variant: "body1", sx: { mb: 2, color: theme.palette.text.secondary }, children: [
26072
+ /* @__PURE__ */ jsxs28(Typography23, { variant: "body1", sx: { mb: 2, color: theme.palette.text.secondary }, children: [
25652
26073
  "Your request to use this deployment is currently being reviewed.",
25653
26074
  /* @__PURE__ */ jsx34("br", {}),
25654
26075
  "For more info, contact your deployment admin or",
@@ -25670,7 +26091,7 @@ var UnderReview_default = UnderReview;
25670
26091
 
25671
26092
  // src/components/feedback/FeedbackPulse.tsx
25672
26093
  import { useEffect as useEffect23, useRef as useRef18, useState as useState27 } from "react";
25673
- import { Box as Box30, IconButton as IconButton20, Paper as Paper6, Slide as Slide3, Typography as Typography25 } from "@mui/material";
26094
+ import { Box as Box30, IconButton as IconButton20, Paper as Paper6, Slide as Slide3, Typography as Typography24 } from "@mui/material";
25674
26095
  import { useTheme as useTheme26, alpha as alpha17 } from "@mui/material/styles";
25675
26096
  import { Fragment as Fragment17, jsx as jsx35, jsxs as jsxs29 } from "react/jsx-runtime";
25676
26097
  var STORAGE_KEY2 = "bandit-feedback-pulse-last";
@@ -25777,7 +26198,7 @@ var FeedbackPulse = ({
25777
26198
  },
25778
26199
  children: [
25779
26200
  /* @__PURE__ */ jsx35(Box30, { component: "span", sx: { display: "inline-flex", lineHeight: 1 }, children: icon }),
25780
- /* @__PURE__ */ jsx35(Typography25, { variant: "caption", sx: { fontWeight: 600 }, children: label })
26201
+ /* @__PURE__ */ jsx35(Typography24, { variant: "caption", sx: { fontWeight: 600 }, children: label })
25781
26202
  ]
25782
26203
  }
25783
26204
  );
@@ -25801,9 +26222,9 @@ var FeedbackPulse = ({
25801
26222
  bgcolor: theme.palette.background.paper,
25802
26223
  boxShadow: `0 12px 40px ${alpha17(theme.palette.common.black, theme.palette.mode === "dark" ? 0.5 : 0.18)}`
25803
26224
  },
25804
- children: thanked ? /* @__PURE__ */ jsx35(Typography25, { sx: { textAlign: "center", py: 1.5, fontWeight: 600 }, children: "Thanks for the feedback!" }) : /* @__PURE__ */ jsxs29(Fragment17, { children: [
26225
+ children: thanked ? /* @__PURE__ */ jsx35(Typography24, { sx: { textAlign: "center", py: 1.5, fontWeight: 600 }, children: "Thanks for the feedback!" }) : /* @__PURE__ */ jsxs29(Fragment17, { children: [
25805
26226
  /* @__PURE__ */ jsxs29(Box30, { sx: { display: "flex", alignItems: "center", mb: 0.5 }, children: [
25806
- /* @__PURE__ */ jsx35(Typography25, { variant: "body2", sx: { fontWeight: 600, flex: 1 }, children: "How's Bandit doing?" }),
26227
+ /* @__PURE__ */ jsx35(Typography24, { variant: "body2", sx: { fontWeight: 600, flex: 1 }, children: "How's Bandit doing?" }),
25807
26228
  /* @__PURE__ */ jsx35(
25808
26229
  IconButton20,
25809
26230
  {
@@ -26329,7 +26750,8 @@ var useVoiceMode = (config) => {
26329
26750
 
26330
26751
  // src/chat/chat.tsx
26331
26752
  import { Fragment as Fragment18, jsx as jsx38, jsxs as jsxs31 } from "react/jsx-runtime";
26332
- var ScheduleConfirmCard = lazy2(() => import("./schedule-confirm-card-7CMATYSP.mjs"));
26753
+ var ImageEditAcknowledgement = React25.lazy(() => import("./image-edit-acknowledgement-RZPQYKYJ.mjs"));
26754
+ var ScheduleConfirmCard = lazy2(() => import("./schedule-confirm-card-D4K76HEA.mjs"));
26333
26755
  var CLASSIC_DRAWER_WIDTH = 340;
26334
26756
  var ChatContent = () => {
26335
26757
  const packageSettings = usePackageSettingsStore((state) => state.settings);
@@ -27868,7 +28290,7 @@ var ChatContent = () => {
27868
28290
  color: theme.palette.text.primary
27869
28291
  }),
27870
28292
  children: /* @__PURE__ */ jsx38(
27871
- Typography26,
28293
+ Typography25,
27872
28294
  {
27873
28295
  sx: {
27874
28296
  fontWeight: 500,
@@ -27895,7 +28317,7 @@ var ChatContent = () => {
27895
28317
  color: theme.palette.text.primary
27896
28318
  }),
27897
28319
  children: /* @__PURE__ */ jsx38(
27898
- Typography26,
28320
+ Typography25,
27899
28321
  {
27900
28322
  sx: {
27901
28323
  fontWeight: 600,
@@ -27931,11 +28353,11 @@ var ChatContent = () => {
27931
28353
  }),
27932
28354
  children: [
27933
28355
  /* @__PURE__ */ jsxs31(Box32, { sx: { flex: 1, minWidth: 0 }, children: [
27934
- /* @__PURE__ */ jsx38(Typography26, { variant: "body2", sx: { fontWeight: 600 }, children: "Add your documents" }),
27935
- /* @__PURE__ */ jsx38(Typography26, { variant: "caption", color: "text.secondary", children: "Upload your policies, handbooks or notes and Bandit answers from them, with sources." })
28356
+ /* @__PURE__ */ jsx38(Typography25, { variant: "body2", sx: { fontWeight: 600 }, children: "Add your documents" }),
28357
+ /* @__PURE__ */ jsx38(Typography25, { variant: "caption", color: "text.secondary", children: "Upload your policies, handbooks or notes and Bandit answers from them, with sources." })
27936
28358
  ] }),
27937
28359
  /* @__PURE__ */ jsx38(
27938
- Button15,
28360
+ Button14,
27939
28361
  {
27940
28362
  size: "small",
27941
28363
  variant: "outlined",
@@ -28029,7 +28451,8 @@ var ChatContent = () => {
28029
28451
  ) }),
28030
28452
  /* @__PURE__ */ jsx38(rate_limit_prompt_default, {}),
28031
28453
  /* @__PURE__ */ jsx38(ask_user_card_default, {}),
28032
- schedulePending && /* @__PURE__ */ jsx38(Suspense2, { fallback: null, children: /* @__PURE__ */ jsx38(ScheduleConfirmCard, {}) }),
28454
+ /* @__PURE__ */ jsx38(React25.Suspense, { fallback: null, children: /* @__PURE__ */ jsx38(ImageEditAcknowledgement, {}) }),
28455
+ schedulePending && /* @__PURE__ */ jsx38(Suspense3, { fallback: null, children: /* @__PURE__ */ jsx38(ScheduleConfirmCard, {}) }),
28033
28456
  /* @__PURE__ */ jsx38(
28034
28457
  chat_input_default,
28035
28458
  {
@@ -28178,11 +28601,10 @@ export {
28178
28601
  createBrandingTheme,
28179
28602
  describeBrandingTheme,
28180
28603
  registry_default,
28181
- predefinedThemes,
28182
28604
  themeSkinIds,
28183
28605
  banditDarkTheme,
28184
28606
  TenantPreview,
28185
28607
  useNotificationService,
28186
28608
  chat_default
28187
28609
  };
28188
- //# sourceMappingURL=chunk-XGIQNHUO.mjs.map
28610
+ //# sourceMappingURL=chunk-4NHPLHXU.mjs.map