@burdenoff/fe-libs 2026.904.7 → 2026.904.9

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 (83) hide show
  1. package/dist/node_modules/react-markdown/lib/index.js +1 -1
  2. package/dist/shared/assistant/attachmentSources.d.ts +58 -0
  3. package/dist/shared/assistant/attachmentSources.d.ts.map +1 -0
  4. package/dist/shared/assistant/attachmentSources.js +41 -0
  5. package/dist/shared/assistant/clipboard.d.ts +12 -0
  6. package/dist/shared/assistant/clipboard.d.ts.map +1 -0
  7. package/dist/shared/assistant/clipboard.js +10 -0
  8. package/dist/shared/assistant/context.d.ts +22 -0
  9. package/dist/shared/assistant/context.d.ts.map +1 -0
  10. package/dist/shared/assistant/context.js +36 -0
  11. package/dist/shared/assistant/index.d.ts +26 -0
  12. package/dist/shared/assistant/index.d.ts.map +1 -1
  13. package/dist/shared/assistant/sandboxReadiness.d.ts +45 -0
  14. package/dist/shared/assistant/sandboxReadiness.d.ts.map +1 -0
  15. package/dist/shared/assistant/sandboxReadiness.js +22 -0
  16. package/dist/shared/assistant/ui/AssistantAutoSpeakToggle.d.ts +20 -0
  17. package/dist/shared/assistant/ui/AssistantAutoSpeakToggle.d.ts.map +1 -0
  18. package/dist/shared/assistant/ui/AssistantAutoSpeakToggle.js +25 -0
  19. package/dist/shared/assistant/ui/AssistantMarkdownImage.d.ts +38 -0
  20. package/dist/shared/assistant/ui/AssistantMarkdownImage.d.ts.map +1 -0
  21. package/dist/shared/assistant/ui/AssistantMarkdownImage.js +147 -0
  22. package/dist/shared/assistant/ui/AssistantMarkdownRenderer.d.ts +10 -0
  23. package/dist/shared/assistant/ui/AssistantMarkdownRenderer.d.ts.map +1 -0
  24. package/dist/shared/assistant/ui/AssistantMarkdownRenderer.js +198 -0
  25. package/dist/shared/assistant/ui/AssistantMessageAttachments.d.ts +10 -0
  26. package/dist/shared/assistant/ui/AssistantMessageAttachments.d.ts.map +1 -0
  27. package/dist/shared/assistant/ui/AssistantMessageAttachments.js +80 -0
  28. package/dist/shared/assistant/ui/AssistantPendingWriteCard.d.ts +32 -0
  29. package/dist/shared/assistant/ui/AssistantPendingWriteCard.d.ts.map +1 -0
  30. package/dist/shared/assistant/ui/AssistantPendingWriteCard.js +53 -0
  31. package/dist/shared/assistant/ui/AssistantReplyLanguageSelector.d.ts +22 -0
  32. package/dist/shared/assistant/ui/AssistantReplyLanguageSelector.d.ts.map +1 -0
  33. package/dist/shared/assistant/ui/AssistantReplyLanguageSelector.js +58 -0
  34. package/dist/shared/assistant/ui/AssistantSpeakButton.d.ts +33 -0
  35. package/dist/shared/assistant/ui/AssistantSpeakButton.d.ts.map +1 -0
  36. package/dist/shared/assistant/ui/AssistantSpeakButton.js +21 -0
  37. package/dist/shared/assistant/ui/AssistantSpokenTurnsNotice.d.ts +31 -0
  38. package/dist/shared/assistant/ui/AssistantSpokenTurnsNotice.d.ts.map +1 -0
  39. package/dist/shared/assistant/ui/AssistantSpokenTurnsNotice.js +31 -0
  40. package/dist/shared/assistant/ui/composer/AssistantAttachmentButton.d.ts +51 -0
  41. package/dist/shared/assistant/ui/composer/AssistantAttachmentButton.d.ts.map +1 -0
  42. package/dist/shared/assistant/ui/composer/AssistantAttachmentButton.js +86 -0
  43. package/dist/shared/assistant/ui/composer/AssistantAttachmentChips.d.ts +29 -0
  44. package/dist/shared/assistant/ui/composer/AssistantAttachmentChips.d.ts.map +1 -0
  45. package/dist/shared/assistant/ui/composer/AssistantAttachmentChips.js +70 -0
  46. package/dist/shared/assistant/ui/composer/AssistantLanguageSelector.d.ts +39 -0
  47. package/dist/shared/assistant/ui/composer/AssistantLanguageSelector.d.ts.map +1 -0
  48. package/dist/shared/assistant/ui/composer/AssistantLanguageSelector.js +42 -0
  49. package/dist/shared/assistant/ui/composer/AssistantModeSelector.d.ts +44 -0
  50. package/dist/shared/assistant/ui/composer/AssistantModeSelector.d.ts.map +1 -0
  51. package/dist/shared/assistant/ui/composer/AssistantModeSelector.js +81 -0
  52. package/dist/shared/assistant/ui/composer/AssistantVoiceButton.d.ts +20 -0
  53. package/dist/shared/assistant/ui/composer/AssistantVoiceButton.d.ts.map +1 -0
  54. package/dist/shared/assistant/ui/composer/AssistantVoiceButton.js +24 -0
  55. package/dist/shared/assistant/ui/composer/AssistantVoiceSelector.d.ts +25 -0
  56. package/dist/shared/assistant/ui/composer/AssistantVoiceSelector.d.ts.map +1 -0
  57. package/dist/shared/assistant/ui/composer/AssistantVoiceSelector.js +37 -0
  58. package/dist/shared/assistant/ui/voice/AssistantVoiceOrb.d.ts +33 -0
  59. package/dist/shared/assistant/ui/voice/AssistantVoiceOrb.d.ts.map +1 -0
  60. package/dist/shared/assistant/ui/voice/AssistantVoiceOrb.js +24 -0
  61. package/dist/shared/assistant/ui/voice/AssistantVoiceSessionButton.d.ts +23 -0
  62. package/dist/shared/assistant/ui/voice/AssistantVoiceSessionButton.d.ts.map +1 -0
  63. package/dist/shared/assistant/ui/voice/AssistantVoiceSessionButton.js +19 -0
  64. package/dist/shared/assistant/ui/voice/VoiceCaption.d.ts +59 -0
  65. package/dist/shared/assistant/ui/voice/VoiceCaption.d.ts.map +1 -0
  66. package/dist/shared/assistant/ui/voice/VoiceCaption.js +25 -0
  67. package/dist/shared/assistant/ui/voice/VoiceConversationView.d.ts +38 -0
  68. package/dist/shared/assistant/ui/voice/VoiceConversationView.d.ts.map +1 -0
  69. package/dist/shared/assistant/ui/voice/VoiceConversationView.js +45 -0
  70. package/dist/shared/assistant/ui/voice/VoiceMiniController.d.ts +32 -0
  71. package/dist/shared/assistant/ui/voice/VoiceMiniController.d.ts.map +1 -0
  72. package/dist/shared/assistant/ui/voice/VoiceMiniController.js +47 -0
  73. package/dist/shared/assistant/ui/voice/VoiceStage.d.ts +42 -0
  74. package/dist/shared/assistant/ui/voice/VoiceStage.d.ts.map +1 -0
  75. package/dist/shared/assistant/ui/voice/VoiceStage.js +58 -0
  76. package/dist/shared/assistant/ui/voice/VoiceTranscriptDrawer.d.ts +39 -0
  77. package/dist/shared/assistant/ui/voice/VoiceTranscriptDrawer.d.ts.map +1 -0
  78. package/dist/shared/assistant/ui/voice/VoiceTranscriptDrawer.js +61 -0
  79. package/dist/shared/assistant/ui/voice/VoiceWaveform.d.ts +33 -0
  80. package/dist/shared/assistant/ui/voice/VoiceWaveform.d.ts.map +1 -0
  81. package/dist/shared/assistant/ui/voice/VoiceWaveform.js +45 -0
  82. package/dist/shared-assistant.js +27 -1
  83. package/package.json +5 -1
