@burdenoff/fe-libs 2026.904.6 → 2026.904.8

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 +1 -1
@@ -0,0 +1,86 @@
1
+ import { useAssistantTr as e } from "../../i18n.js";
2
+ import "../../attachments.js";
3
+ import { availableAttachmentSources as t, deviceHasCamera as n } from "../../attachmentSources.js";
4
+ import { useCallback as r, useEffect as i, useMemo as a, useRef as o, useState as s } from "react";
5
+ import { Camera as c, FileText as l, FolderOpen as u, ImageIcon as d, Plus as f } from "lucide-react";
6
+ import { jsx as p, jsxs as m } from "react/jsx-runtime";
7
+ //#region src/shared/assistant/ui/composer/AssistantAttachmentButton.tsx
8
+ var h = {
9
+ camera: c,
10
+ gallery: d,
11
+ documents: l,
12
+ files: u
13
+ }, g = ({ onFiles: c, onPickerOpen: l, direction: u = "up", canAddMore: d, disabled: g = !1 }) => {
14
+ let _ = e(), v = o(null), y = o(null), b = o(null), [x, S] = s(!1), C = o(l);
15
+ C.current = l;
16
+ let w = a(() => t(n()), []), T = d ? _("composer.attach", "Attach files or photos") : _("composer.attachFull", "Attachment limit reached ({{max}})", { max: 5 });
17
+ i(() => {
18
+ if (!x) return;
19
+ let e = (e) => {
20
+ e.key === "Escape" && (S(!1), b.current?.focus());
21
+ }, t = (e) => {
22
+ let t = e.target;
23
+ y.current?.contains(t) || b.current?.contains(t) || S(!1);
24
+ };
25
+ return window.addEventListener("keydown", e), window.addEventListener("mousedown", t), () => {
26
+ window.removeEventListener("keydown", e), window.removeEventListener("mousedown", t);
27
+ };
28
+ }, [x]);
29
+ let E = r((e) => {
30
+ c(e.target.files), e.target.value = "";
31
+ }, [c]), D = r((e) => {
32
+ let t = v.current;
33
+ t && (t.accept = e.accept, t.multiple = e.multiple, e.capture ? t.setAttribute("capture", e.capture) : t.removeAttribute("capture"), S(!1), C.current?.(), t.click());
34
+ }, []);
35
+ return /* @__PURE__ */ m("div", {
36
+ className: "relative flex-shrink-0",
37
+ children: [
38
+ /* @__PURE__ */ p("input", {
39
+ ref: v,
40
+ type: "file",
41
+ multiple: !0,
42
+ className: "hidden",
43
+ onChange: E,
44
+ tabIndex: -1,
45
+ "aria-hidden": "true",
46
+ "data-testid": "assistant-attach-input"
47
+ }),
48
+ /* @__PURE__ */ p("button", {
49
+ ref: b,
50
+ type: "button",
51
+ onClick: () => S((e) => !e),
52
+ disabled: g || !d,
53
+ title: T,
54
+ "aria-label": T,
55
+ "aria-haspopup": "menu",
56
+ "aria-expanded": x,
57
+ className: "flex size-9 items-center justify-center rounded-full border border-border-subtle bg-bg-elevated text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-border-strong disabled:cursor-not-allowed disabled:text-text-muted disabled:hover:bg-bg-elevated",
58
+ "data-testid": "assistant-attach",
59
+ children: /* @__PURE__ */ p(f, { className: "size-4" })
60
+ }),
61
+ x ? /* @__PURE__ */ p("div", {
62
+ ref: y,
63
+ role: "menu",
64
+ "aria-label": _("composer.attachSource", "Attach from"),
65
+ className: `absolute ${u === "up" ? "bottom-full mb-2" : "top-full mt-2"} left-0 z-30 min-w-44 overflow-hidden rounded-xl border border-border-default bg-bg-elevated py-1 shadow-lg`,
66
+ "data-testid": "assistant-attach-menu",
67
+ children: w.map((e) => {
68
+ let t = h[e.id];
69
+ return /* @__PURE__ */ m("button", {
70
+ type: "button",
71
+ role: "menuitem",
72
+ onClick: () => D(e),
73
+ className: "flex w-full items-center gap-2.5 px-3 py-2 text-left text-sm text-text-primary transition-colors hover:bg-bg-sunken focus:outline-none focus-visible:bg-bg-sunken",
74
+ "data-testid": `assistant-attach-source-${e.id}`,
75
+ children: [/* @__PURE__ */ p(t, {
76
+ className: "size-4 flex-shrink-0 text-text-secondary",
77
+ "aria-hidden": "true"
78
+ }), _(e.labelKey, e.labelFallback)]
79
+ }, e.id);
80
+ })
81
+ }) : null
82
+ ]
83
+ });
84
+ };
85
+ //#endregion
86
+ export { g as AssistantAttachmentButton };
@@ -0,0 +1,29 @@
1
+ import { FC } from 'react';
2
+ import { AssistantAttachment } from '../../attachments';
3
+ interface Props {
4
+ attachments: AssistantAttachment[];
5
+ onRemove: (id: string) => void;
6
+ /** Re-upload a failed attachment from the bytes already prepared. */
7
+ onRetry: (id: string) => void;
8
+ disabled?: boolean;
9
+ /**
10
+ * Files accepted but not yet readable — an image being downscaled, a PDF being
11
+ * extracted. They have no id yet, so they get placeholder chips.
12
+ *
13
+ * ★ Without these the work is invisible: a 50-page PDF or a 12MP photo left an
14
+ * empty strip and a dead send button, and the only signal that anything was
15
+ * happening was a control that had stopped working.
16
+ */
17
+ preparingCount?: number;
18
+ }
19
+ /**
20
+ * Pending-attachment strip above the composer input (Workstream 3).
21
+ *
22
+ * Images get a thumbnail, everything else a typed icon chip. Upload state is
23
+ * shown on the chip itself — a spinner while it is in flight, a red outline plus
24
+ * the reason when it fails — so a farmer on a weak connection can see which
25
+ * photo is still going up instead of guessing why send is disabled.
26
+ */
27
+ export declare const AssistantAttachmentChips: FC<Props>;
28
+ export {};
29
+ //# sourceMappingURL=AssistantAttachmentChips.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AssistantAttachmentChips.d.ts","sourceRoot":"","sources":["../../../../../src/shared/assistant/ui/composer/AssistantAttachmentChips.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAUhC,OAAO,EAAe,KAAK,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAE1E,UAAU,KAAK;IACb,WAAW,EAAE,mBAAmB,EAAE,CAAC;IACnC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,qEAAqE;IACrE,OAAO,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,EAAE,EAAE,CAAC,KAAK,CAwH9C,CAAC"}
@@ -0,0 +1,70 @@
1
+ import { useAssistantTr as e } from "../../i18n.js";
2
+ import { formatBytes as t } from "../../attachments.js";
3
+ import { AlertCircle as n, FileText as r, Loader2 as i, Paperclip as a, RotateCcw as o, X as s } from "lucide-react";
4
+ import { jsx as c, jsxs as l } from "react/jsx-runtime";
5
+ //#region src/shared/assistant/ui/composer/AssistantAttachmentChips.tsx
6
+ var u = ({ attachments: u, onRemove: d, onRetry: f, preparingCount: p = 0, disabled: m = !1 }) => {
7
+ let h = e();
8
+ return u.length === 0 && p === 0 ? null : /* @__PURE__ */ l("ul", {
9
+ className: "mb-1.5 flex flex-wrap gap-1.5 px-0.5",
10
+ "aria-label": h("attachments.listLabel", "Attached files"),
11
+ "data-testid": "assistant-attachment-chips",
12
+ children: [u.map((e) => {
13
+ let u = e.status === "pending" || e.status === "uploading", p = e.status === "error";
14
+ return /* @__PURE__ */ l("li", {
15
+ className: `flex max-w-[220px] items-center gap-1.5 rounded-lg border py-1 pl-1 pr-1.5 ${p ? "border-status-error-text/40 bg-status-error-bg/10" : "border-border-subtle bg-bg-surface"}`,
16
+ "data-testid": "assistant-attachment-chip",
17
+ children: [
18
+ /* @__PURE__ */ c("span", {
19
+ className: "flex size-7 flex-shrink-0 items-center justify-center overflow-hidden rounded-md bg-bg-sunken text-text-muted",
20
+ children: e.previewUrl ? /* @__PURE__ */ c("img", {
21
+ src: e.previewUrl,
22
+ alt: "",
23
+ className: "size-full object-cover"
24
+ }) : p ? /* @__PURE__ */ c(n, { className: "size-3.5 text-status-error-text" }) : e.kind === "text" ? /* @__PURE__ */ c(r, { className: "size-3.5" }) : /* @__PURE__ */ c(a, { className: "size-3.5" })
25
+ }),
26
+ /* @__PURE__ */ l("span", {
27
+ className: "min-w-0 flex-1",
28
+ children: [/* @__PURE__ */ c("span", {
29
+ className: "block truncate text-[11px] font-medium leading-tight text-text-primary",
30
+ children: e.filename
31
+ }), /* @__PURE__ */ c("span", {
32
+ className: "block truncate text-[10px] leading-tight text-text-muted",
33
+ children: p ? e.error ?? h("attachments.failed", "Upload failed") : u ? e.progressNote ?? h("attachments.uploading", "Uploading…") : t(e.size)
34
+ })]
35
+ }),
36
+ p && /* @__PURE__ */ c("button", {
37
+ type: "button",
38
+ onClick: () => f(e.id),
39
+ disabled: m,
40
+ className: "flex size-5 flex-shrink-0 items-center justify-center rounded-full text-status-error-text transition-colors hover:bg-bg-sunken focus:outline-none focus-visible:ring-2 focus-visible:ring-border-strong disabled:cursor-not-allowed",
41
+ "aria-label": h("attachments.retry", "Retry uploading {{name}}", { name: e.filename }),
42
+ "data-testid": "assistant-attachment-retry",
43
+ children: /* @__PURE__ */ c(o, { className: "size-3" })
44
+ }),
45
+ u ? /* @__PURE__ */ c(i, {
46
+ className: "size-3.5 flex-shrink-0 animate-spin text-text-muted",
47
+ "aria-label": h("attachments.uploading", "Uploading…")
48
+ }) : /* @__PURE__ */ c("button", {
49
+ type: "button",
50
+ onClick: () => d(e.id),
51
+ disabled: m,
52
+ className: "flex size-5 flex-shrink-0 items-center justify-center rounded-full text-text-muted transition-colors hover:bg-bg-sunken hover:text-text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-border-strong disabled:cursor-not-allowed",
53
+ "aria-label": h("attachments.remove", "Remove {{name}}", { name: e.filename }),
54
+ "data-testid": "assistant-attachment-remove",
55
+ children: /* @__PURE__ */ c(s, { className: "size-3" })
56
+ })
57
+ ]
58
+ }, e.id);
59
+ }), Array.from({ length: p }, (e, t) => /* @__PURE__ */ l("li", {
60
+ className: "flex items-center gap-1.5 rounded-lg border border-border-subtle bg-bg-sunken px-2 py-1 text-xs text-text-muted",
61
+ "data-testid": "assistant-attachment-preparing",
62
+ children: [/* @__PURE__ */ c(i, {
63
+ className: "size-3 animate-spin",
64
+ "aria-hidden": "true"
65
+ }), h("attachments.preparing", "Reading file…")]
66
+ }, `preparing-${t}`))]
67
+ });
68
+ };
69
+ //#endregion
70
+ export { u as AssistantAttachmentChips };
@@ -0,0 +1,39 @@
1
+ import { FC } from 'react';
2
+ import { AssistantLanguage } from '../../voice/languages';
3
+ interface Props {
4
+ /** The language currently in effect, already resolved (never null) — for display. */
5
+ current: AssistantLanguage;
6
+ /**
7
+ * The RAW stored preference: `null` means "follow the interface".
8
+ *
9
+ * The select must be driven by this rather than by `current`, or the two
10
+ * states collapse: while following an English interface, English is already
11
+ * the shown option, so choosing English fires no change event and the
12
+ * preference can never be pinned — and a later interface switch then moves
13
+ * the assistant language with it, which is exactly what the user was trying
14
+ * to prevent.
15
+ */
16
+ selected: string | null;
17
+ /** `null` restores "follow the interface language". */
18
+ onChange: (code: string | null) => void;
19
+ disabled?: boolean;
20
+ }
21
+ /**
22
+ * Pick the language you SPEAK to the assistant (BOFF-7108).
23
+ *
24
+ * A native `<select>` on purpose. On a phone — which is where a farmer dictating
25
+ * in Tamil actually is — this opens the OS picker, which is already localised,
26
+ * already scrolls properly, and already handles 17 languages across five scripts
27
+ * without us reimplementing any of it. A custom dropdown would look tidier in a
28
+ * desktop screenshot and be worse everywhere that matters.
29
+ *
30
+ * Each option is labelled with its ENDONYM (`தமிழ்`, not `Tamil`): a user who
31
+ * reads only Tamil cannot find "Tamil" in a list. The visible trigger shows the
32
+ * language code so it stays narrow in a docked panel, with the full name as the
33
+ * accessible label.
34
+ */
35
+ export declare const AssistantLanguageSelector: FC<Props>;
36
+ /** Not a language code, so it can never collide with one. */
37
+ export declare const FOLLOW_APP_LOCALE = "__app__";
38
+ export {};
39
+ //# sourceMappingURL=AssistantLanguageSelector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AssistantLanguageSelector.d.ts","sourceRoot":"","sources":["../../../../../src/shared/assistant/ui/composer/AssistantLanguageSelector.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAEhC,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,uBAAuB,CAAC;AAG/B,UAAU,KAAK;IACb,qFAAqF;IACrF,OAAO,EAAE,iBAAiB,CAAC;IAC3B;;;;;;;;;OASG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,uDAAuD;IACvD,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACxC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,yBAAyB,EAAE,EAAE,CAAC,KAAK,CAoD/C,CAAC;AAEF,6DAA6D;AAC7D,eAAO,MAAM,iBAAiB,YAAY,CAAC"}
@@ -0,0 +1,42 @@
1
+ import { ASSISTANT_LANGUAGES as e } from "../../voice/languages.js";
2
+ import { useAssistantTr as t } from "../../i18n.js";
3
+ import { Languages as n } from "lucide-react";
4
+ import { jsx as r, jsxs as i } from "react/jsx-runtime";
5
+ //#region src/shared/assistant/ui/composer/AssistantLanguageSelector.tsx
6
+ var a = ({ current: a, selected: s, onChange: c, disabled: l = !1 }) => {
7
+ let u = t(), d = u("voice.languageLabel", "Language you speak to the assistant");
8
+ return /* @__PURE__ */ i("label", {
9
+ className: `relative flex items-center gap-1 rounded-md px-1.5 py-1 text-[11px] transition-colors focus-within:ring-2 focus-within:ring-border-strong ${l ? "cursor-not-allowed opacity-50" : "cursor-pointer text-text-muted hover:bg-bg-elevated hover:text-text-secondary"}`,
10
+ title: `${d}: ${a.label}`,
11
+ "data-testid": "assistant-language-selector",
12
+ children: [
13
+ /* @__PURE__ */ r(n, {
14
+ className: "size-3.5",
15
+ "aria-hidden": "true"
16
+ }),
17
+ /* @__PURE__ */ r("span", {
18
+ "aria-hidden": "true",
19
+ children: a.code.toUpperCase()
20
+ }),
21
+ /* @__PURE__ */ i("select", {
22
+ "aria-label": d,
23
+ value: s ?? "__app__",
24
+ disabled: l,
25
+ onChange: (e) => {
26
+ let t = e.target.value;
27
+ c(t === "__app__" ? null : t);
28
+ },
29
+ className: "absolute inset-0 cursor-pointer opacity-0 disabled:cursor-not-allowed",
30
+ children: [/* @__PURE__ */ r("option", {
31
+ value: o,
32
+ children: u("voice.languageFollowApp", "Same as interface")
33
+ }), e.map((e) => /* @__PURE__ */ r("option", {
34
+ value: e.code,
35
+ children: e.label
36
+ }, e.code))]
37
+ })
38
+ ]
39
+ });
40
+ }, o = "__app__";
41
+ //#endregion
42
+ export { a as AssistantLanguageSelector, o as FOLLOW_APP_LOCALE };
@@ -0,0 +1,44 @@
1
+ import { FC } from 'react';
2
+ import { LucideIcon } from 'lucide-react';
3
+ import { AssistantModeConfig } from '../../conversation';
4
+ import { AssistantMode } from '../../types';
5
+ interface Props {
6
+ mode: AssistantMode;
7
+ /** Role-filtered list of modes the user may pick (admin-gated modes removed). */
8
+ availableModes: AssistantModeConfig[];
9
+ onModeChange: (mode: AssistantMode) => void;
10
+ disabled?: boolean;
11
+ }
12
+ /**
13
+ * Per-mode presentation metadata. The i18n key + English fallback provide the
14
+ * relabelled display name ('Ask' / 'Take action') while the mode ID stays the
15
+ * transport-level value ('general' / 'api-calls' / 'vibe-plugins').
16
+ */
17
+ export declare const ASSISTANT_MODE_META: Record<AssistantMode, {
18
+ i18nKey: string;
19
+ fallback: string;
20
+ icon: LucideIcon;
21
+ }>;
22
+ /**
23
+ * Where an arrow/Home/End keypress should move within a roving group.
24
+ *
25
+ * Extracted and exported so the wrap-around arithmetic — including the
26
+ * `currentIndex === -1` case, which means "focus is not on any pill yet" — is
27
+ * unit-testable in a repo whose test environment has no DOM. Returns null when
28
+ * the key is not one this group handles.
29
+ */
30
+ export declare function nextModeIndex(key: string, currentIndex: number, lastIndex: number): number | null;
31
+ /**
32
+ * Segmented "pill" mode selector shown BELOW the composer input (per the v2
33
+ * mockups): the active mode is a filled forest-green pill, the others are
34
+ * ghosted outlines. Replaces the old dropdown trigger. HB semantic tokens only.
35
+ *
36
+ * Exposed as a **radiogroup**, not a tablist: these pills pick one option, they
37
+ * do not reveal panels, and a `role="tab"` with no `tabpanel` misdescribes the
38
+ * control to a screen reader. Focus roves — one Tab stop for the group, arrow
39
+ * keys to move between pills — which is what the radio pattern requires and
40
+ * what a keyboard user expects from a segmented control.
41
+ */
42
+ export declare const AssistantModeSelector: FC<Props>;
43
+ export {};
44
+ //# sourceMappingURL=AssistantModeSelector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AssistantModeSelector.d.ts","sourceRoot":"","sources":["../../../../../src/shared/assistant/ui/composer/AssistantModeSelector.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,EAAE,EAAsB,MAAM,OAAO,CAAC;AACzE,OAAO,EAA8B,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAC3E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAI9D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,UAAU,KAAK;IACb,IAAI,EAAE,aAAa,CAAC;IACpB,iFAAiF;IACjF,cAAc,EAAE,mBAAmB,EAAE,CAAC;IACtC,YAAY,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;IAC5C,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAAM,CACtC,aAAa,EACb;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,CAUxD,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,GAChB,MAAM,GAAG,IAAI,CAgBf;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,qBAAqB,EAAE,EAAE,CAAC,KAAK,CAgG3C,CAAC"}
@@ -0,0 +1,81 @@
1
+ import { useAssistantTr as e } from "../../i18n.js";
2
+ import { useCallback as t, useRef as n } from "react";
3
+ import { ShieldCheck as r, Sparkles as i, Zap as a } from "lucide-react";
4
+ import { jsx as o, jsxs as s } from "react/jsx-runtime";
5
+ //#region src/shared/assistant/ui/composer/AssistantModeSelector.tsx
6
+ var c = {
7
+ general: {
8
+ i18nKey: "mode.ask",
9
+ fallback: "Ask",
10
+ icon: i
11
+ },
12
+ assistant: {
13
+ i18nKey: "mode.ask",
14
+ fallback: "Ask",
15
+ icon: i
16
+ },
17
+ "api-calls": {
18
+ i18nKey: "mode.takeAction",
19
+ fallback: "Take action",
20
+ icon: r
21
+ },
22
+ "vibe-plugins": {
23
+ i18nKey: "mode.build",
24
+ fallback: "Build",
25
+ icon: a
26
+ }
27
+ };
28
+ function l(e, t, n) {
29
+ switch (e) {
30
+ case "Home": return 0;
31
+ case "End": return n;
32
+ case "ArrowRight":
33
+ case "ArrowDown": return t >= n ? 0 : t + 1;
34
+ case "ArrowLeft":
35
+ case "ArrowUp": return t <= 0 ? n : t - 1;
36
+ default: return null;
37
+ }
38
+ }
39
+ var u = ({ mode: r, availableModes: i, onModeChange: a, disabled: u = !1 }) => {
40
+ let d = e(), f = n(null), p = t((e) => {
41
+ if (u) return;
42
+ let t = e.target?.closest("[data-mode-id]")?.dataset.modeId, n = t ? i.findIndex((e) => e.id === t) : -1, o = n >= 0 ? n : i.findIndex((e) => e.id === r), s = l(e.key, o, i.length - 1);
43
+ if (s === null) return;
44
+ e.preventDefault();
45
+ let c = i[s];
46
+ c && (f.current?.querySelector(`[data-mode-id="${c.id}"]`)?.focus(), c.id !== r && a(c.id));
47
+ }, [
48
+ i,
49
+ u,
50
+ r,
51
+ a
52
+ ]);
53
+ return i.length < 2 ? null : /* @__PURE__ */ o("div", {
54
+ ref: f,
55
+ role: "radiogroup",
56
+ "aria-label": d("modeSelector.ariaLabel", "Select assistant mode"),
57
+ onKeyDown: p,
58
+ className: "inline-flex items-center gap-1 rounded-full border border-border-subtle bg-bg-elevated p-1",
59
+ "data-testid": "assistant-mode-selector",
60
+ children: i.map((e) => {
61
+ let t = c[e.id], n = t.icon, i = e.id === r;
62
+ return /* @__PURE__ */ s("button", {
63
+ type: "button",
64
+ role: "radio",
65
+ "aria-checked": i,
66
+ tabIndex: i ? 0 : -1,
67
+ disabled: u,
68
+ onClick: () => a(e.id),
69
+ "data-mode-id": e.id,
70
+ className: `flex items-center gap-1.5 rounded-full px-3 py-1.5 text-[13px] font-medium transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-border-strong disabled:opacity-50 ${i ? "bg-action-primary-bg text-action-primary-text shadow-sm" : "text-text-secondary hover:bg-bg-surface hover:text-text-primary"}`,
71
+ "data-testid": `assistant-mode-option-${e.id}`,
72
+ children: [/* @__PURE__ */ o(n, {
73
+ className: "size-3.5",
74
+ "aria-hidden": "true"
75
+ }), /* @__PURE__ */ o("span", { children: d(t.i18nKey, t.fallback) })]
76
+ }, e.id);
77
+ })
78
+ });
79
+ };
80
+ //#endregion
81
+ export { c as ASSISTANT_MODE_META, u as AssistantModeSelector, l as nextModeIndex };
@@ -0,0 +1,20 @@
1
+ import { FC } from 'react';
2
+ interface Props {
3
+ isListening: boolean;
4
+ /** Requested, not yet capturing — the permission prompt / start-up window. */
5
+ isStarting?: boolean;
6
+ onToggle: () => void;
7
+ disabled?: boolean;
8
+ }
9
+ /**
10
+ * Microphone control in the composer — real speech-to-text (Workstream 2),
11
+ * driven by `useVoiceInput`. While listening it becomes a stop button with a
12
+ * live ring, so the control that starts dictation is also the one that ends it
13
+ * (no hunting for a second affordance mid-sentence).
14
+ *
15
+ * The composer renders this only when the browser supports SpeechRecognition;
16
+ * an unsupported engine gets no dead control at all.
17
+ */
18
+ export declare const AssistantVoiceButton: FC<Props>;
19
+ export {};
20
+ //# sourceMappingURL=AssistantVoiceButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AssistantVoiceButton.d.ts","sourceRoot":"","sources":["../../../../../src/shared/assistant/ui/composer/AssistantVoiceButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAIhC,UAAU,KAAK;IACb,WAAW,EAAE,OAAO,CAAC;IACrB,8EAA8E;IAC9E,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,EAAE,EAAE,CAAC,KAAK,CAmD1C,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { useAssistantTr as e } from "../../i18n.js";
2
+ import { Mic as t, Square as n } from "lucide-react";
3
+ import { jsx as r, jsxs as i } from "react/jsx-runtime";
4
+ //#region src/shared/assistant/ui/composer/AssistantVoiceButton.tsx
5
+ var a = ({ isListening: a, isStarting: o = !1, onToggle: s, disabled: c = !1 }) => {
6
+ let l = e(), u = a ? l("composer.voiceStop", "Stop dictation") : o ? l("composer.voiceStarting", "Starting the microphone — tap to cancel") : l("composer.voiceStart", "Dictate a message");
7
+ return /* @__PURE__ */ r("button", {
8
+ type: "button",
9
+ onClick: s,
10
+ disabled: c,
11
+ title: u,
12
+ "aria-label": u,
13
+ "aria-pressed": a || o,
14
+ className: `flex size-9 flex-shrink-0 items-center justify-center rounded-full border transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-border-strong disabled:cursor-not-allowed disabled:opacity-50 ${a ? "border-status-error-text/40 bg-status-error-bg/10 text-status-error-text" : o ? "border-border-strong bg-bg-elevated text-text-secondary" : "border-border-subtle bg-bg-elevated text-text-secondary hover:bg-bg-sunken hover:text-text-primary"}`,
15
+ "data-testid": "assistant-voice",
16
+ "data-state": a ? "listening" : o ? "starting" : "idle",
17
+ children: o && !a ? /* @__PURE__ */ r(t, { className: "size-4 motion-safe:animate-pulse opacity-70" }) : a ? /* @__PURE__ */ i("span", {
18
+ className: "relative flex items-center justify-center",
19
+ children: [/* @__PURE__ */ r("span", { className: "absolute inline-flex size-7 rounded-full bg-status-error-bg/20 motion-safe:animate-ping" }), /* @__PURE__ */ r(n, { className: "relative size-3 fill-current" })]
20
+ }) : /* @__PURE__ */ r(t, { className: "size-4" })
21
+ });
22
+ };
23
+ //#endregion
24
+ export { a as AssistantVoiceButton };
@@ -0,0 +1,25 @@
1
+ import { FC } from 'react';
2
+ /**
3
+ * Pick WHICH voice reads the replies (BOFF-7110).
4
+ *
5
+ * Two voices for the same language can sound very different — Apple's
6
+ * "Enhanced"/"Premium" downloads and Chrome's network "Google …" voices against
7
+ * the compact built-ins — and nothing in the Web Speech API reports quality.
8
+ * We rank by name heuristics to choose a default; this is how the listener
9
+ * overrules that, which is the only reliable answer.
10
+ *
11
+ * Renders nothing when the device offers fewer than two voices for the
12
+ * language: a dropdown with one option is a control that cannot do anything.
13
+ * That is the common case on bare Linux and on locked-down Android webviews,
14
+ * and it is also why "install more voices" copy belongs in the disabled-state
15
+ * message rather than here.
16
+ *
17
+ * A native `<select>` for the same reason as the language chip: on a phone it
18
+ * opens the OS picker, which already scrolls and localises itself.
19
+ */
20
+ export declare const AssistantVoiceSelector: FC<{
21
+ disabled?: boolean;
22
+ }>;
23
+ /** Not a voice name, so it can never collide with one. */
24
+ export declare const AUTO_VOICE = "__auto__";
25
+ //# sourceMappingURL=AssistantVoiceSelector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AssistantVoiceSelector.d.ts","sourceRoot":"","sources":["../../../../../src/shared/assistant/ui/composer/AssistantVoiceSelector.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAKhC;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,sBAAsB,EAAE,EAAE,CAAC;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,CA2D7D,CAAC;AAEF,0DAA0D;AAC1D,eAAO,MAAM,UAAU,aAAa,CAAC"}
@@ -0,0 +1,37 @@
1
+ import { useAssistantTr as e } from "../../i18n.js";
2
+ import { useAssistantSpeech as t } from "../AssistantSpeechProvider.js";
3
+ import { AudioLines as n } from "lucide-react";
4
+ import { jsx as r, jsxs as i } from "react/jsx-runtime";
5
+ //#region src/shared/assistant/ui/composer/AssistantVoiceSelector.tsx
6
+ var a = ({ disabled: a = !1 }) => {
7
+ let s = e(), { voiceChoices: c, activeVoiceName: l, preferredVoiceName: u, setVoice: d } = t();
8
+ if (c.length < 2) return null;
9
+ let f = s("voice.voiceLabel", "Voice that reads replies");
10
+ return /* @__PURE__ */ i("label", {
11
+ className: `relative flex items-center gap-1 rounded-md px-1.5 py-1 text-[11px] transition-colors focus-within:ring-2 focus-within:ring-border-strong ${a ? "cursor-not-allowed opacity-50" : "cursor-pointer text-text-muted hover:bg-bg-elevated hover:text-text-secondary"}`,
12
+ title: `${f}: ${l ?? ""}`,
13
+ "data-testid": "assistant-voice-selector",
14
+ children: [/* @__PURE__ */ r(n, {
15
+ className: "size-3.5",
16
+ "aria-hidden": "true"
17
+ }), /* @__PURE__ */ i("select", {
18
+ "aria-label": f,
19
+ value: u ?? "__auto__",
20
+ disabled: a,
21
+ onChange: (e) => {
22
+ let t = e.target.value;
23
+ d(t === "__auto__" ? null : t);
24
+ },
25
+ className: "absolute inset-0 cursor-pointer opacity-0 disabled:cursor-not-allowed",
26
+ children: [/* @__PURE__ */ r("option", {
27
+ value: o,
28
+ children: l ? `${s("voice.voiceAuto", "Best available")} (${l})` : s("voice.voiceAuto", "Best available")
29
+ }), c.map((e) => /* @__PURE__ */ r("option", {
30
+ value: e.name,
31
+ children: e.name
32
+ }, e.name))]
33
+ })]
34
+ });
35
+ }, o = "__auto__";
36
+ //#endregion
37
+ export { o as AUTO_VOICE, a as AssistantVoiceSelector };
@@ -0,0 +1,33 @@
1
+ import { FC } from 'react';
2
+ import { VoiceSessionSnapshot } from '../../voice/voiceSession';
3
+ /**
4
+ * The session's single animated control (BOFF-7120).
5
+ *
6
+ * It was a decorative round orb with a separate mic button beneath it. Now it is
7
+ * one pill that both SHOWS the state and IS the control, because two elements
8
+ * were saying the same thing: the orb pulsed to mean "listening" while the mic
9
+ * button sat next to it meaning "the microphone". Merging them leaves a single
10
+ * large target, which matters when the user is talking rather than looking.
11
+ *
12
+ * What tapping it does depends on the phase, and the icon always says which:
13
+ * - listening -> mute (Mic)
14
+ * - muted -> unmute (MicOff)
15
+ * - speaking -> interrupt (Square — the stop glyph, not a microphone)
16
+ * - thinking -> nothing (disabled; there is nothing to stop or mute yet)
17
+ *
18
+ * Interruption living here is deliberate: it is the biggest target on screen,
19
+ * and it replaced barge-in when the microphone had to be closed during playback
20
+ * to stop the assistant transcribing its own voice.
21
+ */
22
+ interface Props {
23
+ session: VoiceSessionSnapshot;
24
+ compact?: boolean;
25
+ onPress?: () => void;
26
+ /** Accessible name — the caller knows the translated strings. */
27
+ label: string;
28
+ /** Start-up window: the recogniser has been asked but is not capturing yet. */
29
+ connecting?: boolean;
30
+ }
31
+ export declare const AssistantVoiceOrb: FC<Props>;
32
+ export {};
33
+ //# sourceMappingURL=AssistantVoiceOrb.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AssistantVoiceOrb.d.ts","sourceRoot":"","sources":["../../../../../src/shared/assistant/ui/voice/AssistantVoiceOrb.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAEhC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAGrE;;;;;;;;;;;;;;;;;;GAkBG;AACH,UAAU,KAAK;IACb,OAAO,EAAE,oBAAoB,CAAC;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,iEAAiE;IACjE,KAAK,EAAE,MAAM,CAAC;IACd,+EAA+E;IAC/E,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,eAAO,MAAM,iBAAiB,EAAE,EAAE,CAAC,KAAK,CA8EvC,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { orbMode as e } from "../../voice/voiceSession.js";
2
+ import { Mic as t, MicOff as n, Square as r } from "lucide-react";
3
+ import { jsx as i } from "react/jsx-runtime";
4
+ //#region src/shared/assistant/ui/voice/AssistantVoiceOrb.tsx
5
+ var a = ({ session: a, compact: o = !1, onPress: s, label: c, connecting: l = !1 }) => {
6
+ let u = e(a), { muted: d, phase: f, error: p } = a, m = f === "thinking" || f === "idle" || f === "ended" || p !== null || l, h = d && f !== "speaking" ? "from-bg-elevated to-bg-elevated" : u === "listening" ? "from-action-primary-bg/80 to-action-primary-bg/20" : u === "thinking" ? "from-text-muted/60 to-text-muted/10" : u === "speaking" ? "from-action-primary-bg to-action-primary-bg/40" : "from-bg-elevated to-bg-elevated/40", g = d && f !== "speaking" ? "" : u === "listening" ? "motion-safe:animate-pulse" : u === "thinking" ? "motion-safe:animate-[pulse_1.6s_ease-in-out_infinite]" : u === "speaking" ? "motion-safe:animate-[pulse_0.9s_ease-in-out_infinite]" : "", _ = o ? "h-16 w-40" : "h-24 w-56", v = o ? "size-6" : "size-8", y = f === "speaking" ? r : d ? n : t;
7
+ return /* @__PURE__ */ i("button", {
8
+ type: "button",
9
+ onClick: s,
10
+ disabled: m || !s,
11
+ className: `flex ${_} items-center justify-center rounded-full bg-gradient-to-b ${h} ${g} text-text-primary shadow-lg transition-colors duration-300 focus:outline-none focus-visible:ring-2 focus-visible:ring-border-strong disabled:cursor-default`,
12
+ "aria-label": c,
13
+ "aria-pressed": f === "speaking" ? void 0 : d,
14
+ "data-testid": "assistant-voice-orb",
15
+ "data-state": u,
16
+ "data-muted": d || void 0,
17
+ children: /* @__PURE__ */ i(y, {
18
+ className: v,
19
+ "aria-hidden": "true"
20
+ })
21
+ });
22
+ };
23
+ //#endregion
24
+ export { a as AssistantVoiceOrb };
@@ -0,0 +1,23 @@
1
+ import { FC } from 'react';
2
+ /**
3
+ * Opens the full-screen voice conversation (BOFF-7120).
4
+ *
5
+ * ★ It occupies the SEND button's slot, and only while the composer is empty —
6
+ * which is both what Gemini does and the only thing that fits. The composer row
7
+ * is exactly full at 390px (measured: attach 22-58, textarea 66-228, language
8
+ * chip 236-280, mic 288-324, send 332-368), so adding a sixth control would push
9
+ * send off the edge — the precise bug fixed in #293. Swapping rather than adding
10
+ * costs zero width.
11
+ *
12
+ * There is no ambiguity in the swap: with nothing typed there is nothing to
13
+ * send, so the send button was inert anyway. As soon as the user types, send
14
+ * comes back.
15
+ *
16
+ * Distinct from the existing mic button, which dictates INTO the composer. This
17
+ * starts a hands-free conversation and leaves the composer behind.
18
+ */
19
+ export declare const AssistantVoiceSessionButton: FC<{
20
+ onStart: () => void;
21
+ disabled?: boolean;
22
+ }>;
23
+ //# sourceMappingURL=AssistantVoiceSessionButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AssistantVoiceSessionButton.d.ts","sourceRoot":"","sources":["../../../../../src/shared/assistant/ui/voice/AssistantVoiceSessionButton.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAIhC;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,2BAA2B,EAAE,EAAE,CAAC;IAC3C,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAgBA,CAAC"}
@@ -0,0 +1,19 @@
1
+ import { useAssistantTr as e } from "../../i18n.js";
2
+ import { AudioLines as t } from "lucide-react";
3
+ import { jsx as n } from "react/jsx-runtime";
4
+ //#region src/shared/assistant/ui/voice/AssistantVoiceSessionButton.tsx
5
+ var r = ({ onStart: r, disabled: i = !1 }) => {
6
+ let a = e();
7
+ return /* @__PURE__ */ n("button", {
8
+ type: "button",
9
+ onClick: r,
10
+ disabled: i,
11
+ className: "flex size-9 flex-shrink-0 items-center justify-center rounded-full bg-bg-elevated text-text-secondary shadow-sm transition-all hover:scale-105 hover:bg-bg-raised hover:text-text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-border-strong disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:scale-100",
12
+ "aria-label": a("voiceSession.start", "Start a voice conversation"),
13
+ title: a("voiceSession.start", "Start a voice conversation"),
14
+ "data-testid": "assistant-voice-session-start",
15
+ children: /* @__PURE__ */ n(t, { className: "size-4" })
16
+ });
17
+ };
18
+ //#endregion
19
+ export { r as AssistantVoiceSessionButton };