@burdenoff/fe-libs 2026.904.21 → 2026.904.22

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.
@@ -0,0 +1,22 @@
1
+ import { DocumentExtraction } from './documentText';
2
+ import { DocumentFormat } from './attachments';
3
+ export interface ExtractedDocument {
4
+ extraction: DocumentExtraction;
5
+ /** Already trimmed to `MAX_INLINE_CHARS_PER_FILE`. Absent unless status is 'ok'. */
6
+ textContent?: string;
7
+ /** True when `textContent` is a prefix of what the document actually holds. */
8
+ textTruncated?: boolean;
9
+ /** Page-per-entry text, for the shared budget's page-aligned re-trim. PDFs only. */
10
+ documentPages?: string[];
11
+ }
12
+ /**
13
+ * Pull text out of a document, never throwing.
14
+ *
15
+ * A failure here must not fail the ATTACHMENT: the file still uploads, the
16
+ * model still gets its URL, and the reason we could not read it becomes a
17
+ * sentence in the prompt. Throwing would instead surface as the composer's
18
+ * generic 'unreadable' rejection, and the user would lose the file over
19
+ * something that is only a partial loss.
20
+ */
21
+ export declare function extractDocumentText(file: File, format: DocumentFormat): Promise<ExtractedDocument>;
22
+ //# sourceMappingURL=documentExtract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"documentExtract.d.ts","sourceRoot":"","sources":["../../../src/shared/assistant/documentExtract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,OAAO,EAUL,KAAK,kBAAkB,EACxB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAA6B,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAK/E,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,kBAAkB,CAAC;IAC/B,oFAAoF;IACpF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+EAA+E;IAC/E,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,oFAAoF;IACpF,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AA0ND;;;;;;;;GAQG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,cAAc,GACrB,OAAO,CAAC,iBAAiB,CAAC,CAM5B"}
@@ -0,0 +1,133 @@
1
+ import { MAX_EXTRACTED_CHARS as e, docxXmlToText as t, fitPdfPages as n, joinSheets as r, parseSharedStrings as i, parseWorkbookSheets as a, pdfTextItemsToPage as o, sheetXmlToRows as s } from "./documentText.js";
2
+ import { MAX_INLINE_CHARS_PER_FILE as c } from "./attachments.js";
3
+ //#region src/shared/assistant/documentExtract.ts
4
+ var l = [
5
+ 208,
6
+ 207,
7
+ 17,
8
+ 224,
9
+ 161,
10
+ 177,
11
+ 26,
12
+ 225
13
+ ];
14
+ function u(e) {
15
+ return l.every((t, n) => e[n] === t);
16
+ }
17
+ function d(e) {
18
+ return (e instanceof Error ? e.message : String(e)).replace(/\s+/g, " ").slice(0, 160);
19
+ }
20
+ async function f() {
21
+ let [e, t] = await Promise.all([import("../../node_modules/pdfjs-dist/build/pdf.js"), import("../../node_modules/pdfjs-dist/build/pdf.worker.min.js")]);
22
+ return globalThis.pdfjsWorker = t, e;
23
+ }
24
+ async function p(e) {
25
+ let t = (await f()).getDocument({
26
+ data: e,
27
+ disableFontFace: !0,
28
+ useSystemFonts: !1
29
+ }), r;
30
+ try {
31
+ r = await t.promise;
32
+ } catch (e) {
33
+ return await t.destroy().catch(() => void 0), (e instanceof Error ? e.name : "") === "PasswordException" ? { extraction: { status: "encrypted" } } : { extraction: {
34
+ status: "corrupt",
35
+ detail: d(e)
36
+ } };
37
+ }
38
+ let i = r.numPages, a = Math.min(i, 100), s = [];
39
+ try {
40
+ let e = 0;
41
+ for (let t = 1; t <= a; t += 1) {
42
+ let n = await r.getPage(t), i = o((await n.getTextContent()).items);
43
+ if (n.cleanup(), s.push(i), e += i.length, e >= 2e5) break;
44
+ }
45
+ } catch (e) {
46
+ if (s.length === 0) return { extraction: {
47
+ status: "corrupt",
48
+ detail: d(e)
49
+ } };
50
+ } finally {
51
+ await t.destroy().catch(() => void 0);
52
+ }
53
+ return n(s, i, a, c);
54
+ }
55
+ async function m(e, t) {
56
+ let { unzipSync: n, strFromU8: r } = await import("../../node_modules/fflate/esm/browser.js"), i = n(e, { filter: (e) => t(e.name) }), a = {};
57
+ for (let [e, t] of Object.entries(i)) a[e] = r(t);
58
+ return a;
59
+ }
60
+ async function h(n) {
61
+ if (u(n)) return { extraction: {
62
+ status: "corrupt",
63
+ detail: "not an Office Open XML file — an OLE container, i.e. password-protected or a legacy .doc"
64
+ } };
65
+ let r;
66
+ try {
67
+ r = await m(n, (e) => e === "word/document.xml");
68
+ } catch (e) {
69
+ return { extraction: {
70
+ status: "corrupt",
71
+ detail: d(e)
72
+ } };
73
+ }
74
+ let i = r["word/document.xml"];
75
+ return i ? _(t(i).slice(0, e)) : { extraction: {
76
+ status: "corrupt",
77
+ detail: "no word/document.xml inside the file"
78
+ } };
79
+ }
80
+ async function g(t) {
81
+ if (u(t)) return { extraction: {
82
+ status: "corrupt",
83
+ detail: "not an Office Open XML file — an OLE container, i.e. password-protected or a legacy .xls"
84
+ } };
85
+ let n;
86
+ try {
87
+ n = await m(t, (e) => e === "xl/workbook.xml" || e === "xl/_rels/workbook.xml.rels" || e === "xl/sharedStrings.xml" || e.startsWith("xl/worksheets/"));
88
+ } catch (e) {
89
+ return { extraction: {
90
+ status: "corrupt",
91
+ detail: d(e)
92
+ } };
93
+ }
94
+ let o = n["xl/workbook.xml"];
95
+ if (!o) return { extraction: {
96
+ status: "corrupt",
97
+ detail: "no xl/workbook.xml inside the file"
98
+ } };
99
+ let c = i(n["xl/sharedStrings.xml"] ?? ""), l = a(o, n["xl/_rels/workbook.xml.rels"] ?? "").map((e) => ({
100
+ name: e.name,
101
+ rows: e.path && n[e.path] ? s(n[e.path], c) : []
102
+ })), f = _(r(l).slice(0, e)), p = l.filter((e) => e.rows.length > 0).map((e) => e.name);
103
+ return p.length > 0 && (f.extraction.sheetNames = p), f;
104
+ }
105
+ function _(e) {
106
+ if (e.trim() === "") return { extraction: { status: "no-text" } };
107
+ let t = e.length > c, n = e.slice(0, c), r = n.lastIndexOf("\n");
108
+ return {
109
+ extraction: {
110
+ status: "ok",
111
+ sourceChars: e.length
112
+ },
113
+ textContent: t && r > 8e3 * .5 ? n.slice(0, r) : n,
114
+ textTruncated: t
115
+ };
116
+ }
117
+ async function v(e, t) {
118
+ let n = await y(e, t);
119
+ return n.extraction.format = t, n;
120
+ }
121
+ async function y(e, t) {
122
+ try {
123
+ let n = new Uint8Array(await e.arrayBuffer());
124
+ return t === "pdf" ? await p(n) : t === "docx" ? await h(n) : await g(n);
125
+ } catch (e) {
126
+ return { extraction: {
127
+ status: "corrupt",
128
+ detail: d(e)
129
+ } };
130
+ }
131
+ }
132
+ //#endregion
133
+ export { v as extractDocumentText };
@@ -113,4 +113,7 @@ export * from './transportErrors';
113
113
  export * from './quota';