@@ -0,0 +1,198 @@
1
+ import { Markdown as e, defaultUrlTransform as t } from "../../../node_modules/react-markdown/lib/index.js";
2
+ import n from "../../../node_modules/remark-gfm/lib/index.js";
3
+ import { defaultSchema as r } from "../../../node_modules/hast-util-sanitize/lib/schema.js";
4
+ import i from "../../../node_modules/rehype-sanitize/lib/index.js";
5
+ import { useAssistantTr as a } from "../i18n.js";
6
+ import { copyToClipboard as o } from "../clipboard.js";
7
+ import { AssistantMarkdownImage as s } from "./AssistantMarkdownImage.js";
8
+ import { useState as c } from "react";
9
+ import { Check as l, Copy as u } from "lucide-react";
10
+ import { Fragment as d, jsx as f, jsxs as p } from "react/jsx-runtime";
11
+ //#region src/shared/assistant/ui/AssistantMarkdownRenderer.tsx
12
+ var m = {
13
+ ...r,
14
+ protocols: {
15
+ ...r.protocols,
16
+ src: [...r.protocols?.src ?? [], "data"]
17
+ }
18
+ }, h = /^data:image\/(?:png|jpe?g|gif|webp|avif|bmp);base64,[A-Za-z0-9+/=\s]+$/, g = (e, n, r) => n === "src" && r.tagName === "img" && h.test(e) ? e : t(e), _ = [n], v = [[i, m]];
19
+ function y({ language: e, code: t }) {
20
+ let n = a(), [r, i] = c(!1);
21
+ return /* @__PURE__ */ p("div", {
22
+ className: "group relative my-2 overflow-hidden rounded-md border border-border-default bg-bg-sunken",
23
+ children: [/* @__PURE__ */ p("div", {
24
+ className: "flex items-center justify-between border-b border-border-default bg-bg-elevated px-3 py-1.5",
25
+ children: [/* @__PURE__ */ f("span", {
26
+ className: "font-mono text-[10px] uppercase tracking-wider text-text-muted",
27
+ children: e || "code"
28
+ }), /* @__PURE__ */ f("button", {
29
+ type: "button",
30
+ onClick: () => {
31
+ o(t).then((e) => {
32
+ e && (i(!0), setTimeout(() => i(!1), 2e3));
33
+ });
34
+ },
35
+ className: "flex items-center gap-1 rounded px-1.5 py-0.5 text-[10px] text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary",
36
+ "aria-label": n("markdown.copyCode", "Copy code"),
37
+ "data-testid": "assistant-markdown-copy-code",
38
+ children: r ? /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f(l, { className: "size-3 text-status-success-text" }), /* @__PURE__ */ f("span", { children: n("markdown.copied", "Copied") })] }) : /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f(u, { className: "size-3" }), /* @__PURE__ */ f("span", { children: n("markdown.copy", "Copy") })] })
39
+ })]
40
+ }), /* @__PURE__ */ f("pre", {
41
+ className: "overflow-x-auto p-3 text-xs leading-relaxed",
42
+ children: /* @__PURE__ */ f("code", {
43
+ className: "font-mono text-text-primary",
44
+ children: t
45
+ })
46
+ })]
47
+ });
48
+ }
49
+ function b(e) {
50
+ let t = e?.children?.[0];
51
+ if (!t || t.type !== "element" || t.tagName !== "code") return null;
52
+ let n = t.children.map((e) => e.type === "text" ? e.value : "").join(""), r = t.properties?.className, i = (Array.isArray(r) ? r.map(String) : String(r ?? "").split(" ")).find((e) => e.startsWith("language-"));
53
+ return {
54
+ code: n.replace(/\n$/, ""),
55
+ language: i ? i.slice(9) : ""
56
+ };
57
+ }
58
+ function x(e, t) {
59
+ return typeof e == "string" && e.split(" ").includes(t);
60
+ }
61
+ function S({ level: e, children: t }) {
62
+ let n = `h${e}`, r = {
63
+ 2: "text-[1.15em]",
64
+ 3: "text-[1.08em]",
65
+ 4: "text-[1em]",
66
+ 5: "text-[0.95em]",
67
+ 6: "text-[0.9em]"
68
+ }[e];
69
+ return /* @__PURE__ */ f(n, {
70
+ className: `mb-1 mt-3 font-semibold text-text-primary ${r}`,
71
+ children: t
72
+ });
73
+ }
74
+ var C = {
75
+ a: ({ href: e, children: t }) => /* @__PURE__ */ f("a", {
76
+ href: e,
77
+ target: "_blank",
78
+ rel: "noopener noreferrer",
79
+ className: "text-action-primary-bg underline-offset-2 hover:underline",
80
+ "data-testid": "assistant-markdown-link",
81
+ children: t
82
+ }),
83
+ blockquote: ({ children: e }) => /* @__PURE__ */ f("blockquote", {
84
+ className: "my-2 border-l-2 border-action-primary-bg pl-3 italic text-text-secondary",
85
+ children: e
86
+ }),
87
+ code: ({ children: e }) => /* @__PURE__ */ f("code", {
88
+ className: "rounded bg-bg-sunken px-1 py-0.5 font-mono text-[0.85em] text-text-primary",
89
+ children: e
90
+ }),
91
+ del: ({ children: e }) => /* @__PURE__ */ f("del", {
92
+ className: "text-text-muted line-through",
93
+ children: e
94
+ }),
95
+ em: ({ children: e }) => /* @__PURE__ */ f("em", {
96
+ className: "italic",
97
+ children: e
98
+ }),
99
+ h1: ({ children: e }) => /* @__PURE__ */ f(S, {
100
+ level: 2,
101
+ children: e
102
+ }),
103
+ h2: ({ children: e }) => /* @__PURE__ */ f(S, {
104
+ level: 3,
105
+ children: e
106
+ }),
107
+ h3: ({ children: e }) => /* @__PURE__ */ f(S, {
108
+ level: 4,
109
+ children: e
110
+ }),
111
+ h4: ({ children: e }) => /* @__PURE__ */ f(S, {
112
+ level: 5,
113
+ children: e
114
+ }),
115
+ h5: ({ children: e }) => /* @__PURE__ */ f(S, {
116
+ level: 6,
117
+ children: e
118
+ }),
119
+ h6: ({ children: e }) => /* @__PURE__ */ f(S, {
120
+ level: 6,
121
+ children: e
122
+ }),
123
+ hr: () => /* @__PURE__ */ f("hr", { className: "my-3 border-border-default" }),
124
+ img: ({ src: e, alt: t, title: n }) => /* @__PURE__ */ f(s, {
125
+ src: typeof e == "string" ? e : void 0,
126
+ alt: t,
127
+ title: n
128
+ }),
129
+ input: ({ checked: e, type: t }) => t === "checkbox" ? /* @__PURE__ */ f("input", {
130
+ type: "checkbox",
131
+ checked: !!e,
132
+ disabled: !0,
133
+ className: "mr-1.5 size-3 flex-shrink-0 align-[-0.1em] accent-[var(--color-action-primary-bg)]"
134
+ }) : null,
135
+ li: ({ className: e, children: t }) => /* @__PURE__ */ f("li", {
136
+ className: x(e, "task-list-item") ? "list-none text-text-primary" : "text-text-primary",
137
+ children: t
138
+ }),
139
+ ol: ({ children: e }) => /* @__PURE__ */ f("ol", {
140
+ className: "my-1.5 ml-5 list-decimal space-y-0.5",
141
+ children: e
142
+ }),
143
+ p: ({ children: e }) => /* @__PURE__ */ f("p", {
144
+ className: "my-1.5 leading-relaxed text-text-primary",
145
+ children: e
146
+ }),
147
+ pre: ({ children: e, node: t }) => {
148
+ let n = b(t);
149
+ return n ? /* @__PURE__ */ f(y, {
150
+ language: n.language,
151
+ code: n.code
152
+ }) : /* @__PURE__ */ f("pre", {
153
+ className: "my-2 overflow-x-auto rounded-md border border-border-default bg-bg-sunken p-3 text-xs leading-relaxed",
154
+ children: e
155
+ });
156
+ },
157
+ strong: ({ children: e }) => /* @__PURE__ */ f("strong", {
158
+ className: "font-semibold text-text-primary",
159
+ children: e
160
+ }),
161
+ table: ({ children: e }) => /* @__PURE__ */ f("div", {
162
+ className: "my-2 overflow-x-auto",
163
+ "data-testid": "assistant-markdown-table",
164
+ children: /* @__PURE__ */ f("table", {
165
+ className: "w-full border-collapse text-[0.9em]",
166
+ children: e
167
+ })
168
+ }),
169
+ td: ({ children: e }) => /* @__PURE__ */ f("td", {
170
+ className: "px-2 py-1.5 align-top text-text-primary",
171
+ children: e
172
+ }),
173
+ th: ({ children: e }) => /* @__PURE__ */ f("th", {
174
+ scope: "col",
175
+ className: "px-2 py-1.5 text-left font-semibold text-text-primary",
176
+ children: e
177
+ }),
178
+ tr: ({ children: e }) => /* @__PURE__ */ f("tr", {
179
+ className: "border-b border-border-subtle",
180
+ children: e
181
+ }),
182
+ ul: ({ className: e, children: t }) => /* @__PURE__ */ f("ul", {
183
+ className: x(e, "contains-task-list") ? "my-1.5 ml-0.5 list-none space-y-0.5" : "my-1.5 ml-5 list-disc space-y-0.5",
184
+ children: t
185
+ })
186
+ }, w = ({ content: t }) => /* @__PURE__ */ f("div", {
187
+ className: "break-words [&>*:first-child]:mt-0 [&>*:last-child]:mb-0",
188
+ "data-testid": "assistant-markdown",
189
+ children: /* @__PURE__ */ f(e, {
190
+ remarkPlugins: _,
191
+ rehypePlugins: v,
192
+ urlTransform: g,
193
+ components: C,
194
+ children: t
195
+ })
196
+ });
197
+ //#endregion
198
+ export { w as AssistantMarkdownRenderer, g as assistantUrlTransform };
@@ -0,0 +1,10 @@
1
+ import { FC } from 'react';
2
+ import { AssistantMessageAttachment } from '../attachments';
3
+ /**
4
+ * Attachments rendered under a user message bubble — a durable record of what
5
+ * the farmer actually sent with the question, thumbnails included.
6
+ */
7
+ export declare const AssistantMessageAttachments: FC<{
8
+ attachments: AssistantMessageAttachment[];
9
+ }>;
10
+ //# sourceMappingURL=AssistantMessageAttachments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AssistantMessageAttachments.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/ui/AssistantMessageAttachments.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,EAAE,EAAE,MAAM,OAAO,CAAC;AAKrD,OAAO,EAAe,KAAK,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AA+G9E;;;GAGG;AACH,eAAO,MAAM,2BAA2B,EAAE,EAAE,CAAC;IAC3C,WAAW,EAAE,0BAA0B,EAAE,CAAC;CAC3C,CAoBA,CAAC"}
@@ -0,0 +1,80 @@
1
+ import { graphqlFetch as e } from "../../graphql/fetch.js";
2
+ import "../../../shared-graphql.js";
3
+ import { useAssistantTr as t } from "../i18n.js";
4
+ import { formatBytes as n } from "../attachments.js";
5
+ import { getWorkspaceId as r } from "../context.js";
6
+ import { useEffect as i, useState as a } from "react";
7
+ import { FileText as o, Paperclip as s } from "lucide-react";
8
+ import { jsx as c, jsxs as l } from "react/jsx-runtime";
9
+ //#region src/shared/assistant/ui/AssistantMessageAttachments.tsx
10
+ var u = "\n query HealthyBowlAssistantMessageAttachmentUrl($id: ID!) {\n getFileDownloadUrl(id: $id)\n }\n";
11
+ function d(t) {
12
+ let [n, o] = a(null);
13
+ return i(() => {
14
+ let n = !0, i = r();
15
+ if (!(!t || !i)) return e({
16
+ gateway: "workspace",
17
+ query: u,
18
+ variables: { id: t },
19
+ workspaceToken: !0,
20
+ workspaceId: i,
21
+ suppressGlobalErrorEvent: !0
22
+ }).then((e) => {
23
+ n && o(e.data?.getFileDownloadUrl ?? null);
24
+ }).catch(() => void 0), () => {
25
+ n = !1;
26
+ };
27
+ }, [t]), n;
28
+ }
29
+ var f = ({ attachment: e }) => {
30
+ let r = t(), i = d(e.fileId), a = e.kind === "image", u = a ? /* @__PURE__ */ c("span", {
31
+ className: "block size-full overflow-hidden rounded-lg bg-bg-sunken",
32
+ children: i ? /* @__PURE__ */ c("img", {
33
+ src: i,
34
+ alt: e.filename,
35
+ className: "size-full object-cover",
36
+ loading: "lazy",
37
+ referrerPolicy: "no-referrer",
38
+ "data-testid": "assistant-message-attachment-image"
39
+ }) : /* @__PURE__ */ c("span", {
40
+ className: "flex size-full items-center justify-center text-text-muted",
41
+ children: /* @__PURE__ */ c(s, { className: "size-4" })
42
+ })
43
+ }) : /* @__PURE__ */ l("span", {
44
+ className: "flex items-center gap-1.5 rounded-lg border border-border-subtle bg-bg-surface px-2 py-1.5",
45
+ children: [/* @__PURE__ */ c("span", {
46
+ className: "flex size-6 flex-shrink-0 items-center justify-center rounded-md bg-bg-sunken text-text-muted",
47
+ children: e.kind === "text" ? /* @__PURE__ */ c(o, { className: "size-3.5" }) : /* @__PURE__ */ c(s, { className: "size-3.5" })
48
+ }), /* @__PURE__ */ l("span", {
49
+ className: "min-w-0",
50
+ children: [/* @__PURE__ */ c("span", {
51
+ className: "block truncate text-[11px] font-medium leading-tight text-text-primary",
52
+ children: e.filename
53
+ }), /* @__PURE__ */ c("span", {
54
+ className: "block text-[10px] leading-tight text-text-muted",
55
+ children: n(e.size)
56
+ })]
57
+ })]
58
+ }), f = a ? "block size-16 overflow-hidden rounded-lg" : "block max-w-[200px]";
59
+ return i ? /* @__PURE__ */ c("a", {
60
+ href: i,
61
+ target: "_blank",
62
+ rel: "noopener noreferrer",
63
+ className: `${f} focus:outline-none focus-visible:ring-2 focus-visible:ring-border-strong`,
64
+ title: r("attachments.open", "Open {{name}}", { name: e.filename }),
65
+ children: u
66
+ }) : /* @__PURE__ */ c("span", {
67
+ className: f,
68
+ children: u
69
+ });
70
+ }, p = ({ attachments: e }) => {
71
+ let n = t();
72
+ return e.length === 0 ? null : /* @__PURE__ */ c("ul", {
73
+ className: "mt-1.5 flex flex-wrap justify-end gap-1.5",
74
+ "aria-label": n("attachments.messageLabel", "Files sent with this message"),
75
+ "data-testid": "assistant-message-attachments",
76
+ children: e.map((e) => /* @__PURE__ */ c("li", { children: /* @__PURE__ */ c(f, { attachment: e }) }, e.id))
77
+ });
78
+ };
79
+ //#endregion
80
+ export { p as AssistantMessageAttachments };
@@ -0,0 +1,32 @@
1
+ import { FC } from 'react';
2
+ import { PendingWrite } from '../pendingWrite';
3
+ interface Props {
4
+ pendingWrite: PendingWrite;
5
+ }
6
+ /**
7
+ * What the assistant is asking permission to change — taken from the request it
8
+ * tried to send, not from its description of it.
9
+ *
10
+ * ★ WHY THIS IS NOT DECORATION. In "Take action" mode the sandbox refuses every
11
+ * mutation on its first attempt and tells the model to describe the change and
12
+ * ask. So the thing the user reads, and approves, is a paragraph written by the
13
+ * same model that wants the write to go through. Nothing reconciles that
14
+ * paragraph against the payload: a field can be omitted, a number rounded, a
15
+ * delete described as an update, and the approval still counts. This card is the
16
+ * payload.
17
+ *
18
+ * ★ NO APPROVE BUTTON, DELIBERATELY. A button here would have to send "yes" as a
19
+ * chat message and let the model decide what that authorises — which is the
20
+ * arrangement this card exists to expose, dressed up as a control. The model
21
+ * holds the single-use token; until it does not (see `write-confirmation.ts`,
22
+ * which names the fix as "the UI holding the approval"), a button would promise
23
+ * an authorization boundary the frontend does not have. The user replies in
24
+ * their own words, and the card makes sure they know what they are replying to.
25
+ *
26
+ * ★ The confirmation token is never rendered — `parseConfirmationRefusal` does
27
+ * not even extract it. Echoing it back is what lets the write through, so on
28
+ * screen it would be a bypass rather than information.
29
+ */
30
+ export declare const AssistantPendingWriteCard: FC<Props>;
31
+ export {};
32
+ //# sourceMappingURL=AssistantPendingWriteCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AssistantPendingWriteCard.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/ui/AssistantPendingWriteCard.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAEhC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGpD,UAAU,KAAK;IACb,YAAY,EAAE,YAAY,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,yBAAyB,EAAE,EAAE,CAAC,KAAK,CAgE/C,CAAC"}
@@ -0,0 +1,53 @@
1
+ import { useAssistantTr as e } from "../i18n.js";
2
+ import { ShieldAlert as t } from "lucide-react";
3
+ import { jsx as n, jsxs as r } from "react/jsx-runtime";
4
+ //#region src/shared/assistant/ui/AssistantPendingWriteCard.tsx
5
+ var i = ({ pendingWrite: i }) => {
6
+ let a = e();
7
+ return /* @__PURE__ */ r("div", {
8
+ className: "my-2 overflow-hidden rounded-xl border border-status-warning bg-status-warning/10",
9
+ "data-testid": "assistant-pending-write",
10
+ role: "note",
11
+ "aria-label": a("pendingWrite.aria", "Change awaiting your approval"),
12
+ children: [/* @__PURE__ */ r("div", {
13
+ className: "flex items-start gap-2 border-b border-status-warning/40 px-3 py-2",
14
+ children: [/* @__PURE__ */ n(t, {
15
+ className: "mt-0.5 size-4 flex-shrink-0 text-status-warning",
16
+ "aria-hidden": "true"
17
+ }), /* @__PURE__ */ r("div", {
18
+ className: "min-w-0",
19
+ children: [/* @__PURE__ */ n("p", {
20
+ className: "text-xs font-semibold text-text-primary",
21
+ children: a("pendingWrite.title", "Nothing has changed yet")
22
+ }), /* @__PURE__ */ n("p", {
23
+ className: "text-[11px] leading-relaxed text-text-secondary",
24
+ children: a("pendingWrite.subtitle", "The assistant asked to make this change and was stopped. Reply to approve or decline.")
25
+ })]
26
+ })]
27
+ }), /* @__PURE__ */ r("div", {
28
+ className: "px-3 py-2",
29
+ children: [/* @__PURE__ */ n("p", {
30
+ className: "font-mono text-xs text-text-primary",
31
+ "data-testid": "assistant-pending-write-operation",
32
+ children: i.operation
33
+ }), i.values && i.values.length > 0 ? /* @__PURE__ */ n("dl", {
34
+ className: "mt-2 grid grid-cols-[minmax(0,auto)_minmax(0,1fr)] gap-x-3 gap-y-1 text-xs",
35
+ children: i.values.map((e) => /* @__PURE__ */ r("div", {
36
+ className: "contents",
37
+ children: [/* @__PURE__ */ n("dt", {
38
+ className: "truncate text-text-muted",
39
+ children: e.key
40
+ }), /* @__PURE__ */ n("dd", {
41
+ className: "min-w-0 break-words font-medium text-text-primary",
42
+ children: e.value
43
+ })]
44
+ }, e.key))
45
+ }) : /* @__PURE__ */ n("p", {
46
+ className: "mt-1 text-[11px] text-text-muted",
47
+ children: a("pendingWrite.noValues", "No field values were included with this request.")
48
+ })]
49
+ })]
50
+ });
51
+ };
52
+ //#endregion
53
+ export { i as AssistantPendingWriteCard };
@@ -0,0 +1,22 @@
1
+ import { FC } from 'react';
2
+ /**
3
+ * What language the assistant ANSWERS in.
4
+ *
5
+ * Separate from the composer's language chip, which says what the user SPEAKS.
6
+ * One control used to drive both, plus the voice — which quietly assumes
7
+ * everyone answers in the language they speak. A farmer may dictate in Tamil and
8
+ * want the answer in English to forward to a buyer.
9
+ *
10
+ * ★ The default is "Match my language", and it names the language it currently
11
+ * resolves to. A bare "Match my language" leaves the user to work out what that
12
+ * means today; naming the winner makes the resolved answer visible without
13
+ * making them pin it.
14
+ *
15
+ * ★ Lives in the More menu rather than the composer: the composer row is already
16
+ * exactly full at 390px, and this is a setting rather than a per-turn control.
17
+ *
18
+ * A native `<select>` for the same reason as the language chip — on a phone it
19
+ * opens the OS picker, which already scrolls and localises itself.
20
+ */
21
+ export declare const AssistantReplyLanguageSelector: FC;
22
+ //# sourceMappingURL=AssistantReplyLanguageSelector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AssistantReplyLanguageSelector.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/ui/AssistantReplyLanguageSelector.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,EAAE,EAAE,MAAM,OAAO,CAAC;AAWzC;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,8BAA8B,EAAE,EAoD5C,CAAC"}
@@ -0,0 +1,58 @@
1
+ import { useI18n as e } from "../../providers/shell/I18nProvider.js";
2
+ import { ASSISTANT_LANGUAGES as t } from "../voice/languages.js";
3
+ import { resolveLanguagePair as n } from "../voice/languagePair.js";
4
+ import { useAssistantTr as r } from "../i18n.js";
5
+ import { useAssistantStore as i } from "../store.js";
6
+ import { useMemo as a } from "react";
7
+ import { Languages as o } from "lucide-react";
8
+ import { jsx as s, jsxs as c } from "react/jsx-runtime";
9
+ //#region src/shared/assistant/ui/AssistantReplyLanguageSelector.tsx
10
+ var l = "__match__", u = () => {
11
+ let u = r(), { locale: d } = e(), f = i((e) => e.assistantLanguage), p = i((e) => e.replyLanguage), m = i((e) => e.setReplyLanguage), h = a(() => n(f, p, d), [
12
+ f,
13
+ p,
14
+ d
15
+ ]), g = u("language.replyLabel", "Assistant replies in");
16
+ return /* @__PURE__ */ c("label", {
17
+ className: "relative flex cursor-pointer items-center gap-2.5 px-3 py-2 text-xs text-text-primary transition-colors hover:bg-bg-elevated focus-within:ring-2 focus-within:ring-border-strong",
18
+ title: `${g}: ${h.reply.english}`,
19
+ "data-testid": "assistant-reply-language",
20
+ children: [
21
+ /* @__PURE__ */ s(o, {
22
+ className: "size-3.5 flex-shrink-0 text-text-secondary",
23
+ "aria-hidden": "true"
24
+ }),
25
+ /* @__PURE__ */ s("span", {
26
+ className: "flex-1",
27
+ children: g
28
+ }),
29
+ /* @__PURE__ */ s("span", {
30
+ className: "text-text-muted",
31
+ children: h.reply.label
32
+ }),
33
+ /* @__PURE__ */ c("select", {
34
+ "aria-label": g,
35
+ value: p ?? l,
36
+ onChange: (e) => {
37
+ let t = e.target.value;
38
+ m(t === l ? null : t);
39
+ },
40
+ className: "absolute inset-0 cursor-pointer opacity-0",
41
+ children: [/* @__PURE__ */ c("option", {
42
+ value: l,
43
+ children: [
44
+ u("language.replyMatch", "Match my language"),
45
+ " (",
46
+ h.input.label,
47
+ ")"
48
+ ]
49
+ }), t.map((e) => /* @__PURE__ */ s("option", {
50
+ value: e.code,
51
+ children: e.label
52
+ }, e.code))]
53
+ })
54
+ ]
55
+ });
56
+ };
57
+ //#endregion
58
+ export { u as AssistantReplyLanguageSelector };
@@ -0,0 +1,33 @@
1
+ import { FC } from 'react';
2
+ interface Props {
3
+ isSpeaking: boolean;
4
+ /** No installed voice for the app language — stays visible, but inert. */
5
+ disabled?: boolean;
6
+ /** Why it is disabled; surfaced as the tooltip so it is never a dead control. */
7
+ disabledReason?: string | null;
8
+ /**
9
+ * Id of the element carrying that reason as VISIBLE text.
10
+ *
11
+ * `title` alone is not an explanation: on a disabled button it cannot be
12
+ * reached by touch and cannot take keyboard focus, so assistive tech and
13
+ * phone users — the ones this feature is for — never see it.
14
+ */
15
+ describedById?: string | undefined;
16
+ onClick: () => void;
17
+ }
18
+ /**
19
+ * "Read aloud" control on an assistant message (BOFF-6290).
20
+ *
21
+ * A real `<button>`, sitting in the same action row as Copy and Retry, whose
22
+ * accessible name is the ACTION it will perform next — "Read aloud" when idle,
23
+ * "Stop reading" while speaking — with `aria-pressed` carrying the state. It
24
+ * never moves focus and never renders a live region: a screen-reader user is
25
+ * already having the message read to them by their own software, and a second
26
+ * announcement would talk over it.
27
+ *
28
+ * The parent hides this entirely when the platform has no speech output, so an
29
+ * unsupported browser gets no dead affordance (same rule as the composer mic).
30
+ */
31
+ export declare const AssistantSpeakButton: FC<Props>;
32
+ export {};
33
+ //# sourceMappingURL=AssistantSpeakButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AssistantSpeakButton.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/ui/AssistantSpeakButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAIhC,UAAU,KAAK;IACb,UAAU,EAAE,OAAO,CAAC;IACpB,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iFAAiF;IACjF,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,oBAAoB,EAAE,EAAE,CAAC,KAAK,CA2C1C,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { useAssistantTr as e } from "../i18n.js";
2
+ import { Square as t, Volume2 as n } from "lucide-react";
3
+ import { jsx as r, jsxs as i } from "react/jsx-runtime";
4
+ //#region src/shared/assistant/ui/AssistantSpeakButton.tsx
5
+ var a = ({ isSpeaking: a, disabled: o = !1, disabledReason: s, describedById: c, onClick: l }) => {
6
+ let u = e(), d = a ? u("message.stopSpeakingShort", "Stop") : u("message.speakShort", "Listen"), f = a ? u("message.stopSpeaking", "Stop reading aloud") : u("message.speak", "Read this reply aloud");
7
+ return /* @__PURE__ */ i("button", {
8
+ type: "button",
9
+ onClick: l,
10
+ disabled: o,
11
+ title: o ? s ?? f : f,
12
+ "aria-label": f,
13
+ "aria-pressed": a,
14
+ "aria-describedby": c,
15
+ className: `flex items-center gap-1 rounded-md px-1.5 py-1 text-[11px] transition-colors hover:bg-bg-elevated disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:bg-transparent ${a ? "text-action-primary-bg" : "text-text-muted hover:text-text-secondary"}`,
16
+ "data-testid": "assistant-message-speak",
17
+ children: [a ? /* @__PURE__ */ r(t, { className: "size-3 fill-current" }) : /* @__PURE__ */ r(n, { className: "size-3" }), /* @__PURE__ */ r("span", { children: d })]
18
+ });
19
+ };
20
+ //#endregion
21
+ export { a as AssistantSpeakButton };
@@ -0,0 +1,31 @@
1
+ import { FC } from 'react';
2
+ interface Props {
3
+ /** How many spoken turns this conversation holds. Never rendered at 0. */
4
+ count: number;
5
+ onOpenTranscript: () => void;
6
+ }
7
+ /**
8
+ * "You said things here that this thread is not showing you."
9
+ *
10
+ * ★ The assistant and the typed thread share ONE backend session, and the
11
+ * client sends no history array — the model's memory IS that session. So a
12
+ * spoken turn is in the model's context permanently, whether or not the chat
13
+ * panel draws it. Hiding those turns without saying so produced the worst
14
+ * possible combination: replies that open "as I mentioned about your urea dose"
15
+ * with no such message anywhere on screen, and an Export button on a chat that
16
+ * looks empty.
17
+ *
18
+ * The fix is not to hide harder. It is to stop the thread implying that what it
19
+ * shows is everything it knows.
20
+ *
21
+ * ★ Deliberately NOT a message bubble. Putting spoken content back in the
22
+ * timeline is the exact leak the split was built to stop; this is a pointer to
23
+ * the other surface, and it carries a count rather than any spoken words.
24
+ *
25
+ * Renders above both the message list and the empty state, because the empty
26
+ * state is where it matters most — a voice-only conversation otherwise reads as
27
+ * "nothing here" while holding a full exchange.
28
+ */
29
+ export declare const AssistantSpokenTurnsNotice: FC<Props>;
30
+ export {};
31
+ //# sourceMappingURL=AssistantSpokenTurnsNotice.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AssistantSpokenTurnsNotice.d.ts","sourceRoot":"","sources":["../../../../src/shared/assistant/ui/AssistantSpokenTurnsNotice.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAIhC,UAAU,KAAK;IACb,0EAA0E;IAC1E,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,IAAI,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,0BAA0B,EAAE,EAAE,CAAC,KAAK,CA0ChD,CAAC"}
@@ -0,0 +1,31 @@
1
+ import { useAssistantTr as e } from "../i18n.js";
2
+ import { AudioLines as t } from "lucide-react";
3
+ import { jsx as n, jsxs as r } from "react/jsx-runtime";
4
+ //#region src/shared/assistant/ui/AssistantSpokenTurnsNotice.tsx
5
+ var i = ({ count: i, onOpenTranscript: a }) => {
6
+ let o = e(), s = i === 1 ? o("spokenTurns.one", "1 spoken turn is kept out of this thread") : o("spokenTurns.many", "{{count}} spoken turns are kept out of this thread", { count: i });
7
+ return /* @__PURE__ */ r("div", {
8
+ className: "flex items-center gap-2 border-b border-border-subtle bg-bg-sunken/60 px-3 py-2 text-xs",
9
+ "data-testid": "assistant-spoken-turns-notice",
10
+ children: [
11
+ /* @__PURE__ */ n(t, {
12
+ className: "size-3.5 flex-shrink-0 text-text-secondary",
13
+ "aria-hidden": "true"
14
+ }),
15
+ /* @__PURE__ */ n("span", {
16
+ className: "min-w-0 flex-1 truncate text-text-secondary",
17
+ title: s,
18
+ children: s
19
+ }),
20
+ /* @__PURE__ */ n("button", {
21
+ type: "button",
22
+ onClick: a,
23
+ className: "flex-shrink-0 rounded-md px-2 py-1 font-medium text-action-primary-bg transition-colors hover:bg-bg-elevated focus:outline-none focus-visible:ring-2 focus-visible:ring-border-strong",
24
+ "data-testid": "assistant-open-transcript",
25
+ children: o("spokenTurns.view", "View transcript")
26
+ })
27
+ ]
28
+ });
29
+ };
30
+ //#endregion
31
+ export { i as AssistantSpokenTurnsNotice };
@@ -0,0 +1,51 @@
1
+ import { FC } from 'react';
2
+ interface Props {
3
+ onFiles: (files: FileList | null) => void;
4
+ /**
5
+ * The OS picker is about to take over the screen.
6
+ *
7
+ * ★ Exists for the voice session, which must release the microphone before the
8
+ * camera app claims it. Fired on the `.click()` rather than on selection,
9
+ * because by the time a file arrives the damage is done — and a CANCELLED
10
+ * picker never reports anything at all.
11
+ */
12
+ onPickerOpen?: () => void;
13
+ /**
14
+ * Which way the source menu opens.
15
+ *
16
+ * ★ 'up' is right for the COMPOSER, which sits at the bottom of the panel — a
17
+ * downward menu there is clipped by the panel edge at every size. It is wrong
18
+ * for the voice session, whose attach control is anchored at the TOP: the menu
19
+ * would open above the button, outside the panel, and all four sources would be
20
+ * unreachable. The control would look present and do nothing.
21
+ */
22
+ direction?: "up" | "down";
23
+ /** False once MAX_ATTACHMENTS is reached — the control explains why. */
24
+ canAddMore: boolean;
25
+ disabled?: boolean;
26
+ }
27
+ /**
28
+ * Leading circular "+" attach control in the composer.
29
+ *
30
+ * ★ Opens a SOURCE MENU rather than the OS picker directly. One picker had to
31
+ * stand in for every source, and on a phone it lands in Files — so
32
+ * photographing a diseased leaf, the single most likely thing a farmer wants to
33
+ * show the assistant, meant leaving for the camera app and coming back.
34
+ *
35
+ * ★ One input, reconfigured per source, rather than one input each. `accept` and
36
+ * `capture` are set immediately before `.click()`, so there is exactly one place
37
+ * a change event can arrive from and the existing `onFiles` contract — shared
38
+ * with drag-drop and paste — is untouched.
39
+ *
40
+ * ★ Camera uses the `capture` attribute rather than a bespoke `getUserMedia`
41
+ * viewfinder. That hands capture → preview → retake → use to the OS camera,
42
+ * which does it better, is already localised, and cannot be exercised on a
43
+ * desktop CI box at all; on a device without a camera the attribute is ignored
44
+ * and the picker opens as usual, so it degrades instead of breaking.
45
+ *
46
+ * `accept` is advisory — the picker still allows "All files" on most platforms —
47
+ * so validation lives in `validateFile`, not here.
48
+ */
49
+ export declare const AssistantAttachmentButton: FC<Props>;
50
+ export {};
51
+ //# sourceMappingURL=AssistantAttachmentButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AssistantAttachmentButton.d.ts","sourceRoot":"","sources":["../../../../../src/shared/assistant/ui/composer/AssistantAttachmentButton.tsx"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,EAAE,EACR,MAAM,OAAO,CAAC;AAWf,UAAU,KAAK;IACb,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,KAAK,IAAI,CAAC;IAC1C;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;IAC1B,wEAAwE;IACxE,UAAU,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AASD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,yBAAyB,EAAE,EAAE,CAAC,KAAK,CA4I/C,CAAC"}