@burdenoff/fe-libs 2026.1029.10 → 2026.1030.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/shared/assistant/i18nManifest.d.ts +22 -0
- package/dist/shared/assistant/i18nManifest.js +22 -0
- package/dist/shared/assistant/index.d.ts +6 -0
- package/dist/shared/assistant/ui/AIAssistantPanel.d.ts +26 -0
- package/dist/shared/assistant/ui/AIAssistantPanel.js +197 -160
- package/dist/shared/assistant/ui/AssistantChatArea.d.ts +33 -0
- package/dist/shared/assistant/ui/AssistantChatArea.js +1463 -1397
- package/dist/shared/assistant/ui/AssistantLanguageMenu.d.ts +9 -1
- package/dist/shared/assistant/ui/AssistantLanguageMenu.js +22 -22
- package/dist/shared/assistant/ui/AssistantOverlay.d.ts +91 -3
- package/dist/shared/assistant/ui/AssistantOverlay.js +347 -127
- package/dist/shared/assistant/ui/assistantOverlayState.d.ts +224 -0
- package/dist/shared/assistant/ui/assistantOverlayState.js +116 -29
- package/dist/shared/assistant/ui/compact/AssistantConversationMenu.js +53 -52
- package/dist/shared/assistant/ui/compact/AssistantDiscardDraftDialog.js +3 -4
- package/dist/shared/assistant/ui/compact/AssistantLanguagePill.d.ts +29 -0
- package/dist/shared/assistant/ui/compact/AssistantLanguagePill.js +98 -0
- package/dist/shared/assistant/ui/compact/AssistantVoiceReady.d.ts +31 -0
- package/dist/shared/assistant/ui/compact/AssistantVoiceReady.js +41 -0
- package/dist/shared/assistant/ui/compact/AssistantWaitingChangeRow.d.ts +6 -0
- package/dist/shared/assistant/ui/compact/AssistantWaitingChangeRow.js +5 -5
- package/dist/shared/assistant/ui/compact/AssistantWindowHeader.d.ts +36 -0
- package/dist/shared/assistant/ui/compact/AssistantWindowHeader.js +68 -0
- package/dist/shared/assistant/ui/compact/windowView.d.ts +50 -0
- package/dist/shared/assistant/ui/compact/windowView.js +31 -0
- package/dist/shared/assistant/ui/composer/AssistantAttachmentButton.d.ts +8 -0
- package/dist/shared/assistant/ui/composer/AssistantAttachmentButton.js +34 -34
- package/dist/shared/assistant/ui/composer/AssistantComposer.d.ts +6 -0
- package/dist/shared/assistant/ui/composer/AssistantComposer.js +92 -90
- package/dist/shared/assistant/ui/composer/AssistantModeMenu.d.ts +6 -0
- package/dist/shared/assistant/ui/composer/AssistantModeMenu.js +83 -83
- package/dist/shared/assistant/ui/useAssistantVisualViewport.d.ts +3 -0
- package/dist/shared/assistant/ui/useAssistantVisualViewport.js +24 -0
- package/dist/shared/assistant/ui/voice/AssistantVoiceSession.d.ts +31 -0
- package/dist/shared/assistant/ui/voice/AssistantVoiceSession.js +270 -166
- package/dist/shared/assistant/ui/voice/VoiceStage.d.ts +8 -0
- package/dist/shared/assistant/ui/voice/VoiceStage.js +10 -10
- package/dist/shared/assistant/voice/voiceStartedCue.d.ts +11 -0
- package/dist/shared/assistant/voice/voiceStartedCue.js +7 -0
- package/dist/shared-assistant.js +266 -260
- package/dist/ui/modal-shell.d.ts +6 -1
- package/dist/ui/modal-shell.js +7 -6
- package/package.json +1 -1
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { useI18n as e } from "../../../providers/shell/I18nProvider.js";
|
|
2
|
+
import { EscapeLayer as t } from "../../../components/EscapeLayer.js";
|
|
3
|
+
import { useAssistantTr as n } from "../../i18n.js";
|
|
4
|
+
import { useAssistantStore as r } from "../../store.js";
|
|
5
|
+
import { replyFollowsInput as i, resolveLanguagePair as a } from "../../voice/languagePair.js";
|
|
6
|
+
import { AssistantLanguageMenu as o } from "../AssistantLanguageMenu.js";
|
|
7
|
+
import { useEffect as s, useId as c, useMemo as l, useRef as u, useState as d } from "react";
|
|
8
|
+
import { ChevronDown as f, Languages as p } from "lucide-react";
|
|
9
|
+
import { jsx as m, jsxs as h } from "react/jsx-runtime";
|
|
10
|
+
//#region src/shared/assistant/ui/compact/AssistantLanguagePill.tsx
|
|
11
|
+
var g = ({ voiceLive: g }) => {
|
|
12
|
+
let _ = n(), { locale: v } = e(), y = r((e) => e.assistantLanguage), b = r((e) => e.replyLanguage), x = l(() => a(y, b, v), [
|
|
13
|
+
y,
|
|
14
|
+
b,
|
|
15
|
+
v
|
|
16
|
+
]), [S, C] = d(!1), w = u(null), T = u(null), E = c(), D = c();
|
|
17
|
+
S && g && C(!1);
|
|
18
|
+
let O = u(S);
|
|
19
|
+
s(() => {
|
|
20
|
+
O.current = S;
|
|
21
|
+
});
|
|
22
|
+
let k = u(y);
|
|
23
|
+
s(() => {
|
|
24
|
+
k.current !== y && (k.current = y, O.current && (C(!1), T.current?.focus({ preventScroll: !0 })));
|
|
25
|
+
}, [y]), s(() => {
|
|
26
|
+
if (!S) return;
|
|
27
|
+
let e = (e) => {
|
|
28
|
+
let t = e.target;
|
|
29
|
+
t instanceof Node && w.current?.contains(t) || C(!1);
|
|
30
|
+
};
|
|
31
|
+
return document.addEventListener("pointerdown", e), () => document.removeEventListener("pointerdown", e);
|
|
32
|
+
}, [S]);
|
|
33
|
+
let A = _("window.language", "Language"), j = _("window.languageLocked", "Change the language after you end voice"), M = !i(b) && x.reply.code !== x.input.code ? x.reply.label : null, N = (e) => {
|
|
34
|
+
C(!1), e && T.current?.focus({ preventScroll: !0 });
|
|
35
|
+
};
|
|
36
|
+
return /* @__PURE__ */ h("div", {
|
|
37
|
+
ref: w,
|
|
38
|
+
className: "relative min-w-0 flex-1",
|
|
39
|
+
"data-testid": "assistant-window-language-root",
|
|
40
|
+
children: [
|
|
41
|
+
/* @__PURE__ */ h("button", {
|
|
42
|
+
ref: T,
|
|
43
|
+
type: "button",
|
|
44
|
+
onClick: () => {
|
|
45
|
+
g || C((e) => !e);
|
|
46
|
+
},
|
|
47
|
+
className: `flex h-10 min-w-0 max-w-full items-center gap-1.5 rounded-full border border-border-default px-3 text-sm transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] ${g ? "cursor-default text-text-muted" : "text-text-primary hover:bg-bg-sunken"}`,
|
|
48
|
+
"aria-expanded": S,
|
|
49
|
+
"aria-controls": S ? E : void 0,
|
|
50
|
+
"aria-disabled": g ? "true" : void 0,
|
|
51
|
+
"aria-describedby": g ? D : void 0,
|
|
52
|
+
"aria-label": `${A}: ${x.input.label}`,
|
|
53
|
+
title: g ? j : `${A}: ${x.input.label}`,
|
|
54
|
+
"data-testid": "assistant-window-language",
|
|
55
|
+
children: [
|
|
56
|
+
/* @__PURE__ */ m(p, {
|
|
57
|
+
className: "size-4 shrink-0",
|
|
58
|
+
"aria-hidden": "true"
|
|
59
|
+
}),
|
|
60
|
+
/* @__PURE__ */ m("span", {
|
|
61
|
+
className: "min-w-0 truncate",
|
|
62
|
+
"data-testid": "assistant-window-language-name",
|
|
63
|
+
children: x.input.label
|
|
64
|
+
}),
|
|
65
|
+
/* @__PURE__ */ m(f, {
|
|
66
|
+
className: "size-4 shrink-0",
|
|
67
|
+
"aria-hidden": "true"
|
|
68
|
+
})
|
|
69
|
+
]
|
|
70
|
+
}),
|
|
71
|
+
g ? /* @__PURE__ */ m("span", {
|
|
72
|
+
id: D,
|
|
73
|
+
className: "sr-only",
|
|
74
|
+
"data-testid": "assistant-window-language-reason",
|
|
75
|
+
children: j
|
|
76
|
+
}) : null,
|
|
77
|
+
S ? /* @__PURE__ */ m(t, {
|
|
78
|
+
onDismiss: () => {
|
|
79
|
+
N(!0);
|
|
80
|
+
},
|
|
81
|
+
children: /* @__PURE__ */ h("div", {
|
|
82
|
+
id: E,
|
|
83
|
+
role: "group",
|
|
84
|
+
"aria-label": A,
|
|
85
|
+
className: "absolute start-0 top-full z-30 mt-1 w-72 max-w-[calc(100vw-2rem)] overflow-hidden rounded-card border border-border-default bg-bg-elevated py-1 shadow-[var(--shadow-overlay)]",
|
|
86
|
+
"data-testid": "assistant-window-language-menu",
|
|
87
|
+
children: [/* @__PURE__ */ m(o, { inputOnly: !0 }), M === null ? null : /* @__PURE__ */ m("p", {
|
|
88
|
+
className: "px-3 py-2 text-xs text-text-muted",
|
|
89
|
+
"data-testid": "assistant-window-language-pinned",
|
|
90
|
+
children: _("window.languagePinned", "Replies in {{language}} — change in full assistant", { language: M })
|
|
91
|
+
})]
|
|
92
|
+
})
|
|
93
|
+
}) : null
|
|
94
|
+
]
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
//#endregion
|
|
98
|
+
export { g as AssistantLanguagePill };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
export interface AssistantVoiceReadyProps {
|
|
3
|
+
/**
|
|
4
|
+
* Why a voice conversation cannot start here and now (`voiceStartBlocker`),
|
|
5
|
+
* already worded. Null: it can, and Start voice is offered.
|
|
6
|
+
*/
|
|
7
|
+
blockedReason: string | null;
|
|
8
|
+
/** Start the voice conversation: the chat area's own `startVoiceWithPrewarm`. */
|
|
9
|
+
onStart: () => void;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The Quick assistant window's Voice view, ready: not started (BOFF-10180, C2).
|
|
13
|
+
*
|
|
14
|
+
* ★ Nothing here listens. The brand orb is a still picture, with no ring, halo or
|
|
15
|
+
* motion, so it cannot read as "the microphone is open"; the status line says
|
|
16
|
+
* when it opens ("only when you press Start voice"); opening the window, or
|
|
17
|
+
* switching to this view, starts neither the microphone nor an avatar. The one
|
|
18
|
+
* thing that does is the person's press on Start voice, which is the composer's
|
|
19
|
+
* own start (`startVoiceWithPrewarm`).
|
|
20
|
+
*
|
|
21
|
+
* ★ Start voice is never auto-focused. Focus lands in the message box when the
|
|
22
|
+
* window opens, so a stray Enter cannot open the microphone.
|
|
23
|
+
*
|
|
24
|
+
* ★ Where voice cannot start (the mode takes none, the browser has no speech,
|
|
25
|
+
* the conversation is read-only, or it is busy) the reason takes the button's
|
|
26
|
+
* place, as a polite status, and there is no Start voice to press.
|
|
27
|
+
*
|
|
28
|
+
* ★ The orb is the brand's. Face stills belong to the avatar seam, which offers
|
|
29
|
+
* no accessor for the chosen face, and the window never starts an avatar.
|
|
30
|
+
*/
|
|
31
|
+
export declare const AssistantVoiceReady: FC<AssistantVoiceReadyProps>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { useAssistantTr as e } from "../../i18n.js";
|
|
2
|
+
import { Mic as t } from "lucide-react";
|
|
3
|
+
import { jsx as n, jsxs as r } from "react/jsx-runtime";
|
|
4
|
+
//#region src/shared/assistant/ui/compact/AssistantVoiceReady.tsx
|
|
5
|
+
var i = ({ blockedReason: i, onStart: a }) => {
|
|
6
|
+
let o = e();
|
|
7
|
+
return /* @__PURE__ */ r("div", {
|
|
8
|
+
className: "flex min-h-0 flex-1 flex-col items-center justify-center gap-4 overflow-y-auto px-6 py-4 text-center",
|
|
9
|
+
"data-testid": "assistant-window-voice-ready",
|
|
10
|
+
children: [
|
|
11
|
+
/* @__PURE__ */ n("span", {
|
|
12
|
+
className: "size-28 shrink-0 rounded-full bg-gradient-to-br from-action-primary-bg/80 to-action-primary-bg/20 shadow-[var(--shadow-elevation-3)]",
|
|
13
|
+
"aria-hidden": "true",
|
|
14
|
+
"data-testid": "assistant-window-orb"
|
|
15
|
+
}),
|
|
16
|
+
/* @__PURE__ */ n("h2", {
|
|
17
|
+
className: "text-lg font-semibold text-text-primary",
|
|
18
|
+
children: o("window.voiceReadyTitle", "Talk to the assistant")
|
|
19
|
+
}),
|
|
20
|
+
/* @__PURE__ */ n("p", {
|
|
21
|
+
role: "status",
|
|
22
|
+
"aria-live": "polite",
|
|
23
|
+
className: "max-w-[16rem] text-sm text-text-secondary",
|
|
24
|
+
"data-testid": "assistant-window-voice-status",
|
|
25
|
+
children: i ?? o("window.voiceReadyStatus", "The microphone starts only when you press Start voice.")
|
|
26
|
+
}),
|
|
27
|
+
i === null ? /* @__PURE__ */ r("button", {
|
|
28
|
+
type: "button",
|
|
29
|
+
onClick: a,
|
|
30
|
+
className: "flex h-12 items-center gap-2 rounded-full bg-action-primary-bg px-6 text-base font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg-hover focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] focus-visible:ring-offset-2 focus-visible:ring-offset-[var(--color-bg-elevated)]",
|
|
31
|
+
"data-testid": "assistant-window-start-voice",
|
|
32
|
+
children: [/* @__PURE__ */ n(t, {
|
|
33
|
+
className: "size-5",
|
|
34
|
+
"aria-hidden": "true"
|
|
35
|
+
}), o("window.startVoice", "Start voice")]
|
|
36
|
+
}) : null
|
|
37
|
+
]
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
//#endregion
|
|
41
|
+
export { i as AssistantVoiceReady };
|
|
@@ -4,6 +4,12 @@ export interface AssistantWaitingChangeRowProps {
|
|
|
4
4
|
count: number;
|
|
5
5
|
/** Scroll to the first waiting change's card and focus it. */
|
|
6
6
|
onReview: () => void;
|
|
7
|
+
/**
|
|
8
|
+
* BOFF-10180 (the Quick assistant window): the card is in the full assistant,
|
|
9
|
+
* so the row's one button says so ("Review in full assistant") and `onReview`
|
|
10
|
+
* opens the full assistant. Still a pointer, never a second way to approve.
|
|
11
|
+
*/
|
|
12
|
+
opensFull?: boolean;
|
|
7
13
|
}
|
|
8
14
|
/**
|
|
9
15
|
* The pinned "change waiting" row above the composer (N6, 2026-10-29), in the
|
|
@@ -2,8 +2,8 @@ import { useAssistantTr as e } from "../../i18n.js";
|
|
|
2
2
|
import { ShieldAlert as t } from "lucide-react";
|
|
3
3
|
import { jsx as n, jsxs as r } from "react/jsx-runtime";
|
|
4
4
|
//#region src/shared/assistant/ui/compact/AssistantWaitingChangeRow.tsx
|
|
5
|
-
var i = ({ count: i, onReview: a }) => {
|
|
6
|
-
let
|
|
5
|
+
var i = ({ count: i, onReview: a, opensFull: o = !1 }) => {
|
|
6
|
+
let s = e();
|
|
7
7
|
return /* @__PURE__ */ r("div", {
|
|
8
8
|
className: "mb-2 flex min-h-11 items-center gap-2 rounded-xl border border-status-warning bg-status-warning/10 px-3 py-1",
|
|
9
9
|
"data-testid": "assistant-waiting-change",
|
|
@@ -17,15 +17,15 @@ var i = ({ count: i, onReview: a }) => {
|
|
|
17
17
|
"aria-live": "polite",
|
|
18
18
|
className: "min-w-0 flex-1 text-sm text-text-primary",
|
|
19
19
|
"data-testid": "assistant-waiting-change-status",
|
|
20
|
-
children:
|
|
20
|
+
children: s("waitingChange.status", "{{count}} change(s) waiting for your approval", { count: i })
|
|
21
21
|
}),
|
|
22
22
|
/* @__PURE__ */ n("button", {
|
|
23
23
|
type: "button",
|
|
24
24
|
onClick: a,
|
|
25
25
|
className: "min-h-9 shrink-0 rounded-button px-3 text-sm font-medium text-action-primary-bg transition-colors hover:bg-bg-sunken focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]",
|
|
26
|
-
"aria-label": o("waitingChange.reviewLabel", "Review the change waiting for your approval"),
|
|
26
|
+
"aria-label": o ? void 0 : s("waitingChange.reviewLabel", "Review the change waiting for your approval"),
|
|
27
27
|
"data-testid": "assistant-waiting-change-review",
|
|
28
|
-
children: o("waitingChange.review", "Review")
|
|
28
|
+
children: o ? s("waitingChange.reviewInFull", "Review in full assistant") : s("waitingChange.review", "Review")
|
|
29
29
|
})
|
|
30
30
|
]
|
|
31
31
|
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
import { AssistantWindowView } from '../assistantOverlayState';
|
|
3
|
+
export interface AssistantWindowHeaderProps {
|
|
4
|
+
/** The view the window shows. */
|
|
5
|
+
view: AssistantWindowView;
|
|
6
|
+
/** Chat or Voice was chosen. Presentation only: a live voice conversation carries on. */
|
|
7
|
+
onSelectView: (view: AssistantWindowView) => void;
|
|
8
|
+
/** A voice conversation is live: Minimize ends it, and the language waits. */
|
|
9
|
+
voiceLive: boolean;
|
|
10
|
+
/** Switch the same instance to the full assistant. Nothing is re-sent. */
|
|
11
|
+
onOpenFull: () => void;
|
|
12
|
+
/** Hide the window and keep everything in it (CW-M). */
|
|
13
|
+
onMinimize: () => void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The Quick assistant window's header strip (BOFF-10178 Slice 3, BOFF-10180
|
|
17
|
+
* Slice 5):
|
|
18
|
+
*
|
|
19
|
+
* `[Chat | Voice] [文A English ▾] ………… [Open full assistant] [Minimize]`
|
|
20
|
+
*
|
|
21
|
+
* Four controls, 40 px each (C2). The pilot name is the panel's accessible name,
|
|
22
|
+
* so the strip spends its width on the controls.
|
|
23
|
+
*
|
|
24
|
+
* ★ The view toggle is a two-option segmented control with `aria-pressed`: each
|
|
25
|
+
* option names the view it SHOWS, and the one showing is pressed. It is
|
|
26
|
+
* presentation only: it starts nothing and ends nothing.
|
|
27
|
+
*
|
|
28
|
+
* ★ No Close (CW-M): the window is hidden, never closed, so a draft, its
|
|
29
|
+
* attachments and the conversation stay exactly where they are. Closing is the
|
|
30
|
+
* full panel's. While voice is live Minimize says it ENDS voice (VD): the hidden
|
|
31
|
+
* window keeps everything but the microphone, and nothing listens in the
|
|
32
|
+
* background.
|
|
33
|
+
*
|
|
34
|
+
* ★ Every control keeps one NAME (WCAG 2.5.3): the tooltip is the same words.
|
|
35
|
+
*/
|
|
36
|
+
export declare const AssistantWindowHeader: FC<AssistantWindowHeaderProps>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { useAssistantTr as e } from "../../i18n.js";
|
|
2
|
+
import { AssistantLanguagePill as t } from "./AssistantLanguagePill.js";
|
|
3
|
+
import { AudioLines as n, MessageSquare as r, Minus as i, PanelRight as a } from "lucide-react";
|
|
4
|
+
import { jsx as o, jsxs as s } from "react/jsx-runtime";
|
|
5
|
+
//#region src/shared/assistant/ui/compact/AssistantWindowHeader.tsx
|
|
6
|
+
var c = "flex size-10 shrink-0 items-center justify-center rounded-button transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]", l = "text-text-secondary hover:bg-bg-sunken hover:text-text-primary", u = ({ view: u, onSelectView: d, voiceLive: f, onOpenFull: p, onMinimize: m }) => {
|
|
7
|
+
let h = e(), g = h("window.openFull", "Open full assistant"), _ = f ? h("window.minimizeEndsVoice", "Minimize (ends voice)") : h("window.minimize", "Minimize"), v = h("window.viewChat", "Chat"), y = h("window.viewVoice", "Voice"), b = (e, t, n) => {
|
|
8
|
+
let r = u === e;
|
|
9
|
+
return /* @__PURE__ */ o("button", {
|
|
10
|
+
type: "button",
|
|
11
|
+
onClick: () => d(e),
|
|
12
|
+
className: `flex size-10 shrink-0 items-center justify-center rounded-full transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] ${r ? "bg-[var(--color-accent-soft)] text-action-primary-bg" : "text-text-secondary hover:bg-bg-sunken hover:text-text-primary"}`,
|
|
13
|
+
"aria-pressed": r,
|
|
14
|
+
"aria-label": t,
|
|
15
|
+
title: t,
|
|
16
|
+
"data-testid": `assistant-window-view-${e}`,
|
|
17
|
+
children: n
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
return /* @__PURE__ */ s("div", {
|
|
21
|
+
className: "flex min-h-14 items-center gap-1 border-b border-border-default px-2 py-2",
|
|
22
|
+
"data-testid": "assistant-window-header",
|
|
23
|
+
"data-presentation": "compact",
|
|
24
|
+
"data-surface": "window",
|
|
25
|
+
children: [
|
|
26
|
+
/* @__PURE__ */ s("div", {
|
|
27
|
+
role: "group",
|
|
28
|
+
"aria-label": h("window.view", "View"),
|
|
29
|
+
className: "flex shrink-0 items-center rounded-full border border-border-default",
|
|
30
|
+
"data-testid": "assistant-window-view",
|
|
31
|
+
children: [b("chat", v, /* @__PURE__ */ o(r, {
|
|
32
|
+
className: "size-5",
|
|
33
|
+
"aria-hidden": "true"
|
|
34
|
+
})), b("voice", y, /* @__PURE__ */ o(n, {
|
|
35
|
+
className: "size-5",
|
|
36
|
+
"aria-hidden": "true"
|
|
37
|
+
}))]
|
|
38
|
+
}),
|
|
39
|
+
/* @__PURE__ */ o(t, { voiceLive: f }),
|
|
40
|
+
/* @__PURE__ */ o("button", {
|
|
41
|
+
type: "button",
|
|
42
|
+
onClick: () => p(),
|
|
43
|
+
className: `${c} ${l}`,
|
|
44
|
+
"aria-label": g,
|
|
45
|
+
title: g,
|
|
46
|
+
"data-testid": "assistant-open-full",
|
|
47
|
+
children: /* @__PURE__ */ o(a, {
|
|
48
|
+
className: "size-5",
|
|
49
|
+
"aria-hidden": "true"
|
|
50
|
+
})
|
|
51
|
+
}),
|
|
52
|
+
/* @__PURE__ */ o("button", {
|
|
53
|
+
type: "button",
|
|
54
|
+
onClick: m,
|
|
55
|
+
className: `${c} ${l}`,
|
|
56
|
+
"aria-label": _,
|
|
57
|
+
title: _,
|
|
58
|
+
"data-testid": "assistant-minimize",
|
|
59
|
+
children: /* @__PURE__ */ o(i, {
|
|
60
|
+
className: "size-5",
|
|
61
|
+
"aria-hidden": "true"
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
]
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
//#endregion
|
|
68
|
+
export { u as AssistantWindowHeader };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { isAssistantWindowView, AssistantWindowView } from '../assistantOverlayState';
|
|
2
|
+
export { isAssistantWindowView, type AssistantWindowView };
|
|
3
|
+
/**
|
|
4
|
+
* CW-V: the view the window shows.
|
|
5
|
+
*
|
|
6
|
+
* - Not live: the LAST USED view; the first time (nothing stored) Voice where
|
|
7
|
+
* voice is available here, else Chat.
|
|
8
|
+
* - Live: the voice view, which is where a conversation that has just started is
|
|
9
|
+
* (starting it from the Chat view moves the window to it), unless the person
|
|
10
|
+
* stepped to Chat since. That step is NOT the last-used view: it lasts as long
|
|
11
|
+
* as the conversation does.
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveWindowView(input: {
|
|
14
|
+
/** The last-used view the overlay remembers, or null when none was ever chosen. */
|
|
15
|
+
stored: AssistantWindowView | null;
|
|
16
|
+
/** Voice can start here (speech works and the mode takes it). */
|
|
17
|
+
voiceAvailable: boolean;
|
|
18
|
+
/** A voice session is live. */
|
|
19
|
+
voiceLive: boolean;
|
|
20
|
+
/** The view the person chose during the live conversation. */
|
|
21
|
+
liveView: AssistantWindowView;
|
|
22
|
+
}): AssistantWindowView;
|
|
23
|
+
/** What the chat area tells the panel about voice in the window. */
|
|
24
|
+
export interface WindowVoiceState {
|
|
25
|
+
/** Voice can start here: `voiceSession.available` and the active mode takes it. */
|
|
26
|
+
available: boolean;
|
|
27
|
+
/** A session is live. */
|
|
28
|
+
live: boolean;
|
|
29
|
+
}
|
|
30
|
+
/** Until the chat area reports: not available, not live. */
|
|
31
|
+
export declare const WINDOW_VOICE_IDLE: WindowVoiceState;
|
|
32
|
+
/**
|
|
33
|
+
* The window's view and how a person changes it. A choice made while voice is
|
|
34
|
+
* live is the live conversation's alone; any other choice is remembered
|
|
35
|
+
* (`onRemember`, the overlay's storage). Each new conversation starts on Voice.
|
|
36
|
+
*
|
|
37
|
+
* ★ The state is adjusted DURING render, conditional on the flag it follows, so
|
|
38
|
+
* no frame shows the wrong view.
|
|
39
|
+
*/
|
|
40
|
+
export declare function useAssistantWindowView(args: {
|
|
41
|
+
stored: AssistantWindowView | null;
|
|
42
|
+
onRemember: ((view: AssistantWindowView) => void) | undefined;
|
|
43
|
+
voice: WindowVoiceState;
|
|
44
|
+
}): {
|
|
45
|
+
view: AssistantWindowView;
|
|
46
|
+
/** The person chose a view in the header. */
|
|
47
|
+
select: (view: AssistantWindowView) => void;
|
|
48
|
+
/** The view is where the person IS now (Continue in chat): remembered, never a live-only choice. */
|
|
49
|
+
remember: (view: AssistantWindowView) => void;
|
|
50
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import "../assistantOverlayState.js";
|
|
2
|
+
import { useCallback as e, useState as t } from "react";
|
|
3
|
+
//#region src/shared/assistant/ui/compact/windowView.ts
|
|
4
|
+
function n(e) {
|
|
5
|
+
return e.voiceLive ? e.liveView : e.stored ?? (e.voiceAvailable ? "voice" : "chat");
|
|
6
|
+
}
|
|
7
|
+
var r = {
|
|
8
|
+
available: !1,
|
|
9
|
+
live: !1
|
|
10
|
+
};
|
|
11
|
+
function i(r) {
|
|
12
|
+
let { stored: i, onRemember: a, voice: o } = r, [s, c] = t("voice"), [l, u] = t(null), [d, f] = t(o.live);
|
|
13
|
+
d !== o.live && (f(o.live), o.live && c("voice"));
|
|
14
|
+
let p = n({
|
|
15
|
+
stored: l ?? i,
|
|
16
|
+
voiceAvailable: o.available,
|
|
17
|
+
voiceLive: o.live,
|
|
18
|
+
liveView: s
|
|
19
|
+
}), m = o.live, h = e((e) => {
|
|
20
|
+
u(e), a?.(e);
|
|
21
|
+
}, [a]);
|
|
22
|
+
return {
|
|
23
|
+
view: p,
|
|
24
|
+
select: e((e) => {
|
|
25
|
+
m ? c(e) : h(e);
|
|
26
|
+
}, [m, h]),
|
|
27
|
+
remember: h
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { r as WINDOW_VOICE_IDLE, n as resolveWindowView, i as useAssistantWindowView };
|
|
@@ -32,6 +32,14 @@ interface Props {
|
|
|
32
32
|
* into them; and a product composing its own chrome keeps the icon it has.
|
|
33
33
|
*/
|
|
34
34
|
showLabel?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* BOFF-10178 (compact only): keep the source menu inside the VISUAL viewport.
|
|
37
|
+
* On a phone with the keyboard up the sheet is only as tall as the visual
|
|
38
|
+
* viewport leaves it (`--assistant-viewport-height`), and the menu opens
|
|
39
|
+
* upward inside it, so its height is capped to what the sheet can hold and it
|
|
40
|
+
* scrolls past that. Absent, the menu is exactly what it was.
|
|
41
|
+
*/
|
|
42
|
+
capMenuHeight?: boolean;
|
|
35
43
|
}
|
|
36
44
|
/**
|
|
37
45
|
* Leading circular "+" attach control in the composer.
|
|
@@ -9,88 +9,88 @@ import { useCallback as c, useEffect as l, useMemo as u, useRef as d, useState a
|
|
|
9
9
|
import { Camera as p, FileText as m, FolderOpen as h, ImageIcon as g, Plus as _ } from "lucide-react";
|
|
10
10
|
import { jsx as v, jsxs as y } from "react/jsx-runtime";
|
|
11
11
|
//#region src/shared/assistant/ui/composer/AssistantAttachmentButton.tsx
|
|
12
|
-
var b = "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",
|
|
12
|
+
var b = " max-h-[calc(var(--assistant-viewport-height,100dvh)-10rem)] overflow-y-auto", x = "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", S = `${x} sm:w-auto sm:gap-1.5 sm:px-3`, C = {
|
|
13
13
|
camera: p,
|
|
14
14
|
gallery: g,
|
|
15
15
|
documents: m,
|
|
16
16
|
files: h
|
|
17
|
-
},
|
|
18
|
-
let
|
|
19
|
-
|
|
20
|
-
let
|
|
17
|
+
}, w = ({ onFiles: p, onPickerOpen: m, direction: h = "up", canAddMore: g, disabled: w = !1, showLabel: T = !1, capMenuHeight: E = !1 }) => {
|
|
18
|
+
let D = t(), O = d(null), k = d(null), A = d(null), [j, M] = f(!1), N = d(m);
|
|
19
|
+
N.current = m;
|
|
20
|
+
let P = u(() => n(r()), []), F = i(a()), I = g || F, L = g ? F ? `${D("composer.attach", "Attach files or photos")} · ${D("composer.connectSource", "Connect a source")}` : D("composer.attach", "Attach files or photos") : D("composer.attachFull", "Attachment limit reached ({{max}})", { max: 5 }), R = T && g && F ? D("composer.connectSource", "Connect a source") : null, z = s(A, () => M(!1));
|
|
21
21
|
l(() => {
|
|
22
|
-
if (!
|
|
22
|
+
if (!j) return;
|
|
23
23
|
let e = (e) => {
|
|
24
24
|
let t = e.target;
|
|
25
|
-
|
|
25
|
+
k.current?.contains(t) || A.current?.contains(t) || M(!1);
|
|
26
26
|
};
|
|
27
27
|
return window.addEventListener("mousedown", e), () => {
|
|
28
28
|
window.removeEventListener("mousedown", e);
|
|
29
29
|
};
|
|
30
|
-
}, [
|
|
31
|
-
let
|
|
30
|
+
}, [j]);
|
|
31
|
+
let B = c((e) => {
|
|
32
32
|
p(e.target.files), e.target.value = "";
|
|
33
|
-
}, [p]),
|
|
34
|
-
let t =
|
|
35
|
-
t && (t.accept = e.accept, t.multiple = e.multiple, e.capture ? t.setAttribute("capture", e.capture) : t.removeAttribute("capture"),
|
|
33
|
+
}, [p]), V = c((e) => {
|
|
34
|
+
let t = O.current;
|
|
35
|
+
t && (t.accept = e.accept, t.multiple = e.multiple, e.capture ? t.setAttribute("capture", e.capture) : t.removeAttribute("capture"), M(!1), N.current?.(), t.click());
|
|
36
36
|
}, []);
|
|
37
37
|
return /* @__PURE__ */ y("div", {
|
|
38
38
|
className: "relative flex-shrink-0",
|
|
39
39
|
children: [
|
|
40
40
|
/* @__PURE__ */ v("input", {
|
|
41
|
-
ref:
|
|
41
|
+
ref: O,
|
|
42
42
|
type: "file",
|
|
43
43
|
multiple: !0,
|
|
44
44
|
className: "hidden",
|
|
45
|
-
onChange:
|
|
45
|
+
onChange: B,
|
|
46
46
|
tabIndex: -1,
|
|
47
47
|
"aria-hidden": "true",
|
|
48
48
|
"data-testid": "assistant-attach-input"
|
|
49
49
|
}),
|
|
50
50
|
/* @__PURE__ */ y("button", {
|
|
51
|
-
ref:
|
|
51
|
+
ref: A,
|
|
52
52
|
type: "button",
|
|
53
|
-
onClick: () =>
|
|
54
|
-
disabled:
|
|
55
|
-
title:
|
|
56
|
-
"aria-label":
|
|
53
|
+
onClick: () => M((e) => !e),
|
|
54
|
+
disabled: w || !I,
|
|
55
|
+
title: L,
|
|
56
|
+
"aria-label": L,
|
|
57
57
|
"aria-haspopup": "menu",
|
|
58
|
-
"aria-expanded":
|
|
59
|
-
className:
|
|
58
|
+
"aria-expanded": j,
|
|
59
|
+
className: R ? S : x,
|
|
60
60
|
"data-testid": "assistant-attach",
|
|
61
|
-
children: [/* @__PURE__ */ v(_, { className: "size-4" }),
|
|
61
|
+
children: [/* @__PURE__ */ v(_, { className: "size-4" }), R ? /* @__PURE__ */ v("span", {
|
|
62
62
|
className: "hidden max-w-[10rem] truncate text-[13px] font-medium leading-tight sm:block",
|
|
63
63
|
"data-testid": "assistant-attach-label",
|
|
64
|
-
children:
|
|
64
|
+
children: R
|
|
65
65
|
}) : null]
|
|
66
66
|
}),
|
|
67
|
-
|
|
68
|
-
...
|
|
67
|
+
j ? /* @__PURE__ */ v(e, {
|
|
68
|
+
...z,
|
|
69
69
|
children: /* @__PURE__ */ y("div", {
|
|
70
|
-
ref:
|
|
70
|
+
ref: k,
|
|
71
71
|
role: "menu",
|
|
72
|
-
"aria-label":
|
|
73
|
-
className: `absolute ${h === "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`,
|
|
72
|
+
"aria-label": D("composer.attachSource", "Attach from"),
|
|
73
|
+
className: `absolute ${h === "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${E ? b : ""}`,
|
|
74
74
|
"data-testid": "assistant-attach-menu",
|
|
75
|
-
children: [
|
|
76
|
-
let t =
|
|
75
|
+
children: [P.map((e) => {
|
|
76
|
+
let t = C[e.id];
|
|
77
77
|
return /* @__PURE__ */ y("button", {
|
|
78
78
|
type: "button",
|
|
79
79
|
role: "menuitem",
|
|
80
80
|
disabled: !g,
|
|
81
|
-
onClick: () =>
|
|
81
|
+
onClick: () => V(e),
|
|
82
82
|
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 disabled:cursor-not-allowed disabled:text-text-muted disabled:hover:bg-transparent",
|
|
83
83
|
"data-testid": `assistant-attach-source-${e.id}`,
|
|
84
84
|
children: [/* @__PURE__ */ v(t, {
|
|
85
85
|
className: "size-4 flex-shrink-0 text-text-secondary",
|
|
86
86
|
"aria-hidden": "true"
|
|
87
|
-
}),
|
|
87
|
+
}), D(e.labelKey, e.labelFallback)]
|
|
88
88
|
}, e.id);
|
|
89
|
-
}), /* @__PURE__ */ v(o, { onActivate: () =>
|
|
89
|
+
}), /* @__PURE__ */ v(o, { onActivate: () => M(!1) })]
|
|
90
90
|
})
|
|
91
91
|
}) : null
|
|
92
92
|
]
|
|
93
93
|
});
|
|
94
94
|
};
|
|
95
95
|
//#endregion
|
|
96
|
-
export {
|
|
96
|
+
export { w as AssistantAttachmentButton };
|
|
@@ -115,6 +115,12 @@ interface Props {
|
|
|
115
115
|
* "change waiting" row. Classic never draws it.
|
|
116
116
|
*/
|
|
117
117
|
aboveCard?: ReactNode;
|
|
118
|
+
/**
|
|
119
|
+
* BOFF-10180 (compact only): show the mode as a read-only label, not a menu. The
|
|
120
|
+
* Quick assistant window names the mode it is in and leaves changing it to the
|
|
121
|
+
* full panel (DESIGN §3.3). Absent: the Mode menu.
|
|
122
|
+
*/
|
|
123
|
+
modeReadOnly?: boolean;
|
|
118
124
|
}
|
|
119
125
|
/** A local draft snapshot; no files are uploaded by observing it. */
|
|
120
126
|
export interface AssistantComposerDraft {
|