114
114
  export * from './aiCredits';
115
115
  export * from './useWorkspaceWriteAccess';
116
+ export * from './documentExtract';
117
+ export * from './useAssistantAttachments';
118
+ export * from './ui/composer/AssistantComposer';
116
119
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/assistant/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAGH,cAAc,gCAAgC,CAAC;AAC/C,cAAc,mCAAmC,CAAC;AAClD,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,OAAO,CAAC;AACtB,cAAc,SAAS,CAAC;AAGxB,cAAc,wBAAwB,CAAC;AAKvC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,+BAA+B,CAAC;AAG9C,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAGlC,cAAc,gBAAgB,CAAC;AAK/B,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,4BAA4B,CAAC;AAG3C,cAAc,QAAQ,CAAC;AAEvB,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AAGvC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAG/B,cAAc,4BAA4B,CAAC;AAC3C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,sBAAsB,CAAC;AAGrC,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,kCAAkC,CAAC;AACjD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,qCAAqC,CAAC;AACpD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iCAAiC,CAAC;AAChD,cAAc,yCAAyC,CAAC;AACxD,cAAc,wCAAwC,CAAC;AACvD,cAAc,yCAAyC,CAAC;AACxD,cAAc,qCAAqC,CAAC;AACpD,cAAc,oCAAoC,CAAC;AACnD,cAAc,sCAAsC,CAAC;AACrD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,wCAAwC,CAAC;AACvD,cAAc,yBAAyB,CAAC;AACxC,cAAc,kCAAkC,CAAC;AACjD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,uBAAuB,CAAC;AACtC,cAAc,kCAAkC,CAAC;AACjD,cAAc,0BAA0B,CAAC;AAGzC,cAAc,WAAW,CAAC;AAE1B,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AAErC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAE1C,cAAc,kCAAkC,CAAC;AAEjD,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAElC,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,2BAA2B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/shared/assistant/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAGH,cAAc,gCAAgC,CAAC;AAC/C,cAAc,mCAAmC,CAAC;AAClD,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,OAAO,CAAC;AACtB,cAAc,SAAS,CAAC;AAGxB,cAAc,wBAAwB,CAAC;AAKvC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,+BAA+B,CAAC;AAG9C,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAGlC,cAAc,gBAAgB,CAAC;AAK/B,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,4BAA4B,CAAC;AAG3C,cAAc,QAAQ,CAAC;AAEvB,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AAGvC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAG/B,cAAc,4BAA4B,CAAC;AAC3C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,sBAAsB,CAAC;AAGrC,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,kCAAkC,CAAC;AACjD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,qCAAqC,CAAC;AACpD,cAAc,2BAA2B,CAAC;AAC1C,cAAc,iCAAiC,CAAC;AAChD,cAAc,yCAAyC,CAAC;AACxD,cAAc,wCAAwC,CAAC;AACvD,cAAc,yCAAyC,CAAC;AACxD,cAAc,qCAAqC,CAAC;AACpD,cAAc,oCAAoC,CAAC;AACnD,cAAc,sCAAsC,CAAC;AACrD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,wCAAwC,CAAC;AACvD,cAAc,yBAAyB,CAAC;AACxC,cAAc,kCAAkC,CAAC;AACjD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,uBAAuB,CAAC;AACtC,cAAc,kCAAkC,CAAC;AACjD,cAAc,0BAA0B,CAAC;AAGzC,cAAc,WAAW,CAAC;AAE1B,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,0BAA0B,CAAC;AACzC,cAAc,sBAAsB,CAAC;AAErC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAE1C,cAAc,kCAAkC,CAAC;AAEjD,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAElC,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,2BAA2B,CAAC;AAE1C,cAAc,mBAAmB,CAAC;AAClC,cAAc,2BAA2B,CAAC;AAE1C,cAAc,iCAAiC,CAAC"}
@@ -0,0 +1,50 @@
1
+ import { FC } from 'react';
2
+ import { AssistantModeConfig } from '../../conversation';
3
+ import { AssistantMode } from '../../types';
4
+ import { AssistantAttachment } from '../../attachments';
5
+ interface Props {
6
+ /**
7
+ * Writes would be refused in this workspace. Advisory only — the mode stays
8
+ * fully usable for planning; this exists so the refusal is known BEFORE the
9
+ * user invests a turn in it, not after.
10
+ */
11
+ writeAccessDenied?: boolean;
12
+ /**
13
+ * Send the message. `attachments` are already uploaded and ready; the chat
14
+ * area folds them into the prompt and onto the user bubble.
15
+ */
16
+ onSend: (message: string, attachments: AssistantAttachment[]) => void;
17
+ /** True while a turn is in-flight (assistant busy) — disables send + shows the spinner. */
18
+ disabled?: boolean;
19
+ placeholder?: string;
20
+ mode: AssistantMode;
21
+ /** Role-filtered list of modes the user may pick. */
22
+ availableModes: AssistantModeConfig[];
23
+ onModeChange: (mode: AssistantMode) => void;
24
+ /**
25
+ * Opens the hands-free voice conversation. Omitted where a voice session
26
+ * cannot work at all (no speech input, or nothing able to speak the reply),
27
+ * which is what hides the control rather than offering a dead button.
28
+ */
29
+ onStartVoiceSession?: () => void;
30
+ }
31
+ /**
32
+ * AssistantComposer — the v2 input surface (per the ai-assistant-v2 mockups).
33
+ *
34
+ * A rounded card with, on the input row: a leading circular "+" attach control,
35
+ * an auto-growing textarea, a microphone, and a prominent filled forest-green
36
+ * circular send button. BELOW the input sit the mode pills ("Ask" active-green /
37
+ * "Take action" outline); ABOVE it, chips for any pending attachments.
38
+ *
39
+ * Multimodal input (Workstreams 2–3) arrives through three converging paths —
40
+ * the attach button, drag-and-drop onto the card, and paste (a screenshot from
41
+ * the clipboard is the single most common way a user shares one) — plus voice
42
+ * dictation, which appends recognised speech to the draft so it can still be
43
+ * edited before sending.
44
+ *
45
+ * Fully controlled (React state for the text) with auto-grow via a layout
46
+ * effect. Enter sends, Shift+Enter inserts a newline.
47
+ */
48
+ export declare const AssistantComposer: FC<Props>;
49
+ export {};
50
+ //# sourceMappingURL=AssistantComposer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AssistantComposer.d.ts","sourceRoot":"","sources":["../../../../../src/shared/assistant/ui/composer/AssistantComposer.tsx"],"names":[],"mappings":"AAAA,OAAO,EAQL,KAAK,EAAE,EAER,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAG9D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAQjD,OAAO,EAGL,KAAK,mBAAmB,EAEzB,MAAM,mBAAmB,CAAC;AAQ3B,UAAU,KAAK;IACb;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B;;;OAGG;IACH,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,mBAAmB,EAAE,KAAK,IAAI,CAAC;IACtE,2FAA2F;IAC3F,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,aAAa,CAAC;IACpB,qDAAqD;IACrD,cAAc,EAAE,mBAAmB,EAAE,CAAC;IACtC,YAAY,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;IAC5C;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;CAClC;AAID;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,iBAAiB,EAAE,EAAE,CAAC,KAAK,CA4ZvC,CAAC"}
@@ -0,0 +1,185 @@
1
+ import { useI18n as e } from "../../../providers/shell/I18nProvider.js";
2
+ import { useVoiceInput as t } from "../../voice/useVoiceInput.js";
3
+ import { languageDisplayName as n } from "../../voice/speechVoices.js";
4
+ import { resolveAssistantLanguage as ee } from "../../voice/languages.js";
5
+ import { useAssistantTr as r } from "../../i18n.js";
6
+ import { getSendableAttachments as i } from "../../attachments.js";
7
+ import { useAssistantStore as a } from "../../store.js";
8
+ import { AssistantAttachmentButton as o } from "./AssistantAttachmentButton.js";
9
+ import { AssistantAttachmentChips as s } from "./AssistantAttachmentChips.js";
10
+ import { AssistantLanguageSelector as c } from "./AssistantLanguageSelector.js";
11
+ import { AssistantModeSelector as te } from "./AssistantModeSelector.js";
12
+ import { AssistantVoiceButton as ne } from "./AssistantVoiceButton.js";
13
+ import { AssistantVoiceSessionButton as re } from "../voice/AssistantVoiceSessionButton.js";
14
+ import { useAssistantAttachments as ie } from "../../useAssistantAttachments.js";
15
+ import { useCallback as l, useLayoutEffect as ae, useMemo as oe, useRef as u, useState as d } from "react";
16
+ import { ArrowUp as f, Loader2 as p } from "lucide-react";
17
+ import { jsx as m, jsxs as h } from "react/jsx-runtime";
18
+ //#region src/shared/assistant/ui/composer/AssistantComposer.tsx
19
+ var g = 200, _ = ({ writeAccessDenied: _ = !1, onSend: v, disabled: y = !1, placeholder: b, mode: se, availableModes: x, onModeChange: ce, onStartVoiceSession: S }) => {
20
+ let C = r(), { locale: w } = e(), T = u(null), [E, D] = d(""), [O, k] = d(!1), A = u(0), { attachments: j, isBusy: M, preparingCount: N, sendBlocker: P, rejection: F, addFiles: I, remove: L, retry: R, clear: z, clearRejection: B, canAddMore: V } = ie(), H = l((e) => {
21
+ D((t) => t ? `${t.replace(/\s+$/, "")} ${e}` : e);
22
+ }, []), U = a((e) => e.assistantLanguage), W = a((e) => e.setAssistantLanguage), G = oe(() => ee(U, w), [U, w]), K = t({
23
+ onFinalize: H,
24
+ language: G.tag
25
+ }), le = b ?? C("composer.placeholder", "Ask HealthyBowl anything…"), q = E.trim(), J = i(j), Y = j.filter((e) => e.status === "error"), X = (q.length > 0 || J.length > 0) && !y && P === null;
26
+ ae(() => {
27
+ let e = T.current;
28
+ e && (e.style.height = "auto", e.style.height = `${Math.min(e.scrollHeight, g)}px`);
29
+ }, [E]);
30
+ let Z = l(() => {
31
+ X && (K.isActive && K.stop(), v(E.trim(), J), D(""), z());
32
+ }, [
33
+ X,
34
+ v,
35
+ E,
36
+ J,
37
+ z,
38
+ K
39
+ ]), ue = l((e) => {
40
+ e.key === "Enter" && !e.shiftKey && (e.preventDefault(), Z());
41
+ }, [Z]), Q = l((e) => {
42
+ let t = Array.from(e.clipboardData?.files ?? []);
43
+ t.length !== 0 && (e.preventDefault(), I(t));
44
+ }, [I]), de = l((e) => {
45
+ Array.from(e.dataTransfer?.types ?? []).includes("Files") && (A.current += 1, k(!0));
46
+ }, []), fe = l(() => {
47
+ A.current = Math.max(0, A.current - 1), A.current === 0 && k(!1);
48
+ }, []), pe = l((e) => {
49
+ Array.from(e.dataTransfer?.types ?? []).includes("Files") && e.preventDefault();
50
+ }, []), me = l((e) => {
51
+ e.preventDefault(), A.current = 0, k(!1), I(e.dataTransfer?.files ?? null);
52
+ }, [I]), he = (e) => {
53
+ switch (e) {
54
+ case "too-many": return C("attachments.tooMany", "You can attach up to {{max}} files per message.", { max: 5 });
55
+ case "too-large": return C("attachments.tooLarge", "That file is larger than 10 MB.");
56
+ case "empty": return C("attachments.empty", "That file is empty.");
57
+ case "unreadable": return C("attachments.unreadable", "That file could not be read.");
58
+ case "unsupported-image": return C("attachments.unsupportedImage", "This device can't read that image format, so it can't be resized to send. Save it as JPEG and try again.");
59
+ }
60
+ }, $ = (() => {
61
+ switch (K.error) {
62
+ case "not-allowed": return C("voice.denied", "Microphone access is blocked. Allow it in your browser settings to dictate.");
63
+ case "audio-capture": return C("voice.noMic", "No microphone was found.");
64
+ case "network": return C("voice.network", "Speech recognition is offline right now.");
65
+ case "language-not-supported": return C("voice.languageNotSupported", "This browser cannot recognise spoken {{language}}. Try a different browser, or type instead.", { language: n(K.language, w) });
66
+ case "unknown": return C("voice.failed", "Dictation stopped unexpectedly.");
67
+ default: return null;
68
+ }
69
+ })();
70
+ return /* @__PURE__ */ h("div", {
71
+ className: "flex-shrink-0 px-3 pb-3 pt-1.5",
72
+ children: [
73
+ /* @__PURE__ */ h("div", {
74
+ onDragEnter: de,
75
+ onDragLeave: fe,
76
+ onDragOver: pe,
77
+ onDrop: me,
78
+ className: `rounded-2xl border p-2 shadow-sm transition-colors focus-within:border-action-primary-bg focus-within:ring-2 focus-within:ring-action-primary-bg/15 ${O ? "border-action-primary-bg border-dashed bg-action-primary-bg/5" : "border-border-default bg-bg-sunken"}`,
79
+ "data-testid": "assistant-composer",
80
+ children: [
81
+ /* @__PURE__ */ m(s, {
82
+ attachments: j,
83
+ onRemove: L,
84
+ onRetry: R,
85
+ disabled: y,
86
+ preparingCount: N
87
+ }),
88
+ /* @__PURE__ */ h("div", {
89
+ className: "flex items-end gap-2",
90
+ children: [
91
+ /* @__PURE__ */ m(o, {
92
+ onFiles: I,
93
+ canAddMore: V,
94
+ disabled: y
95
+ }),
96
+ /* @__PURE__ */ m("textarea", {
97
+ ref: T,
98
+ value: E,
99
+ onChange: (e) => D(e.target.value),
100
+ onKeyDown: ue,
101
+ onPaste: Q,
102
+ disabled: y,
103
+ placeholder: O ? C("composer.dropHere", "Drop files to attach") : le,
104
+ rows: 1,
105
+ className: "min-h-[36px] flex-1 resize-none self-center bg-transparent px-1 py-2 text-[15px] leading-relaxed text-text-primary placeholder:text-text-muted focus:outline-none disabled:opacity-50",
106
+ "aria-label": C("chatInput.ariaLabel", "Message input"),
107
+ "data-testid": "assistant-input"
108
+ }),
109
+ /* @__PURE__ */ m(c, {
110
+ current: G,
111
+ selected: U,
112
+ onChange: W,
113
+ disabled: y || K.isActive
114
+ }),
115
+ K.isSupported && /* @__PURE__ */ m(ne, {
116
+ isListening: K.isListening,
117
+ isStarting: K.isStarting,
118
+ onToggle: K.toggle,
119
+ disabled: y
120
+ }),
121
+ S && q.length === 0 && j.length === 0 && !K.isActive ? /* @__PURE__ */ m(re, {
122
+ onStart: S,
123
+ disabled: y
124
+ }) : /* @__PURE__ */ m("button", {
125
+ type: "button",
126
+ onClick: Z,
127
+ disabled: !X,
128
+ className: `flex size-9 flex-shrink-0 items-center justify-center rounded-full shadow-sm transition-all focus:outline-none focus-visible:ring-2 focus-visible:ring-border-strong ${X ? "bg-action-primary-bg text-action-primary-text hover:scale-105 hover:bg-action-primary-bg-hover" : "bg-bg-elevated text-text-muted"}`,
129
+ "aria-label": C("chatInput.send", "Send message"),
130
+ "data-testid": "assistant-send",
131
+ children: y || M ? /* @__PURE__ */ m(p, { className: "size-4 animate-spin" }) : /* @__PURE__ */ m(f, { className: "size-4" })
132
+ })
133
+ ]
134
+ }),
135
+ x.length > 1 && /* @__PURE__ */ m("div", {
136
+ className: "mt-2 px-0.5",
137
+ children: /* @__PURE__ */ m(te, {
138
+ mode: se,
139
+ availableModes: x,
140
+ onModeChange: ce,
141
+ disabled: y
142
+ })
143
+ })
144
+ ]
145
+ }),
146
+ _ ? /* @__PURE__ */ m("p", {
147
+ className: "mt-1.5 px-1 text-center text-[11px] text-status-warning",
148
+ "data-testid": "assistant-write-access-warning",
149
+ children: C("composer.writeAccessDenied", "Changes may not save — your role in this workspace does not currently allow edits. You can still plan and review an action.")
150
+ }) : null,
151
+ /* @__PURE__ */ m("div", {
152
+ className: "mt-1.5 px-1 text-center text-[11px]",
153
+ "aria-live": "polite",
154
+ children: K.isStarting && !K.isListening ? /* @__PURE__ */ m("p", {
155
+ className: "text-text-muted",
156
+ "data-testid": "assistant-voice-starting",
157
+ children: C("voice.starting", "Starting the microphone…")
158
+ }) : K.isListening ? /* @__PURE__ */ h("p", {
159
+ className: "text-status-error-text",
160
+ "data-testid": "assistant-voice-status",
161
+ children: [/* @__PURE__ */ m("span", { className: "mr-1 inline-block size-1.5 rounded-full bg-status-error-text align-middle motion-safe:animate-pulse" }), K.interim || C("voice.listening", "Listening… speak now, tap the square to stop")]
162
+ }) : $ ? /* @__PURE__ */ m("p", {
163
+ className: "text-status-error-text",
164
+ "data-testid": "assistant-voice-error",
165
+ children: $
166
+ }) : F ? /* @__PURE__ */ m("button", {
167
+ type: "button",
168
+ onClick: B,
169
+ className: "text-status-error-text underline-offset-2 hover:underline",
170
+ "data-testid": "assistant-attachment-rejection",
171
+ children: he(F)
172
+ }) : P === "failed" ? /* @__PURE__ */ m("p", {
173
+ className: "text-status-error-text",
174
+ "data-testid": "assistant-attachment-blocked",
175
+ children: Y.length === 1 ? C("attachments.blockedOne", "{{name}} didn't upload. Retry or remove it before sending.", { name: Y[0].filename }) : C("attachments.blockedMany", "{{count}} attachments didn't upload. Retry or remove them before sending.", { count: Y.length })
176
+ }) : /* @__PURE__ */ m("p", {
177
+ className: "text-text-muted",
178
+ children: C("composer.hint", "Enter to send · Shift+Enter for newline · drag or paste files to attach")
179
+ })
180
+ })
181
+ ]
182
+ });
183
+ };
184
+ //#endregion
185
+ export { _ as AssistantComposer };
@@ -0,0 +1,26 @@
1
+ import { AssistantAttachment, AttachmentRejection, AttachmentSendBlocker } from './attachments';
2
+ export interface UseAssistantAttachmentsResult {
3
+ /** Files being read/downscaled — no chip of their own yet. See below. */
4
+ preparingCount: number;
5
+ attachments: AssistantAttachment[];
6
+ /** True while any attachment is still being prepared or uploaded. */
7
+ isBusy: boolean;
8
+ /**
9
+ * Why the current attachments block sending, or `null` when they do not.
10
+ * Strictly wider than `isBusy`: it also reports the terminal 'failed' state,
11
+ * which is not busy but must still stop a send. See
12
+ * `getAttachmentSendBlocker`.
13
+ */
14
+ sendBlocker: AttachmentSendBlocker | null;
15
+ /** Most recent rejection, for a transient inline message. */
16
+ rejection: AttachmentRejection | null;
17
+ addFiles: (files: FileList | File[] | null | undefined) => void;
18
+ remove: (id: string) => void;
19
+ /** Re-upload a single failed attachment, reusing the bytes already prepared. */
20
+ retry: (id: string) => void;
21
+ clear: () => void;
22
+ clearRejection: () => void;
23
+ canAddMore: boolean;
24
+ }
25
+ export declare function useAssistantAttachments(): UseAssistantAttachmentsResult;
26
+ //# sourceMappingURL=useAssistantAttachments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAssistantAttachments.d.ts","sourceRoot":"","sources":["../../../src/shared/assistant/useAssistantAttachments.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAOH,OAAO,EAYL,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,EAC3B,MAAM,eAAe,CAAC;AAmJvB,MAAM,WAAW,6BAA6B;IAC5C,yEAAyE;IACzE,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,mBAAmB,EAAE,CAAC;IACnC,qEAAqE;IACrE,MAAM,EAAE,OAAO,CAAC;IAChB;;;;;OAKG;IACH,WAAW,EAAE,qBAAqB,GAAG,IAAI,CAAC;IAC1C,6DAA6D;IAC7D,SAAS,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACtC,QAAQ,EAAE,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,EAAE,GAAG,IAAI,GAAG,SAAS,KAAK,IAAI,CAAC;IAChE,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,gFAAgF;IAChF,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,wBAAgB,uBAAuB,IAAI,6BAA6B,CAoRvE"}