@burdenoff/fe-libs 2026.1029.7 → 2026.1029.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/shared/assistant/appControls/grammar.js +21 -21
- package/dist/shared/assistant/appControls/registry.js +1 -1
- package/dist/shared/assistant/commandCompletions.js +87 -86
- package/dist/shared/assistant/i18nManifest.d.ts +27 -0
- package/dist/shared/assistant/i18nManifest.js +27 -0
- package/dist/shared/assistant/index.d.ts +9 -0
- package/dist/shared/assistant/localUiCommands.js +106 -112
- package/dist/shared/assistant/localeCommands/languageNames.js +13 -13
- package/dist/shared/assistant/ui/AIAssistantPanel.d.ts +11 -0
- package/dist/shared/assistant/ui/AIAssistantPanel.js +233 -127
- package/dist/shared/assistant/ui/AssistantChatArea.d.ts +13 -1
- package/dist/shared/assistant/ui/AssistantChatArea.js +1632 -1605
- package/dist/shared/assistant/ui/AssistantConversationList.d.ts +17 -1
- package/dist/shared/assistant/ui/AssistantConversationList.js +304 -274
- package/dist/shared/assistant/ui/AssistantOverlay.d.ts +11 -0
- package/dist/shared/assistant/ui/AssistantOverlay.js +106 -90
- package/dist/shared/assistant/ui/closeGuard.d.ts +21 -0
- package/dist/shared/assistant/ui/closeGuard.js +8 -0
- package/dist/shared/assistant/ui/compact/AssistantCompactHeader.d.ts +38 -0
- package/dist/shared/assistant/ui/compact/AssistantCompactHeader.js +89 -0
- package/dist/shared/assistant/ui/compact/AssistantConversationMenu.d.ts +29 -0
- package/dist/shared/assistant/ui/compact/AssistantConversationMenu.js +87 -0
- package/dist/shared/assistant/ui/compact/AssistantDiscardDraftDialog.d.ts +19 -0
- package/dist/shared/assistant/ui/compact/AssistantDiscardDraftDialog.js +38 -0
- package/dist/shared/assistant/ui/compact/AssistantMenuView.d.ts +42 -0
- package/dist/shared/assistant/ui/compact/AssistantMenuView.js +285 -0
- package/dist/shared/assistant/ui/compact/AssistantVoiceLanguageSettings.d.ts +24 -0
- package/dist/shared/assistant/ui/compact/AssistantVoiceLanguageSettings.js +68 -0
- package/dist/shared/assistant/ui/compact/AssistantWaitingChangeRow.d.ts +21 -0
- package/dist/shared/assistant/ui/compact/AssistantWaitingChangeRow.js +34 -0
- package/dist/shared/assistant/ui/compact/compactChrome.d.ts +75 -0
- package/dist/shared/assistant/ui/compact/compactChrome.js +39 -0
- package/dist/shared/assistant/ui/compact/waitingChange.d.ts +49 -0
- package/dist/shared/assistant/ui/compact/waitingChange.js +12 -0
- package/dist/shared/assistant/ui/composer/AssistantComposer.d.ts +19 -18
- package/dist/shared/assistant/ui/composer/AssistantComposer.js +261 -224
- package/dist/shared/assistant/useAssistantKeyboardShortcuts.d.ts +16 -2
- package/dist/shared/assistant/useAssistantKeyboardShortcuts.js +28 -16
- package/dist/shared-assistant.js +51 -42
- package/package.json +1 -1
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
export interface AssistantMenuViewProps {
|
|
3
|
+
/** The element id `aria-controls` on ☰ points at. */
|
|
4
|
+
id: string;
|
|
5
|
+
/** Back to the conversation (Back, or Escape from the list). */
|
|
6
|
+
onClose: () => void;
|
|
7
|
+
/** The × of the view: closes the assistant, through the panel's close request (N1). */
|
|
8
|
+
onRequestClosePanel: () => void;
|
|
9
|
+
/** New conversation: the panel creates it, closes this view and focuses the message box. */
|
|
10
|
+
onNewConversation: () => void;
|
|
11
|
+
/** Absent: the live-avatar tab is not offered here, and the Avatar row is not shown. */
|
|
12
|
+
onOpenAvatar?: () => void;
|
|
13
|
+
docked?: boolean;
|
|
14
|
+
/** Absent: the product shows no dock control, and Panel layout is not shown. */
|
|
15
|
+
onToggleDock?: () => void;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The ☰ view (BOFF-10177, F2): what the compact header's ☰ opens, over the
|
|
19
|
+
* panel, in place of the classic header's New and History buttons and the ⋯
|
|
20
|
+
* menu's settings.
|
|
21
|
+
*
|
|
22
|
+
* - Back to conversation, and × (which closes the assistant);
|
|
23
|
+
* - New conversation (Ctrl/Cmd+Shift+N);
|
|
24
|
+
* - Recent: the newest conversations, and View all history, which opens the
|
|
25
|
+
* EXISTING history list (search, All/Chat/Voice, Pinned, Archived, groups,
|
|
26
|
+
* row actions, delete confirmation) as a sub-view;
|
|
27
|
+
* - Settings: Voice & language (a sub-view holding the six controls), Avatar
|
|
28
|
+
* (a row that opens the Avatar tab; nothing of the avatar is duplicated
|
|
29
|
+
* here) and Panel layout (Dock/Float, D13: its only home).
|
|
30
|
+
*
|
|
31
|
+
* ★ Every screen has an `h2` and a Back; focus lands on Back on open, on the
|
|
32
|
+
* sub-view's Back on entering one, and on the row that opened it on the way
|
|
33
|
+
* back. Escape closes ONE layer: a sub-view returns to this list, and this list
|
|
34
|
+
* returns to the conversation (`menuViewEscape`). The view is ONE `EscapeLayer`
|
|
35
|
+
* that decides, so it can neither double-close nor skip a screen, and a menu or
|
|
36
|
+
* dialog opened over it (mounted later) takes the key first.
|
|
37
|
+
*
|
|
38
|
+
* ★ The conversation under it stays mounted and `inert` (the panel sets that,
|
|
39
|
+
* as for the History drawer), so the draft, a running reply and a waiting
|
|
40
|
+
* change are exactly as they were.
|
|
41
|
+
*/
|
|
42
|
+
export declare const AssistantMenuView: FC<AssistantMenuViewProps>;
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { useI18n as e } from "../../../providers/shell/I18nProvider.js";
|
|
2
|
+
import { EscapeLayer as t } from "../../../components/EscapeLayer.js";
|
|
3
|
+
import { useAssistantProduct as n } from "../../product.js";
|
|
4
|
+
import { useAssistantTr as r } from "../../i18n.js";
|
|
5
|
+
import { useAssistantStore as i } from "../../store.js";
|
|
6
|
+
import { resolveLanguagePair as a } from "../../voice/languagePair.js";
|
|
7
|
+
import { useOverlayFocus as o } from "../useOverlayFocus.js";
|
|
8
|
+
import { assistantModeOptionName as s } from "../composer/AssistantModeMenu.js";
|
|
9
|
+
import { useAssistantHistoryStatus as c } from "../../history/historyStatus.js";
|
|
10
|
+
import { assistantHistory as l } from "../../history/historyEngine.js";
|
|
11
|
+
import { AssistantConversationList as u } from "../AssistantConversationList.js";
|
|
12
|
+
import { fieldOwnsEscape as ee, menuViewEscape as te, recentConversations as ne } from "./compactChrome.js";
|
|
13
|
+
import { AssistantVoiceLanguageSettings as re } from "./AssistantVoiceLanguageSettings.js";
|
|
14
|
+
import { useEffect as d, useLayoutEffect as f, useMemo as p, useRef as m, useState as h } from "react";
|
|
15
|
+
import { ArrowLeft as g, ChevronRight as _, History as ie, Languages as ae, PanelRight as oe, Plus as se, UserRound as v, X as y } from "lucide-react";
|
|
16
|
+
import { Fragment as b, jsx as x, jsxs as S } from "react/jsx-runtime";
|
|
17
|
+
//#region src/shared/assistant/ui/compact/AssistantMenuView.tsx
|
|
18
|
+
var C = "focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]", w = `flex size-10 shrink-0 items-center justify-center rounded-button text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary ${C}`, T = `flex min-h-11 w-full items-center gap-3 px-4 py-2 text-left text-sm text-text-primary transition-colors hover:bg-bg-sunken ${C} focus-visible:ring-inset`, ce = typeof document > "u" ? d : f, E = ({ icon: e, label: t, trailing: n, onClick: r, buttonRef: i, testId: a, describedBy: o }) => /* @__PURE__ */ S("button", {
|
|
19
|
+
ref: i,
|
|
20
|
+
type: "button",
|
|
21
|
+
onClick: r,
|
|
22
|
+
className: T,
|
|
23
|
+
"aria-describedby": o,
|
|
24
|
+
"data-testid": a,
|
|
25
|
+
children: [
|
|
26
|
+
/* @__PURE__ */ x("span", {
|
|
27
|
+
className: "flex size-5 shrink-0 items-center justify-center text-text-secondary",
|
|
28
|
+
children: e
|
|
29
|
+
}),
|
|
30
|
+
/* @__PURE__ */ x("span", {
|
|
31
|
+
className: "min-w-0 flex-1 truncate",
|
|
32
|
+
children: t
|
|
33
|
+
}),
|
|
34
|
+
n
|
|
35
|
+
]
|
|
36
|
+
}), D = ({ id: e, children: t }) => /* @__PURE__ */ x("h3", {
|
|
37
|
+
id: e,
|
|
38
|
+
className: "px-4 pb-1 pt-4 text-xs font-semibold uppercase tracking-wide text-text-muted",
|
|
39
|
+
children: t
|
|
40
|
+
}), le = ({ title: e, tr: t, onBack: n, backRef: r }) => /* @__PURE__ */ S("div", {
|
|
41
|
+
className: "flex items-center gap-1 border-b border-border-default bg-bg-elevated px-2 py-2",
|
|
42
|
+
children: [/* @__PURE__ */ x("button", {
|
|
43
|
+
ref: r,
|
|
44
|
+
type: "button",
|
|
45
|
+
onClick: n,
|
|
46
|
+
className: w,
|
|
47
|
+
"aria-label": t("menuView.back", "Back"),
|
|
48
|
+
"data-testid": "assistant-menu-subview-back",
|
|
49
|
+
children: /* @__PURE__ */ x(g, {
|
|
50
|
+
className: "size-5",
|
|
51
|
+
"aria-hidden": "true"
|
|
52
|
+
})
|
|
53
|
+
}), /* @__PURE__ */ x("h2", {
|
|
54
|
+
className: "min-w-0 flex-1 truncate text-base font-semibold text-text-primary",
|
|
55
|
+
children: e
|
|
56
|
+
})]
|
|
57
|
+
}), O = ({ id: d, onClose: f, onRequestClosePanel: O, onNewConversation: ue, onOpenAvatar: k, docked: A, onToggleDock: j }) => {
|
|
58
|
+
let M = r(), { locale: N } = e(), { modes: P } = n(), { conversations: F, activeConversationId: I, assistantLanguage: L, replyLanguage: R, setActiveConversation: z, setMode: B } = i(), V = c((e) => e.product !== null), [H, U] = h("root"), de = o("assistant-menu"), W = m(null), G = m(null), K = m(null), q = m(null), J = m("back"), Y = (e) => {
|
|
59
|
+
J.current = e, U(e);
|
|
60
|
+
}, X = () => U("root");
|
|
61
|
+
ce(() => {
|
|
62
|
+
if (H !== "history") {
|
|
63
|
+
if (H === "voice-language") {
|
|
64
|
+
G.current?.focus({ preventScroll: !0 });
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
((J.current === "voice-language" ? K.current : J.current === "history" ? q.current : null) ?? W.current)?.focus({ preventScroll: !0 }), J.current = "back";
|
|
68
|
+
}
|
|
69
|
+
}, [H]);
|
|
70
|
+
let Z = p(() => ne(F), [F]), fe = M("history.untitled", "Untitled conversation"), pe = (e) => {
|
|
71
|
+
if (V) l.openConversation(e, { modes: P });
|
|
72
|
+
else {
|
|
73
|
+
let t = F.find((t) => t.id === e);
|
|
74
|
+
z(e), t && B(t.mode);
|
|
75
|
+
}
|
|
76
|
+
f();
|
|
77
|
+
}, me = a(L, R, N).input.label, Q = `${d}-settings`, $ = `${d}-recent`;
|
|
78
|
+
return /* @__PURE__ */ x(t, {
|
|
79
|
+
onDismiss: () => {
|
|
80
|
+
H === "history" && ee(document.activeElement) || (te(H) === "back" ? X() : f());
|
|
81
|
+
},
|
|
82
|
+
children: /* @__PURE__ */ x("div", {
|
|
83
|
+
ref: de,
|
|
84
|
+
id: d,
|
|
85
|
+
tabIndex: -1,
|
|
86
|
+
role: "region",
|
|
87
|
+
"aria-label": M("menuView.title", "Assistant menu"),
|
|
88
|
+
className: "absolute inset-0 z-10 flex flex-col bg-bg-surface outline-none animate-in slide-in-from-left-2 fade-in duration-200 motion-reduce:animate-none",
|
|
89
|
+
"data-testid": "assistant-menu-view",
|
|
90
|
+
"data-menu-view": H,
|
|
91
|
+
children: H === "root" ? /* @__PURE__ */ S(b, { children: [/* @__PURE__ */ S("div", {
|
|
92
|
+
className: "flex items-center gap-1 border-b border-border-default bg-bg-elevated px-2 py-2",
|
|
93
|
+
children: [
|
|
94
|
+
/* @__PURE__ */ S("button", {
|
|
95
|
+
ref: W,
|
|
96
|
+
type: "button",
|
|
97
|
+
onClick: f,
|
|
98
|
+
className: `flex min-h-10 items-center gap-1.5 rounded-button px-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken ${C}`,
|
|
99
|
+
"data-testid": "assistant-menu-back",
|
|
100
|
+
children: [/* @__PURE__ */ x(g, {
|
|
101
|
+
className: "size-5",
|
|
102
|
+
"aria-hidden": "true"
|
|
103
|
+
}), M("menuView.backToConversation", "Back to conversation")]
|
|
104
|
+
}),
|
|
105
|
+
/* @__PURE__ */ x("h2", {
|
|
106
|
+
className: "sr-only",
|
|
107
|
+
children: M("menuView.title", "Assistant menu")
|
|
108
|
+
}),
|
|
109
|
+
/* @__PURE__ */ x("span", { className: "flex-1" }),
|
|
110
|
+
/* @__PURE__ */ x("button", {
|
|
111
|
+
type: "button",
|
|
112
|
+
onClick: O,
|
|
113
|
+
className: w,
|
|
114
|
+
"aria-label": M("header.close", "Close assistant"),
|
|
115
|
+
"data-testid": "assistant-menu-close",
|
|
116
|
+
children: /* @__PURE__ */ x(y, {
|
|
117
|
+
className: "size-5",
|
|
118
|
+
"aria-hidden": "true"
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
]
|
|
122
|
+
}), /* @__PURE__ */ S("div", {
|
|
123
|
+
className: "scrollbar-slim min-h-0 flex-1 overflow-y-auto pb-2",
|
|
124
|
+
children: [
|
|
125
|
+
/* @__PURE__ */ S("button", {
|
|
126
|
+
type: "button",
|
|
127
|
+
onClick: ue,
|
|
128
|
+
className: `${T} mt-2`,
|
|
129
|
+
"aria-keyshortcuts": "Control+Shift+N Meta+Shift+N",
|
|
130
|
+
"data-testid": "assistant-new-conversation",
|
|
131
|
+
children: [
|
|
132
|
+
/* @__PURE__ */ x("span", {
|
|
133
|
+
className: "flex size-5 shrink-0 items-center justify-center text-text-secondary",
|
|
134
|
+
children: /* @__PURE__ */ x(se, {
|
|
135
|
+
className: "size-5",
|
|
136
|
+
"aria-hidden": "true"
|
|
137
|
+
})
|
|
138
|
+
}),
|
|
139
|
+
/* @__PURE__ */ x("span", {
|
|
140
|
+
className: "min-w-0 flex-1 truncate",
|
|
141
|
+
children: M("header.newConversation", "New conversation")
|
|
142
|
+
}),
|
|
143
|
+
/* @__PURE__ */ x("kbd", {
|
|
144
|
+
className: "shrink-0 text-xs text-text-muted",
|
|
145
|
+
children: "Ctrl+Shift+N"
|
|
146
|
+
})
|
|
147
|
+
]
|
|
148
|
+
}),
|
|
149
|
+
/* @__PURE__ */ S("section", {
|
|
150
|
+
"aria-labelledby": $,
|
|
151
|
+
"data-testid": "assistant-menu-recent",
|
|
152
|
+
children: [
|
|
153
|
+
/* @__PURE__ */ x(D, {
|
|
154
|
+
id: $,
|
|
155
|
+
children: M("menuView.recent", "Recent")
|
|
156
|
+
}),
|
|
157
|
+
Z.length === 0 ? /* @__PURE__ */ x("p", {
|
|
158
|
+
className: "px-4 py-2 text-sm text-text-muted",
|
|
159
|
+
children: M("conversations.empty", "No conversations yet")
|
|
160
|
+
}) : /* @__PURE__ */ x("ul", { children: Z.map((e) => {
|
|
161
|
+
let t = P.find((t) => t.id === e.mode);
|
|
162
|
+
return /* @__PURE__ */ x("li", { children: /* @__PURE__ */ x("button", {
|
|
163
|
+
type: "button",
|
|
164
|
+
onClick: () => pe(e.id),
|
|
165
|
+
className: `${T} ${e.id === I ? "bg-bg-elevated" : ""}`,
|
|
166
|
+
"aria-current": e.id === I ? "true" : void 0,
|
|
167
|
+
"data-testid": "assistant-menu-recent-row",
|
|
168
|
+
children: /* @__PURE__ */ S("span", {
|
|
169
|
+
className: "min-w-0 flex-1",
|
|
170
|
+
children: [/* @__PURE__ */ x("span", {
|
|
171
|
+
className: "block truncate",
|
|
172
|
+
children: e.title || fe
|
|
173
|
+
}), t ? /* @__PURE__ */ x("span", {
|
|
174
|
+
className: "block truncate text-xs text-text-muted",
|
|
175
|
+
children: s(M, t)
|
|
176
|
+
}) : null]
|
|
177
|
+
})
|
|
178
|
+
}) }, e.id);
|
|
179
|
+
}) }),
|
|
180
|
+
/* @__PURE__ */ x(E, {
|
|
181
|
+
buttonRef: q,
|
|
182
|
+
icon: /* @__PURE__ */ x(ie, {
|
|
183
|
+
className: "size-5",
|
|
184
|
+
"aria-hidden": "true"
|
|
185
|
+
}),
|
|
186
|
+
label: M("menuView.viewAllHistory", "View all history"),
|
|
187
|
+
trailing: /* @__PURE__ */ x(_, {
|
|
188
|
+
className: "size-4 text-text-muted",
|
|
189
|
+
"aria-hidden": "true"
|
|
190
|
+
}),
|
|
191
|
+
onClick: () => Y("history"),
|
|
192
|
+
testId: "assistant-open-history"
|
|
193
|
+
})
|
|
194
|
+
]
|
|
195
|
+
}),
|
|
196
|
+
/* @__PURE__ */ S("section", {
|
|
197
|
+
"aria-labelledby": Q,
|
|
198
|
+
"data-testid": "assistant-menu-settings",
|
|
199
|
+
children: [
|
|
200
|
+
/* @__PURE__ */ x(D, {
|
|
201
|
+
id: Q,
|
|
202
|
+
children: M("menuView.settings", "Settings")
|
|
203
|
+
}),
|
|
204
|
+
/* @__PURE__ */ x(E, {
|
|
205
|
+
buttonRef: K,
|
|
206
|
+
icon: /* @__PURE__ */ x(ae, {
|
|
207
|
+
className: "size-5",
|
|
208
|
+
"aria-hidden": "true"
|
|
209
|
+
}),
|
|
210
|
+
label: M("menuView.voiceLanguage", "Voice & language"),
|
|
211
|
+
trailing: /* @__PURE__ */ S(b, { children: [/* @__PURE__ */ x("span", {
|
|
212
|
+
className: "max-w-[40%] truncate text-xs text-text-muted",
|
|
213
|
+
children: me
|
|
214
|
+
}), /* @__PURE__ */ x(_, {
|
|
215
|
+
className: "size-4 shrink-0 text-text-muted",
|
|
216
|
+
"aria-hidden": "true"
|
|
217
|
+
})] }),
|
|
218
|
+
onClick: () => Y("voice-language"),
|
|
219
|
+
testId: "assistant-menu-voice-language-row"
|
|
220
|
+
}),
|
|
221
|
+
k ? /* @__PURE__ */ x(E, {
|
|
222
|
+
icon: /* @__PURE__ */ x(v, {
|
|
223
|
+
className: "size-5",
|
|
224
|
+
"aria-hidden": "true"
|
|
225
|
+
}),
|
|
226
|
+
label: M("menuView.avatar", "Avatar"),
|
|
227
|
+
trailing: /* @__PURE__ */ x(_, {
|
|
228
|
+
className: "size-4 text-text-muted",
|
|
229
|
+
"aria-hidden": "true"
|
|
230
|
+
}),
|
|
231
|
+
onClick: k,
|
|
232
|
+
testId: "assistant-menu-avatar-row"
|
|
233
|
+
}) : null,
|
|
234
|
+
j ? /* @__PURE__ */ S("fieldset", {
|
|
235
|
+
className: "hidden min-w-0 border-0 px-4 py-2 sm:block",
|
|
236
|
+
"data-testid": "assistant-menu-panel-layout",
|
|
237
|
+
children: [/* @__PURE__ */ S("legend", {
|
|
238
|
+
className: "flex items-center gap-3 py-1 text-sm text-text-primary",
|
|
239
|
+
children: [/* @__PURE__ */ x(oe, {
|
|
240
|
+
className: "size-5 shrink-0 text-text-secondary",
|
|
241
|
+
"aria-hidden": "true"
|
|
242
|
+
}), M("menuView.panelLayout", "Panel layout")]
|
|
243
|
+
}), [[
|
|
244
|
+
!0,
|
|
245
|
+
M("menuView.layoutDocked", "Docked side panel"),
|
|
246
|
+
"docked"
|
|
247
|
+
], [
|
|
248
|
+
!1,
|
|
249
|
+
M("menuView.layoutFloating", "Floating window"),
|
|
250
|
+
"floating"
|
|
251
|
+
]].map(([e, t, n]) => /* @__PURE__ */ S("label", {
|
|
252
|
+
className: "flex min-h-11 cursor-pointer items-center gap-3 pl-8 text-sm text-text-primary focus-within:ring-2 focus-within:ring-[var(--color-focus-ring)]",
|
|
253
|
+
children: [/* @__PURE__ */ x("input", {
|
|
254
|
+
type: "radio",
|
|
255
|
+
name: `${d}-layout`,
|
|
256
|
+
checked: !!A === e,
|
|
257
|
+
onChange: () => {
|
|
258
|
+
!!A !== e && j();
|
|
259
|
+
},
|
|
260
|
+
className: "size-4 accent-current",
|
|
261
|
+
"data-testid": `assistant-panel-layout-${n}`
|
|
262
|
+
}), t]
|
|
263
|
+
}, n))]
|
|
264
|
+
}) : null
|
|
265
|
+
]
|
|
266
|
+
})
|
|
267
|
+
]
|
|
268
|
+
})] }) : H === "voice-language" ? /* @__PURE__ */ S(b, { children: [/* @__PURE__ */ x(le, {
|
|
269
|
+
title: M("menuView.voiceLanguage", "Voice & language"),
|
|
270
|
+
tr: M,
|
|
271
|
+
onBack: X,
|
|
272
|
+
backRef: G
|
|
273
|
+
}), /* @__PURE__ */ x("div", {
|
|
274
|
+
className: "scrollbar-slim min-h-0 flex-1 overflow-y-auto",
|
|
275
|
+
children: /* @__PURE__ */ x(re, {})
|
|
276
|
+
})] }) : /* @__PURE__ */ x(u, {
|
|
277
|
+
presentation: "compact",
|
|
278
|
+
onBack: X,
|
|
279
|
+
onDone: f
|
|
280
|
+
})
|
|
281
|
+
})
|
|
282
|
+
});
|
|
283
|
+
};
|
|
284
|
+
//#endregion
|
|
285
|
+
export { O as AssistantMenuView };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* ☰ → Settings → Voice & language: the six controls the classic ⋯ menu held
|
|
4
|
+
* (BOFF-10177, DESIGN-DELTA A4), the SAME components under the SAME render
|
|
5
|
+
* conditions, so a value read or written here is the one the ⋯ menu would have
|
|
6
|
+
* read or written: nothing is stored anew.
|
|
7
|
+
*
|
|
8
|
+
* 1 Language you speak `AssistantLanguageMenu`
|
|
9
|
+
* 2 Assistant replies in `AssistantLanguageMenu`
|
|
10
|
+
* 3 Read replies aloud below; needs speech output
|
|
11
|
+
* 4 Voice + Preview voice picker needs 2 or more voices; Preview a resolved voice
|
|
12
|
+
* 5 Wait after I stop talking needs speech recognition
|
|
13
|
+
* 6 Hands-free interrupt (beta) needs speech recognition or microphone capture
|
|
14
|
+
*
|
|
15
|
+
* ★ Read replies aloud is a real checkbox in its label, like the hands-free
|
|
16
|
+
* toggle beside it: the visible words are its name (WCAG 2.5.3). Blocked and
|
|
17
|
+
* off, it is `aria-disabled` (not `disabled`) and says why on screen, so a
|
|
18
|
+
* keyboard or screen-reader user finds it and hears the reason, as the classic
|
|
19
|
+
* row's tooltip told a mouse. Turning it OFF also silences what is speaking.
|
|
20
|
+
*
|
|
21
|
+
* ★ None of these touches the interface language: they set the assistant's
|
|
22
|
+
* own language and voice.
|
|
23
|
+
*/
|
|
24
|
+
export declare const AssistantVoiceLanguageSettings: FC;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { useAssistantTr as e } from "../../i18n.js";
|
|
2
|
+
import { useAssistantSpeech as t } from "../AssistantSpeechProvider.js";
|
|
3
|
+
import { AssistantLanguageMenu as n } from "../AssistantLanguageMenu.js";
|
|
4
|
+
import { AssistantVoiceSelector as r } from "../composer/AssistantVoiceSelector.js";
|
|
5
|
+
import { AssistantVoiceGraceSelector as i } from "../composer/AssistantVoiceGraceSelector.js";
|
|
6
|
+
import { AssistantHandsFreeInterruptToggle as a } from "../composer/AssistantHandsFreeInterruptToggle.js";
|
|
7
|
+
import { useId as o } from "react";
|
|
8
|
+
import { Play as s, Volume2 as c } from "lucide-react";
|
|
9
|
+
import { Fragment as l, jsx as u, jsxs as d } from "react/jsx-runtime";
|
|
10
|
+
//#region src/shared/assistant/ui/compact/AssistantVoiceLanguageSettings.tsx
|
|
11
|
+
var f = () => {
|
|
12
|
+
let f = e(), p = o(), { isSupported: m, isBlocked: h, disabledReason: g, autoSpeakEnabled: _, setAutoSpeak: v, stop: y, previewVoice: b, activeVoiceName: x } = t(), S = h && !_;
|
|
13
|
+
return /* @__PURE__ */ d("div", {
|
|
14
|
+
className: "py-1 [&_label]:min-h-11 [&_label]:text-sm",
|
|
15
|
+
"data-testid": "assistant-menu-voice-language",
|
|
16
|
+
children: [
|
|
17
|
+
m ? /* @__PURE__ */ d(l, { children: [/* @__PURE__ */ d("label", {
|
|
18
|
+
className: "flex items-center gap-2.5 px-4 py-2 text-text-primary focus-within:ring-2 focus-within:ring-inset focus-within:ring-[var(--color-focus-ring)]",
|
|
19
|
+
"data-testid": "assistant-auto-speak",
|
|
20
|
+
children: [
|
|
21
|
+
/* @__PURE__ */ u(c, {
|
|
22
|
+
className: "size-4 flex-shrink-0 text-text-secondary",
|
|
23
|
+
"aria-hidden": "true"
|
|
24
|
+
}),
|
|
25
|
+
/* @__PURE__ */ u("span", {
|
|
26
|
+
className: "flex-1",
|
|
27
|
+
children: f("menuView.readAloud", "Read replies aloud")
|
|
28
|
+
}),
|
|
29
|
+
/* @__PURE__ */ u("input", {
|
|
30
|
+
type: "checkbox",
|
|
31
|
+
checked: _,
|
|
32
|
+
"aria-disabled": S ? "true" : void 0,
|
|
33
|
+
"aria-describedby": S && g ? p : void 0,
|
|
34
|
+
onChange: () => {
|
|
35
|
+
S || (_ && y(), v(!_));
|
|
36
|
+
},
|
|
37
|
+
className: "size-4 accent-current aria-disabled:cursor-default aria-disabled:opacity-60",
|
|
38
|
+
"data-testid": "assistant-auto-speak-checkbox"
|
|
39
|
+
})
|
|
40
|
+
]
|
|
41
|
+
}), S && g ? /* @__PURE__ */ u("p", {
|
|
42
|
+
id: p,
|
|
43
|
+
className: "px-4 pb-2 text-xs text-text-muted",
|
|
44
|
+
children: g
|
|
45
|
+
}) : null] }) : null,
|
|
46
|
+
/* @__PURE__ */ u(n, {}),
|
|
47
|
+
/* @__PURE__ */ d("div", {
|
|
48
|
+
className: "flex items-center gap-1 px-3 py-1.5",
|
|
49
|
+
"data-testid": "assistant-menu-voice",
|
|
50
|
+
children: [/* @__PURE__ */ u(r, {}), x ? /* @__PURE__ */ d("button", {
|
|
51
|
+
type: "button",
|
|
52
|
+
onClick: () => b(x),
|
|
53
|
+
className: "flex min-h-9 items-center gap-1 rounded-md px-2 text-xs text-text-muted transition-colors hover:bg-bg-elevated hover:text-text-secondary focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]",
|
|
54
|
+
"aria-label": f("voice.preview", "Hear this voice"),
|
|
55
|
+
"data-testid": "assistant-voice-preview",
|
|
56
|
+
children: [/* @__PURE__ */ u(s, {
|
|
57
|
+
className: "size-3",
|
|
58
|
+
"aria-hidden": "true"
|
|
59
|
+
}), f("voice.previewShort", "Preview")]
|
|
60
|
+
}) : null]
|
|
61
|
+
}),
|
|
62
|
+
/* @__PURE__ */ u(i, {}),
|
|
63
|
+
/* @__PURE__ */ u(a, {})
|
|
64
|
+
]
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
//#endregion
|
|
68
|
+
export { f as AssistantVoiceLanguageSettings };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { FC } from 'react';
|
|
2
|
+
export interface AssistantWaitingChangeRowProps {
|
|
3
|
+
/** How many changes wait for the person's approval in the open conversation. */
|
|
4
|
+
count: number;
|
|
5
|
+
/** Scroll to the first waiting change's card and focus it. */
|
|
6
|
+
onReview: () => void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The pinned "change waiting" row above the composer (N6, 2026-10-29), in the
|
|
10
|
+
* compact presentation only.
|
|
11
|
+
*
|
|
12
|
+
* ★ A pointer to the EXISTING approval card, never a second way to approve: it
|
|
13
|
+
* has no Approve and no Decline, only Review, which scrolls to the card and
|
|
14
|
+
* focuses it. The card stays the one place a change is confirmed, by a button
|
|
15
|
+
* press (`writeProposals.ts`: voice and typed "yes" never approve).
|
|
16
|
+
*
|
|
17
|
+
* The cards sit inside the message list, so a person who scrolled up never saw
|
|
18
|
+
* a new one arrive; this row is always on screen while any change waits. Its
|
|
19
|
+
* text is a polite status, so the arrival is announced without taking focus.
|
|
20
|
+
*/
|
|
21
|
+
export declare const AssistantWaitingChangeRow: FC<AssistantWaitingChangeRowProps>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useAssistantTr as e } from "../../i18n.js";
|
|
2
|
+
import { ShieldAlert as t } from "lucide-react";
|
|
3
|
+
import { jsx as n, jsxs as r } from "react/jsx-runtime";
|
|
4
|
+
//#region src/shared/assistant/ui/compact/AssistantWaitingChangeRow.tsx
|
|
5
|
+
var i = ({ count: i, onReview: a }) => {
|
|
6
|
+
let o = e();
|
|
7
|
+
return /* @__PURE__ */ r("div", {
|
|
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
|
+
"data-testid": "assistant-waiting-change",
|
|
10
|
+
children: [
|
|
11
|
+
/* @__PURE__ */ n(t, {
|
|
12
|
+
className: "size-4 shrink-0 text-status-warning",
|
|
13
|
+
"aria-hidden": "true"
|
|
14
|
+
}),
|
|
15
|
+
/* @__PURE__ */ n("p", {
|
|
16
|
+
role: "status",
|
|
17
|
+
"aria-live": "polite",
|
|
18
|
+
className: "min-w-0 flex-1 text-sm text-text-primary",
|
|
19
|
+
"data-testid": "assistant-waiting-change-status",
|
|
20
|
+
children: o("waitingChange.status", "{{count}} change(s) waiting for your approval", { count: i })
|
|
21
|
+
}),
|
|
22
|
+
/* @__PURE__ */ n("button", {
|
|
23
|
+
type: "button",
|
|
24
|
+
onClick: a,
|
|
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"),
|
|
27
|
+
"data-testid": "assistant-waiting-change-review",
|
|
28
|
+
children: o("waitingChange.review", "Review")
|
|
29
|
+
})
|
|
30
|
+
]
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
//#endregion
|
|
34
|
+
export { i as AssistantWaitingChangeRow };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Conversation } from '../../conversation';
|
|
2
|
+
/**
|
|
3
|
+
* Expand/Restore is offered only from this viewport width up (N2, 2026-10-29).
|
|
4
|
+
*
|
|
5
|
+
* At 1536 px the page keeps at least 520 px beside the default sidebar once the
|
|
6
|
+
* assistant is 760 px wide (DESIGN-DELTA A2); at 1440 it would keep 424 px, so
|
|
7
|
+
* the control is not offered there. A viewport that is exactly this wide IS
|
|
8
|
+
* offered it: the rule is "at least".
|
|
9
|
+
*/
|
|
10
|
+
export declare const COMPACT_EXPAND_MIN_VIEWPORT_PX = 1536;
|
|
11
|
+
/** Is Expand/Restore offered at this viewport width? */
|
|
12
|
+
export declare function compactExpandOffered(viewportWidthPx: number): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Is Expand/Restore offered at the viewport's CURRENT width? Follows a resize.
|
|
15
|
+
* Never offered without a window (a server render): the control appears only
|
|
16
|
+
* where a width was actually read.
|
|
17
|
+
*
|
|
18
|
+
* ★ `enabled` is false for the classic presentation, which then listens to
|
|
19
|
+
* nothing and reads nothing: the hook has to be called either way.
|
|
20
|
+
*/
|
|
21
|
+
export declare function useCompactExpandOffered(enabled: boolean): boolean;
|
|
22
|
+
/** The ☰ view's screens: its own list, and the two sub-views opened from it. */
|
|
23
|
+
export type MenuViewName = 'root' | 'voice-language' | 'history';
|
|
24
|
+
/**
|
|
25
|
+
* What Escape does inside the ☰ view: a sub-view goes back to the list, and the
|
|
26
|
+
* list closes the view. One key press, one layer (LA-06).
|
|
27
|
+
*/
|
|
28
|
+
export declare function menuViewEscape(view: MenuViewName): 'back' | 'close';
|
|
29
|
+
/** The parts of the focused element `fieldOwnsEscape` reads. */
|
|
30
|
+
export interface EscapeFieldLike {
|
|
31
|
+
readonly tagName?: string;
|
|
32
|
+
readonly value?: unknown;
|
|
33
|
+
getAttribute(name: string): string | null;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Does the focused element handle Escape ITSELF, so the ☰ view must not also
|
|
37
|
+
* step back? Inside History: the rename box (Escape cancels the rename) and the
|
|
38
|
+
* search box WHEN IT HOLDS TEXT (Escape clears it). An empty search box, a
|
|
39
|
+
* filter radio or a button leave Escape to the layer.
|
|
40
|
+
*
|
|
41
|
+
* ★ The ☰ view's `EscapeLayer` runs in the document CAPTURE phase, before the
|
|
42
|
+
* field's own React handler, so without this one press did both: cancelled the
|
|
43
|
+
* rename or cleared the search AND dropped back to the ☰ list.
|
|
44
|
+
*/
|
|
45
|
+
export declare function fieldOwnsEscape(element: EscapeFieldLike | null): boolean;
|
|
46
|
+
/** What the composer reports about its draft (`AssistantComposerDraft`, the part N1 reads). */
|
|
47
|
+
export interface UnsentDraft {
|
|
48
|
+
readonly text: string;
|
|
49
|
+
readonly hasAttachments: boolean;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Is there something typed or attached that has not been sent? Whitespace alone
|
|
53
|
+
* is not a draft: it is the composer's own `hasDraft` rule, so Close does not
|
|
54
|
+
* ask about a box that looks empty.
|
|
55
|
+
*/
|
|
56
|
+
export declare function draftIsUnsent(draft: UnsentDraft): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* What a request to close the panel does (N1): close, or ask first.
|
|
59
|
+
*
|
|
60
|
+
* `confirmed` is true while the person's "Discard and close" is running, so the
|
|
61
|
+
* close that follows it is not asked about again.
|
|
62
|
+
*/
|
|
63
|
+
export declare function closeRequestAction(args: {
|
|
64
|
+
compact: boolean;
|
|
65
|
+
unsent: boolean;
|
|
66
|
+
confirmed: boolean;
|
|
67
|
+
}): 'close' | 'ask';
|
|
68
|
+
/** How many conversations "Recent" lists. */
|
|
69
|
+
export declare const COMPACT_RECENT_LIMIT = 5;
|
|
70
|
+
/**
|
|
71
|
+
* The conversations "Recent" lists: the newest first, by last activity, with
|
|
72
|
+
* nothing in them left out (an empty draft is not something to go back to).
|
|
73
|
+
* The open conversation stays in the list, marked by the caller.
|
|
74
|
+
*/
|
|
75
|
+
export declare function recentConversations(conversations: readonly Conversation[], limit?: number): Conversation[];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useSyncExternalStore as e } from "react";
|
|
2
|
+
//#region src/shared/assistant/ui/compact/compactChrome.ts
|
|
3
|
+
var t = 1536;
|
|
4
|
+
function n(e) {
|
|
5
|
+
return e >= t;
|
|
6
|
+
}
|
|
7
|
+
function r(e) {
|
|
8
|
+
return typeof window > "u" ? () => {} : (window.addEventListener("resize", e), () => window.removeEventListener("resize", e));
|
|
9
|
+
}
|
|
10
|
+
var i = () => () => {}, a = () => typeof window < "u" && n(window.innerWidth), o = () => !1;
|
|
11
|
+
function s(t) {
|
|
12
|
+
return e(t ? r : i, t ? a : o, o);
|
|
13
|
+
}
|
|
14
|
+
function c(e) {
|
|
15
|
+
return e === "root" ? "close" : "back";
|
|
16
|
+
}
|
|
17
|
+
function l(e) {
|
|
18
|
+
if (!e || e.tagName !== "INPUT") return !1;
|
|
19
|
+
let t = e.getAttribute("type");
|
|
20
|
+
return t !== null && t !== "text" && t !== "search" ? !1 : e.getAttribute("role") === "searchbox" ? typeof e.value == "string" && e.value !== "" : !0;
|
|
21
|
+
}
|
|
22
|
+
function u(e) {
|
|
23
|
+
return e.text.trim() !== "" || e.hasAttachments;
|
|
24
|
+
}
|
|
25
|
+
function d(e) {
|
|
26
|
+
return e.compact && e.unsent && !e.confirmed ? "ask" : "close";
|
|
27
|
+
}
|
|
28
|
+
var f = 5;
|
|
29
|
+
function p(e, t = 5) {
|
|
30
|
+
return e.filter((e) => e.messages.length > 0).map((e, t) => ({
|
|
31
|
+
conversation: e,
|
|
32
|
+
index: t
|
|
33
|
+
})).sort((e, t) => {
|
|
34
|
+
let n = Date.parse(t.conversation.updatedAt) - Date.parse(e.conversation.updatedAt);
|
|
35
|
+
return Number.isNaN(n) || n === 0 ? e.index - t.index : n;
|
|
36
|
+
}).slice(0, t).map((e) => e.conversation);
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
export { t as COMPACT_EXPAND_MIN_VIEWPORT_PX, f as COMPACT_RECENT_LIMIT, d as closeRequestAction, n as compactExpandOffered, u as draftIsUnsent, l as fieldOwnsEscape, c as menuViewEscape, p as recentConversations, s as useCompactExpandOffered };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pure half of the compact "change waiting" row (N6, BOFF-10177): how many
|
|
3
|
+
* changes wait for the person's approval, and how Review reaches the card.
|
|
4
|
+
*
|
|
5
|
+
* ★ The row is a POINTER to the existing approval card and nothing else. These
|
|
6
|
+
* two functions count and find; neither approves, declines or withdraws.
|
|
7
|
+
*/
|
|
8
|
+
/** What this reads of one proposal entry (`WriteProposalEntry`). */
|
|
9
|
+
export interface WaitingChangeEntry {
|
|
10
|
+
readonly session: {
|
|
11
|
+
readonly conversationId: string;
|
|
12
|
+
readonly sandboxId: string;
|
|
13
|
+
};
|
|
14
|
+
readonly view: {
|
|
15
|
+
readonly status: string;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* How many changes wait in the open conversation. The rule of
|
|
20
|
+
* `writeProposalPanel.liveFor` (pending, and not in a replaced sandbox), read
|
|
21
|
+
* from the entries so it can be recomputed on every arrival and decision.
|
|
22
|
+
*
|
|
23
|
+
* ★ Only changes with a card ON THE PAGE are counted (`cardProposalIds`: the
|
|
24
|
+
* proposals the typed thread's replies reference). The row points at a card; a
|
|
25
|
+
* change a SPOKEN turn left behind has none once the voice session ends (the
|
|
26
|
+
* thread omits spoken turns and the transcript draws no cards), so counting it
|
|
27
|
+
* would show a Review that finds nothing. Omitted, every pending change counts.
|
|
28
|
+
*/
|
|
29
|
+
export declare function countWaitingChanges(entries: Readonly<Record<string, WaitingChangeEntry>>, conversationId: string | null, retiredSandboxes: ReadonlySet<string>, cardProposalIds?: ReadonlySet<string>): number;
|
|
30
|
+
/** A card waiting for a decision: the existing card's own markers. */
|
|
31
|
+
export declare const WAITING_CARD_SELECTOR = "[data-testid=\"assistant-write-proposal\"][data-status=\"pending\"]";
|
|
32
|
+
/** The slice of an element Review uses. */
|
|
33
|
+
interface ReviewableCard {
|
|
34
|
+
closest(selector: string): unknown;
|
|
35
|
+
hasAttribute(name: string): boolean;
|
|
36
|
+
setAttribute(name: string, value: string): void;
|
|
37
|
+
scrollIntoView(options?: ScrollIntoViewOptions): void;
|
|
38
|
+
focus(options?: FocusOptions): void;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Scroll to the first waiting card under `root` and focus it. A replaced
|
|
42
|
+
* sandbox's card is inert and is skipped. The card is a `group` that cannot take
|
|
43
|
+
* focus by itself, so it is made a programmatic focus target first
|
|
44
|
+
* (`tabindex="-1"`, which adds no Tab stop). Returns whether a card was found.
|
|
45
|
+
*/
|
|
46
|
+
export declare function focusFirstWaitingCard(root: {
|
|
47
|
+
querySelectorAll(selector: string): ArrayLike<ReviewableCard>;
|
|
48
|
+
} | null): boolean;
|
|
49
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region src/shared/assistant/ui/compact/waitingChange.ts
|
|
2
|
+
function e(e, t, n, r) {
|
|
3
|
+
return t === null ? 0 : Object.entries(e).filter(([e, i]) => i.session.conversationId === t && i.view.status === "pending" && !n.has(i.session.sandboxId) && (r === void 0 || r.has(e))).length;
|
|
4
|
+
}
|
|
5
|
+
var t = "[data-testid=\"assistant-write-proposal\"][data-status=\"pending\"]";
|
|
6
|
+
function n(e) {
|
|
7
|
+
if (!e) return !1;
|
|
8
|
+
let n = Array.from(e.querySelectorAll(t)).find((e) => !e.closest("[inert]"));
|
|
9
|
+
return n ? (n.scrollIntoView({ block: "center" }), n.hasAttribute("tabindex") || n.setAttribute("tabindex", "-1"), n.focus({ preventScroll: !0 }), !0) : !1;
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
12
|
+
export { t as WAITING_CARD_SELECTOR, e as countWaitingChanges, n as focusFirstWaitingCard };
|