@green-on-grey/ui-kit 0.1.4 → 1.0.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/README.md CHANGED
@@ -24,7 +24,7 @@ const resources = { en: { translation: en, uikit: kit.en }, ru: { translation: r
24
24
 
25
25
  ```tsx
26
26
  import { Avatar, Icon, Modal, ToastProvider, TopBar, useToast } from "@green-on-grey/ui-kit";
27
- import { Chat, ChatLayout, ConversationList } from "@green-on-grey/ui-kit/chat";
27
+ import { AgentButton, AgentMenu, Chat, ChatLayout, ConversationList } from "@green-on-grey/ui-kit/chat";
28
28
  import { LoginScreen } from "@green-on-grey/ui-kit/login";
29
29
  ```
30
30
 
@@ -0,0 +1,36 @@
1
+ import { type ReactNode } from "react";
2
+ import { type IconName } from "../components/Icon";
3
+ /** One of the ways an agent can look and talk; what it means only the product knows (R7). */
4
+ export interface AgentOption {
5
+ key: string;
6
+ label: string;
7
+ icon: IconName;
8
+ }
9
+ /** The round button of the agent: its icon, and the menu behind it. */
10
+ export declare function AgentButton({ icon, label, title, expanded, small, disabled, onClick }: {
11
+ icon: IconName;
12
+ label: string;
13
+ title?: string;
14
+ expanded?: boolean;
15
+ small?: boolean;
16
+ disabled?: boolean;
17
+ onClick?: () => void;
18
+ }): import("react").JSX.Element;
19
+ /** The menu of the agent (FTR.NAB.CMN-0005 R15, design §6.6): its name and a
20
+ * choice among the options of the product, as tiles with icons. The name is
21
+ * saved on Enter and on leaving the field; a pick is saved at once. */
22
+ export declare function AgentMenu({ label, name, options, value, optionsLabel, hint, narrow, onRename, onPick, onClose }: {
23
+ /** The name of the dialog for a screen reader. */
24
+ label: string;
25
+ name: string;
26
+ options: AgentOption[];
27
+ value: string;
28
+ optionsLabel: ReactNode;
29
+ hint?: ReactNode;
30
+ /** A narrow place — a side panel: the options go in one column. */
31
+ narrow?: boolean;
32
+ onRename: (name: string) => void;
33
+ onPick: (key: string) => void;
34
+ /** Must keep its identity between renders. */
35
+ onClose: () => void;
36
+ }): import("react").JSX.Element;
@@ -1,4 +1,5 @@
1
1
  export { Chat } from "./Chat";
2
2
  export { ChatLayout, ConversationList, NewTopicModal } from "./ConversationList";
3
+ export { AgentButton, AgentMenu, type AgentOption } from "./AgentMenu";
3
4
  export { useRecorder } from "./useRecorder";
4
5
  export type { ChatAttachment, ChatConfirmation, ChatConversation, ChatMark, ChatMessage, ChatProps, ChatStep, ChatUpload, ChatWait, ConversationListProps, } from "./types";
package/dist/chat.js CHANGED
@@ -1,20 +1,20 @@
1
- import { c as e, d as t, i as n, l as r } from "./ui-CGgZtEVm.js";
2
- import { n as i, t as a } from "./i18n-BJ8rsBp6.js";
3
- import { Fragment as o, jsx as s, jsxs as c } from "react/jsx-runtime";
4
- import { useCallback as l, useEffect as u, useLayoutEffect as d, useRef as f, useState as p } from "react";
5
- import { useTranslation as m } from "react-i18next";
1
+ import { c as e, d as t, i as n, l as r, s as i } from "./ui-CGgZtEVm.js";
2
+ import { n as a, t as o } from "./i18n-BJ8rsBp6.js";
3
+ import { Fragment as s, jsx as c, jsxs as l } from "react/jsx-runtime";
4
+ import { useCallback as u, useEffect as d, useLayoutEffect as f, useRef as p, useState as m } from "react";
5
+ import { useTranslation as h } from "react-i18next";
6
6
  //#region src/chat/MessageView.tsx
7
- var h = (e) => e === "zh" ? "zh-CN" : e;
8
- function g(e, t) {
9
- return new Intl.DateTimeFormat(h(t), {
7
+ var g = (e) => e === "zh" ? "zh-CN" : e;
8
+ function _(e, t) {
9
+ return new Intl.DateTimeFormat(g(t), {
10
10
  hour: "2-digit",
11
11
  minute: "2-digit"
12
12
  }).format(new Date(e));
13
13
  }
14
- function _(e, t) {
14
+ function v(e, t) {
15
15
  return t || e.length <= 3 ? e : e.slice(-2);
16
16
  }
17
- var v = {
17
+ var y = {
18
18
  done: {
19
19
  icon: "check",
20
20
  cls: "ok"
@@ -28,30 +28,30 @@ var v = {
28
28
  cls: "run"
29
29
  }
30
30
  };
31
- function y({ m: e, busy: n, onRetry: i }) {
32
- let { t: l, i18n: u } = m(a), [d, f] = p(!1);
33
- if (e.role === "note") return /* @__PURE__ */ s("div", {
31
+ function b({ m: e, busy: n, onRetry: i }) {
32
+ let { t: a, i18n: u } = h(o), [d, f] = m(!1);
33
+ if (e.role === "note") return /* @__PURE__ */ c("div", {
34
34
  className: "sysnote",
35
35
  children: e.text
36
36
  });
37
- let h = e.mark && /* @__PURE__ */ c("div", {
37
+ let p = e.mark && /* @__PURE__ */ l("div", {
38
38
  className: "chan",
39
39
  children: [
40
- /* @__PURE__ */ s(t, {
40
+ /* @__PURE__ */ c(t, {
41
41
  name: e.mark.icon,
42
42
  size: 12
43
43
  }),
44
44
  " ",
45
45
  e.mark.text,
46
46
  " · ",
47
- g(e.createdAt, u.language)
47
+ _(e.createdAt, u.language)
48
48
  ]
49
- }), y = e.attachments && e.attachments.length > 0 && /* @__PURE__ */ s("div", {
49
+ }), g = e.attachments && e.attachments.length > 0 && /* @__PURE__ */ c("div", {
50
50
  className: "atts",
51
- children: e.attachments.map((e) => /* @__PURE__ */ c("a", {
51
+ children: e.attachments.map((e) => /* @__PURE__ */ l("a", {
52
52
  href: e.href,
53
53
  children: [
54
- /* @__PURE__ */ s(t, {
54
+ /* @__PURE__ */ c(t, {
55
55
  name: "clip",
56
56
  size: 12
57
57
  }),
@@ -60,83 +60,83 @@ function y({ m: e, busy: n, onRetry: i }) {
60
60
  ]
61
61
  }, e.id))
62
62
  });
63
- if (e.role === "user") return /* @__PURE__ */ c("div", {
63
+ if (e.role === "user") return /* @__PURE__ */ l("div", {
64
64
  className: "m u",
65
65
  children: [
66
- h,
66
+ p,
67
67
  e.content,
68
- e.text && /* @__PURE__ */ s("div", {
68
+ e.text && /* @__PURE__ */ c("div", {
69
69
  className: "bub",
70
70
  children: e.text
71
71
  }),
72
- y,
73
- e.wait && /* @__PURE__ */ s("div", {
72
+ g,
73
+ e.wait && /* @__PURE__ */ c("div", {
74
74
  className: `turnstate${e.wait.state === "queued" ? " q" : ""}`,
75
75
  role: "status",
76
- children: e.wait.state === "queued" ? /* @__PURE__ */ c(o, { children: [/* @__PURE__ */ s(t, {
76
+ children: e.wait.state === "queued" ? /* @__PURE__ */ l(s, { children: [/* @__PURE__ */ c(t, {
77
77
  name: "clock",
78
78
  size: 12
79
- }), l("chat.wait.queued", { count: e.wait.position })] }) : /* @__PURE__ */ c(o, { children: [/* @__PURE__ */ s("span", {
79
+ }), a("chat.wait.queued", { count: e.wait.position })] }) : /* @__PURE__ */ l(s, { children: [/* @__PURE__ */ c("span", {
80
80
  className: "spin",
81
81
  "aria-hidden": "true"
82
- }), l("chat.wait.starting")] })
82
+ }), a("chat.wait.starting")] })
83
83
  })
84
84
  ]
85
85
  });
86
86
  let b = e.steps ?? [], x = (e.status === "streaming" || e.status === "pending") && !e.text && !e.content;
87
- return /* @__PURE__ */ c("div", {
87
+ return /* @__PURE__ */ l("div", {
88
88
  className: "m a",
89
89
  children: [
90
- h,
91
- b.length > 3 && /* @__PURE__ */ s("button", {
90
+ p,
91
+ b.length > 3 && /* @__PURE__ */ c("button", {
92
92
  className: "tools-fold",
93
93
  onClick: () => f((e) => !e),
94
94
  "aria-expanded": d,
95
- children: d ? l("chat.hideSteps") : l("chat.moreSteps", { count: b.length - 2 })
95
+ children: d ? a("chat.hideSteps") : a("chat.moreSteps", { count: b.length - 2 })
96
96
  }),
97
- _(b, d).map((e) => /* @__PURE__ */ c("div", {
97
+ v(b, d).map((e) => /* @__PURE__ */ l("div", {
98
98
  className: "tool",
99
99
  title: e.title,
100
- children: [/* @__PURE__ */ s(t, {
101
- name: v[e.status].icon,
100
+ children: [/* @__PURE__ */ c(t, {
101
+ name: y[e.status].icon,
102
102
  size: 14,
103
- className: v[e.status].cls
104
- }), /* @__PURE__ */ s("span", {
103
+ className: y[e.status].cls
104
+ }), /* @__PURE__ */ c("span", {
105
105
  className: "tx",
106
106
  children: e.text
107
107
  })]
108
108
  }, e.id)),
109
- x && /* @__PURE__ */ s("div", {
109
+ x && /* @__PURE__ */ c("div", {
110
110
  className: "bub",
111
- children: /* @__PURE__ */ c("span", {
111
+ children: /* @__PURE__ */ l("span", {
112
112
  className: "typing",
113
- "aria-label": l("chat.typing"),
113
+ "aria-label": a("chat.typing"),
114
114
  children: [
115
- /* @__PURE__ */ s("i", {}),
116
- /* @__PURE__ */ s("i", {}),
117
- /* @__PURE__ */ s("i", {})
115
+ /* @__PURE__ */ c("i", {}),
116
+ /* @__PURE__ */ c("i", {}),
117
+ /* @__PURE__ */ c("i", {})
118
118
  ]
119
119
  })
120
120
  }),
121
- e.content ?? (e.text && /* @__PURE__ */ s("div", {
121
+ e.content ?? (e.text && /* @__PURE__ */ c("div", {
122
122
  className: "bub",
123
- children: /* @__PURE__ */ s(r, { text: e.text })
123
+ children: /* @__PURE__ */ c(r, { text: e.text })
124
124
  })),
125
- y,
126
- e.error && /* @__PURE__ */ c("div", {
125
+ g,
126
+ e.error && /* @__PURE__ */ l("div", {
127
127
  className: `llmerr${e.error.transient ? " amber" : ""}`,
128
128
  role: "alert",
129
129
  style: { marginTop: 6 },
130
- children: [/* @__PURE__ */ s("span", { children: e.error.text }), i && /* @__PURE__ */ s("div", {
130
+ children: [/* @__PURE__ */ c("span", { children: e.error.text }), i && /* @__PURE__ */ c("div", {
131
131
  className: "row",
132
- children: /* @__PURE__ */ c("button", {
132
+ children: /* @__PURE__ */ l("button", {
133
133
  className: "btn sm",
134
134
  disabled: n,
135
135
  onClick: i,
136
- children: [/* @__PURE__ */ s(t, {
136
+ children: [/* @__PURE__ */ c(t, {
137
137
  name: "refresh",
138
138
  size: 14
139
- }), l("chat.retry")]
139
+ }), a("chat.retry")]
140
140
  })
141
141
  })]
142
142
  })
@@ -145,30 +145,30 @@ function y({ m: e, busy: n, onRetry: i }) {
145
145
  }
146
146
  //#endregion
147
147
  //#region src/chat/useRecorder.ts
148
- function b() {
149
- let e = typeof window < "u" && "MediaRecorder" in window && !!navigator.mediaDevices?.getUserMedia, [t, n] = p(!1), [r, i] = p(0), [a, o] = p(!1), s = f(null), c = f([]), d = f(void 0), m = f(null), h = l(() => {
150
- window.clearInterval(d.current), s.current?.stream.getTracks().forEach((e) => e.stop()), s.current = null, n(!1);
151
- }, []), g = l(async () => {
148
+ function x() {
149
+ let e = typeof window < "u" && "MediaRecorder" in window && !!navigator.mediaDevices?.getUserMedia, [t, n] = m(!1), [r, i] = m(0), [a, o] = m(!1), s = p(null), c = p([]), l = p(void 0), f = p(null), h = u(() => {
150
+ window.clearInterval(l.current), s.current?.stream.getTracks().forEach((e) => e.stop()), s.current = null, n(!1);
151
+ }, []), g = u(async () => {
152
152
  if (e && !s.current) {
153
153
  o(!1);
154
154
  try {
155
155
  let e = await navigator.mediaDevices.getUserMedia({ audio: !0 }), t = new MediaRecorder(e);
156
156
  c.current = [], t.ondataavailable = (e) => e.data.size > 0 && c.current.push(e.data), t.onstop = () => {
157
157
  let e = c.current.length ? new Blob(c.current, { type: t.mimeType || "audio/webm" }) : null;
158
- h(), m.current?.(e), m.current = null;
158
+ h(), f.current?.(e), f.current = null;
159
159
  }, s.current = t, t.start(), i(0), n(!0);
160
160
  let r = Date.now();
161
- d.current = window.setInterval(() => i(Math.floor((Date.now() - r) / 1e3)), 250);
161
+ l.current = window.setInterval(() => i(Math.floor((Date.now() - r) / 1e3)), 250);
162
162
  } catch {
163
163
  o(!0), h();
164
164
  }
165
165
  }
166
- }, [e, h]), _ = l(() => new Promise((e) => {
166
+ }, [e, h]), _ = u(() => new Promise((e) => {
167
167
  let t = s.current;
168
168
  if (!t || t.state === "inactive") return e(null);
169
- m.current = e, t.stop();
169
+ f.current = e, t.stop();
170
170
  }), []);
171
- return u(() => () => h(), [h]), {
171
+ return d(() => () => h(), [h]), {
172
172
  supported: e,
173
173
  recording: t,
174
174
  seconds: `${Math.floor(r / 60)}:${String(r % 60).padStart(2, "0")}`,
@@ -179,9 +179,9 @@ function b() {
179
179
  }
180
180
  //#endregion
181
181
  //#region src/chat/Chat.tsx
182
- function x(n) {
183
- let r = i(), a = e(), [l, u] = p(""), [m, h] = p([]), [g, _] = p(!1), [v, x] = p(null), S = f(null), C = f(null), w = b(), { messages: T, busy: E = !1 } = n, D = T[0]?.id, O = T[T.length - 1], k = O?.id, A = O?.text.length, j = O?.steps?.length, M = f({ height: 0 });
184
- d(() => {
182
+ function S(n) {
183
+ let r = a(), i = e(), [o, u] = m(""), [d, h] = m([]), [g, _] = m(!1), [v, y] = m(null), S = p(null), C = p(null), w = x(), { messages: T, busy: E = !1 } = n, D = T[0]?.id, O = T[T.length - 1], k = O?.id, A = O?.text.length, j = O?.steps?.length, M = p({ height: 0 });
184
+ f(() => {
185
185
  let e = S.current;
186
186
  if (!e) return;
187
187
  let t = M.current;
@@ -199,15 +199,15 @@ function x(n) {
199
199
  ]);
200
200
  let N = async (e, t) => {
201
201
  let r = e.trim();
202
- if (!(!r && m.length === 0 || E || n.readOnly)) try {
203
- await n.onSend(r, m.map((e) => e.id), t), u(""), h([]), x(null);
202
+ if (!(!r && d.length === 0 || E || n.readOnly)) try {
203
+ await n.onSend(r, d.map((e) => e.id), t), u(""), h([]), y(null);
204
204
  } catch {}
205
205
  }, P = async (e) => {
206
206
  if (e?.length && n.onUpload) {
207
207
  _(!0);
208
208
  for (let t of Array.from(e)) {
209
209
  if (n.upload && t.size > n.upload.maxBytes) {
210
- a({
210
+ i({
211
211
  kind: "error",
212
212
  title: r("chat.tooLarge"),
213
213
  text: t.name
@@ -223,71 +223,71 @@ function x(n) {
223
223
  let e = await w.stop();
224
224
  if (!e || !n.onTranscribe) return;
225
225
  let t = await n.onTranscribe(e).catch(() => null);
226
- t !== null && (t ? x(t) : a({
226
+ t !== null && (t ? y(t) : i({
227
227
  kind: "error",
228
228
  title: r("chat.voice.notRecognized")
229
229
  }));
230
230
  }, I = (e) => {
231
231
  (e.key === " " || e.key === "Enter") && (e.preventDefault(), !e.repeat && (w.recording ? F() : w.start()));
232
232
  }, L = r("chat.placeholder", { name: n.agentName });
233
- return /* @__PURE__ */ c("section", {
233
+ return /* @__PURE__ */ l("section", {
234
234
  className: "nb-chat",
235
235
  "aria-label": r("chat.title"),
236
236
  children: [
237
237
  n.header,
238
- /* @__PURE__ */ c("div", {
238
+ /* @__PURE__ */ l("div", {
239
239
  className: "msgs",
240
240
  ref: S,
241
241
  "aria-live": "polite",
242
242
  children: [
243
- n.hasOlder && n.onLoadOlder && /* @__PURE__ */ s("button", {
243
+ n.hasOlder && n.onLoadOlder && /* @__PURE__ */ c("button", {
244
244
  className: "btn ghost sm",
245
245
  style: { alignSelf: "center" },
246
246
  disabled: n.loadingOlder,
247
247
  onClick: n.onLoadOlder,
248
248
  children: r("chat.loadOlder")
249
249
  }),
250
- n.historyError && /* @__PURE__ */ s("div", {
250
+ n.historyError && /* @__PURE__ */ c("div", {
251
251
  className: "err-text",
252
252
  children: n.historyError
253
253
  }),
254
- T.length === 0 && !n.loading && !n.historyError && /* @__PURE__ */ c("div", {
254
+ T.length === 0 && !n.loading && !n.historyError && /* @__PURE__ */ l("div", {
255
255
  className: "empty",
256
256
  children: [
257
- /* @__PURE__ */ s("div", {
257
+ /* @__PURE__ */ c("div", {
258
258
  className: "ic",
259
- children: /* @__PURE__ */ s(t, { name: "spark" })
259
+ children: /* @__PURE__ */ c(t, { name: "spark" })
260
260
  }),
261
- /* @__PURE__ */ s("b", { children: r("chat.emptyTitle", { name: n.agentName }) }),
261
+ /* @__PURE__ */ c("b", { children: r("chat.emptyTitle", { name: n.agentName }) }),
262
262
  n.emptyHint
263
263
  ]
264
264
  }),
265
- T.map((e) => /* @__PURE__ */ s(y, {
265
+ T.map((e) => /* @__PURE__ */ c(b, {
266
266
  m: e,
267
267
  busy: E,
268
268
  onRetry: n.onRetry && (() => n.onRetry?.(e.id))
269
269
  }, e.id)),
270
- n.onConfirm && (n.confirmations ?? []).map((e) => /* @__PURE__ */ c("div", {
270
+ n.onConfirm && (n.confirmations ?? []).map((e) => /* @__PURE__ */ l("div", {
271
271
  className: "confirm",
272
272
  role: "group",
273
273
  "aria-label": r("chat.confirm.title"),
274
274
  children: [
275
- /* @__PURE__ */ c("div", {
275
+ /* @__PURE__ */ l("div", {
276
276
  className: "h",
277
- children: [/* @__PURE__ */ s(t, {
277
+ children: [/* @__PURE__ */ c(t, {
278
278
  name: "alert",
279
279
  size: 16
280
280
  }), r("chat.confirm.title")]
281
281
  }),
282
- /* @__PURE__ */ s("div", { children: r("chat.confirm.text", { action: e.summary }) }),
283
- e.preview !== void 0 && /* @__PURE__ */ s("pre", { children: e.preview }),
284
- /* @__PURE__ */ c("div", {
282
+ /* @__PURE__ */ c("div", { children: r("chat.confirm.text", { action: e.summary }) }),
283
+ e.preview !== void 0 && /* @__PURE__ */ c("pre", { children: e.preview }),
284
+ /* @__PURE__ */ l("div", {
285
285
  className: "acts",
286
- children: [/* @__PURE__ */ s("button", {
286
+ children: [/* @__PURE__ */ c("button", {
287
287
  className: "btn sm primary",
288
288
  onClick: () => n.onConfirm?.(e.id, !0),
289
289
  children: r("chat.confirm.approve")
290
- }), /* @__PURE__ */ s("button", {
290
+ }), /* @__PURE__ */ c("button", {
291
291
  className: "btn sm",
292
292
  onClick: () => n.onConfirm?.(e.id, !1),
293
293
  children: r("chat.confirm.reject")
@@ -297,26 +297,26 @@ function x(n) {
297
297
  }, e.id))
298
298
  ]
299
299
  }),
300
- n.readOnly ? /* @__PURE__ */ s("div", {
300
+ n.readOnly ? /* @__PURE__ */ c("div", {
301
301
  className: "composer",
302
302
  style: {
303
303
  justifyContent: "space-between",
304
304
  alignItems: "center"
305
305
  },
306
306
  children: n.readOnly
307
- }) : v === null ? /* @__PURE__ */ c(o, { children: [
307
+ }) : v === null ? /* @__PURE__ */ l(s, { children: [
308
308
  n.aboveComposer,
309
- m.length > 0 && /* @__PURE__ */ s("div", {
309
+ d.length > 0 && /* @__PURE__ */ c("div", {
310
310
  className: "pending-files",
311
- children: m.map((e) => /* @__PURE__ */ c("span", {
311
+ children: d.map((e) => /* @__PURE__ */ l("span", {
312
312
  className: "chip",
313
313
  children: [
314
- /* @__PURE__ */ s(t, {
314
+ /* @__PURE__ */ c(t, {
315
315
  name: "clip",
316
316
  size: 12
317
317
  }),
318
318
  e.fileName,
319
- /* @__PURE__ */ s("button", {
319
+ /* @__PURE__ */ c("button", {
320
320
  className: "iconbtn",
321
321
  style: {
322
322
  width: 18,
@@ -324,7 +324,7 @@ function x(n) {
324
324
  },
325
325
  "aria-label": r("remove"),
326
326
  onClick: () => h((t) => t.filter((t) => t.id !== e.id)),
327
- children: /* @__PURE__ */ s(t, {
327
+ children: /* @__PURE__ */ c(t, {
328
328
  name: "x",
329
329
  size: 12
330
330
  })
@@ -332,16 +332,16 @@ function x(n) {
332
332
  ]
333
333
  }, e.id))
334
334
  }),
335
- /* @__PURE__ */ c("div", {
335
+ /* @__PURE__ */ l("div", {
336
336
  className: "composer",
337
337
  children: [
338
- n.onUpload && /* @__PURE__ */ c(o, { children: [/* @__PURE__ */ s("button", {
338
+ n.onUpload && /* @__PURE__ */ l(s, { children: [/* @__PURE__ */ c("button", {
339
339
  className: "iconbtn",
340
340
  "aria-label": r("chat.attach"),
341
341
  disabled: g,
342
342
  onClick: () => C.current?.click(),
343
- children: /* @__PURE__ */ s(t, { name: "clip" })
344
- }), /* @__PURE__ */ s("input", {
343
+ children: /* @__PURE__ */ c(t, { name: "clip" })
344
+ }), /* @__PURE__ */ c("input", {
345
345
  ref: C,
346
346
  type: "file",
347
347
  multiple: !0,
@@ -349,20 +349,20 @@ function x(n) {
349
349
  accept: n.upload?.accept,
350
350
  onChange: (e) => P(e.target.files)
351
351
  })] }),
352
- w.recording ? /* @__PURE__ */ s("div", {
352
+ w.recording ? /* @__PURE__ */ c("div", {
353
353
  className: "rec-box",
354
354
  children: r("chat.voice.recording", { seconds: w.seconds })
355
- }) : /* @__PURE__ */ s("textarea", {
355
+ }) : /* @__PURE__ */ c("textarea", {
356
356
  rows: 1,
357
- value: l,
357
+ value: o,
358
358
  placeholder: L,
359
359
  "aria-label": L,
360
360
  onChange: (e) => u(e.target.value),
361
361
  onKeyDown: (e) => {
362
- e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing && (e.preventDefault(), N(l, !1));
362
+ e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing && (e.preventDefault(), N(o, !1));
363
363
  }
364
364
  }),
365
- n.onTranscribe && /* @__PURE__ */ s("button", {
365
+ n.onTranscribe && /* @__PURE__ */ c("button", {
366
366
  className: `iconbtn${w.recording ? " rec" : ""}`,
367
367
  disabled: !w.supported,
368
368
  "aria-label": w.recording ? r("chat.voice.stop") : r("chat.voice.hold"),
@@ -373,51 +373,51 @@ function x(n) {
373
373
  onPointerUp: () => w.recording && F(),
374
374
  onPointerLeave: () => w.recording && F(),
375
375
  onKeyDown: I,
376
- children: /* @__PURE__ */ s(t, { name: "mic" })
376
+ children: /* @__PURE__ */ c(t, { name: "mic" })
377
377
  }),
378
- n.onCancel && E ? /* @__PURE__ */ s("button", {
378
+ n.onCancel && E ? /* @__PURE__ */ c("button", {
379
379
  className: "iconbtn fill",
380
380
  "aria-label": r("chat.stop"),
381
381
  onClick: n.onCancel,
382
- children: /* @__PURE__ */ s(t, { name: "stop" })
383
- }) : /* @__PURE__ */ s("button", {
382
+ children: /* @__PURE__ */ c(t, { name: "stop" })
383
+ }) : /* @__PURE__ */ c("button", {
384
384
  className: "iconbtn fill",
385
385
  "aria-label": r("chat.send"),
386
- disabled: !l.trim() && m.length === 0 || E,
387
- onClick: () => N(l, !1),
388
- children: /* @__PURE__ */ s(t, { name: "send" })
386
+ disabled: !o.trim() && d.length === 0 || E,
387
+ onClick: () => N(o, !1),
388
+ children: /* @__PURE__ */ c(t, { name: "send" })
389
389
  })
390
390
  ]
391
391
  }),
392
- w.error && /* @__PURE__ */ s("div", {
392
+ w.error && /* @__PURE__ */ c("div", {
393
393
  className: "err-text",
394
394
  style: { padding: "0 12px 8px" },
395
395
  children: r("chat.voice.micDenied")
396
396
  })
397
- ] }) : /* @__PURE__ */ c("div", {
397
+ ] }) : /* @__PURE__ */ l("div", {
398
398
  className: "transcript",
399
399
  style: { margin: 10 },
400
400
  children: [
401
- /* @__PURE__ */ s("div", {
401
+ /* @__PURE__ */ c("div", {
402
402
  className: "small muted",
403
403
  style: { marginBottom: 4 },
404
404
  children: r("chat.voice.review")
405
405
  }),
406
- /* @__PURE__ */ s("textarea", {
406
+ /* @__PURE__ */ c("textarea", {
407
407
  className: "inp",
408
408
  rows: 3,
409
409
  value: v,
410
- onChange: (e) => x(e.target.value),
410
+ onChange: (e) => y(e.target.value),
411
411
  "aria-label": r("chat.voice.review")
412
412
  }),
413
- /* @__PURE__ */ c("div", {
413
+ /* @__PURE__ */ l("div", {
414
414
  className: "row2",
415
415
  style: { marginTop: 8 },
416
- children: [/* @__PURE__ */ s("button", {
416
+ children: [/* @__PURE__ */ c("button", {
417
417
  className: "btn ghost sm",
418
- onClick: () => x(null),
418
+ onClick: () => y(null),
419
419
  children: r("delete")
420
- }), /* @__PURE__ */ s("button", {
420
+ }), /* @__PURE__ */ c("button", {
421
421
  className: "btn primary sm",
422
422
  disabled: !v.trim() || E,
423
423
  onClick: () => N(v, !0),
@@ -431,145 +431,217 @@ function x(n) {
431
431
  }
432
432
  //#endregion
433
433
  //#region src/chat/ConversationList.tsx
434
- function S({ conversations: e, currentId: n, open: r, onPick: a, onClose: l, onCreate: d }) {
435
- let f = i(), [m, h] = p(!1);
436
- u(() => {
434
+ function C({ conversations: e, currentId: n, open: r, onPick: i, onClose: o, onCreate: u }) {
435
+ let f = a(), [p, h] = m(!1);
436
+ d(() => {
437
437
  if (!r) return;
438
- let e = (e) => e.key === "Escape" && l();
438
+ let e = (e) => e.key === "Escape" && o();
439
439
  return document.addEventListener("keydown", e), () => document.removeEventListener("keydown", e);
440
- }, [r, l]);
441
- let g = (e) => /* @__PURE__ */ c("button", {
440
+ }, [r, o]);
441
+ let g = (e) => /* @__PURE__ */ l("button", {
442
442
  className: `it${e.id === n ? " on" : ""}${e.group === "archive" ? " arch" : ""}`,
443
- onClick: () => a(e.id),
443
+ onClick: () => i(e.id),
444
444
  "aria-current": e.id === n,
445
445
  children: [
446
- /* @__PURE__ */ s(t, {
446
+ /* @__PURE__ */ c(t, {
447
447
  name: e.icon ?? "msg2",
448
448
  size: 16
449
449
  }),
450
- /* @__PURE__ */ s("span", {
450
+ /* @__PURE__ */ c("span", {
451
451
  className: "nm",
452
452
  children: e.title
453
453
  }),
454
- !!e.unread && /* @__PURE__ */ s("span", {
454
+ !!e.unread && /* @__PURE__ */ c("span", {
455
455
  className: "unread",
456
456
  "aria-label": f("chat.unread", { count: e.unread }),
457
457
  children: e.unread
458
458
  }),
459
- /* @__PURE__ */ s("span", {
459
+ /* @__PURE__ */ c("span", {
460
460
  className: "sub",
461
461
  children: e.when
462
462
  })
463
463
  ]
464
464
  }, e.id), _ = (t) => e.filter((e) => e.group === t), v = _("topic"), y = _("archive");
465
- return /* @__PURE__ */ c(o, { children: [
466
- /* @__PURE__ */ c("nav", {
465
+ return /* @__PURE__ */ l(s, { children: [
466
+ /* @__PURE__ */ l("nav", {
467
467
  className: `nb-side${r ? " open" : ""}`,
468
468
  "aria-label": f("chat.conversations"),
469
469
  children: [
470
- d && /* @__PURE__ */ c("button", {
470
+ u && /* @__PURE__ */ l("button", {
471
471
  className: "btn sm",
472
472
  style: {
473
473
  width: "100%",
474
474
  justifyContent: "center"
475
475
  },
476
476
  onClick: () => h(!0),
477
- children: [/* @__PURE__ */ s(t, {
477
+ children: [/* @__PURE__ */ c(t, {
478
478
  name: "plus",
479
479
  size: 15
480
480
  }), f("chat.newTopic")]
481
481
  }),
482
- /* @__PURE__ */ s("div", {
482
+ /* @__PURE__ */ c("div", {
483
483
  className: "h",
484
484
  children: f("chat.conversation")
485
485
  }),
486
486
  _("main").map(g),
487
- /* @__PURE__ */ s("div", {
487
+ /* @__PURE__ */ c("div", {
488
488
  className: "h",
489
489
  children: f("chat.topics")
490
490
  }),
491
- v.length === 0 && /* @__PURE__ */ s("div", {
491
+ v.length === 0 && /* @__PURE__ */ c("div", {
492
492
  className: "small muted",
493
493
  style: { padding: "0 10px" },
494
494
  children: f("chat.noTopics")
495
495
  }),
496
496
  v.map(g),
497
- y.length > 0 && /* @__PURE__ */ s("div", {
497
+ y.length > 0 && /* @__PURE__ */ c("div", {
498
498
  className: "h",
499
499
  children: f("chat.archive")
500
500
  }),
501
501
  y.map(g)
502
502
  ]
503
503
  }),
504
- r && /* @__PURE__ */ s("button", {
504
+ r && /* @__PURE__ */ c("button", {
505
505
  type: "button",
506
506
  className: "nb-scrim",
507
507
  "aria-label": f("close"),
508
- onClick: l
508
+ onClick: o
509
509
  }),
510
- m && d && /* @__PURE__ */ s(w, {
510
+ p && u && /* @__PURE__ */ c(T, {
511
511
  onClose: () => h(!1),
512
- onCreate: d
512
+ onCreate: u
513
513
  })
514
514
  ] });
515
515
  }
516
- function C({ narrow: e, children: t }) {
517
- return /* @__PURE__ */ s("div", {
516
+ function w({ narrow: e, children: t }) {
517
+ return /* @__PURE__ */ c("div", {
518
518
  className: `nb-app${e ? " narrow" : ""}`,
519
519
  children: t
520
520
  });
521
521
  }
522
- function w({ onClose: e, onCreate: t }) {
523
- let r = i(), [a, l] = p(""), [u, d] = p(!1), [f, m] = p(null), h = async () => {
524
- if (a.trim() && !u) {
525
- d(!0), m(null);
522
+ function T({ onClose: e, onCreate: t }) {
523
+ let r = a(), [i, o] = m(""), [u, d] = m(!1), [f, p] = m(null), h = async () => {
524
+ if (i.trim() && !u) {
525
+ d(!0), p(null);
526
526
  try {
527
- await t(a.trim()), e();
527
+ await t(i.trim()), e();
528
528
  } catch (e) {
529
- m(e instanceof Error ? e.message : String(e)), d(!1);
529
+ p(e instanceof Error ? e.message : String(e)), d(!1);
530
530
  }
531
531
  }
532
532
  };
533
- return /* @__PURE__ */ c(n, {
533
+ return /* @__PURE__ */ l(n, {
534
534
  title: r("chat.newTopic"),
535
535
  onClose: e,
536
- footer: /* @__PURE__ */ c(o, { children: [/* @__PURE__ */ s("button", {
536
+ footer: /* @__PURE__ */ l(s, { children: [/* @__PURE__ */ c("button", {
537
537
  className: "btn ghost",
538
538
  onClick: e,
539
539
  children: r("cancel")
540
- }), /* @__PURE__ */ s("button", {
540
+ }), /* @__PURE__ */ c("button", {
541
541
  className: "btn primary",
542
- disabled: !a.trim() || u,
542
+ disabled: !i.trim() || u,
543
543
  onClick: h,
544
544
  children: r("create")
545
545
  })] }),
546
- children: [/* @__PURE__ */ c("div", {
546
+ children: [/* @__PURE__ */ l("div", {
547
547
  className: "field",
548
548
  children: [
549
- /* @__PURE__ */ s("label", {
549
+ /* @__PURE__ */ c("label", {
550
550
  htmlFor: "topic-title",
551
551
  children: r("chat.topicTitle")
552
552
  }),
553
- /* @__PURE__ */ s("input", {
553
+ /* @__PURE__ */ c("input", {
554
554
  id: "topic-title",
555
555
  className: "inp",
556
- value: a,
556
+ value: i,
557
557
  maxLength: 120,
558
- onChange: (e) => l(e.target.value),
558
+ onChange: (e) => o(e.target.value),
559
559
  onKeyDown: (e) => e.key === "Enter" && h()
560
560
  }),
561
- /* @__PURE__ */ s("div", {
561
+ /* @__PURE__ */ c("div", {
562
562
  className: "hint",
563
563
  children: r("chat.topicHint")
564
564
  })
565
565
  ]
566
- }), f && /* @__PURE__ */ s("div", {
566
+ }), f && /* @__PURE__ */ c("div", {
567
567
  className: "err-text",
568
568
  children: f
569
569
  })]
570
570
  });
571
571
  }
572
572
  //#endregion
573
- export { x as Chat, C as ChatLayout, S as ConversationList, w as NewTopicModal, b as useRecorder };
573
+ //#region src/chat/AgentMenu.tsx
574
+ function E({ icon: e, label: n, title: r, expanded: i, small: a, disabled: o, onClick: s }) {
575
+ return /* @__PURE__ */ c("button", {
576
+ type: "button",
577
+ className: `agentbtn${a ? " s" : ""}`,
578
+ disabled: o,
579
+ "aria-label": n,
580
+ "aria-expanded": i,
581
+ title: r,
582
+ onMouseDown: (e) => i && e.stopPropagation(),
583
+ onClick: s,
584
+ children: /* @__PURE__ */ c(t, {
585
+ name: e,
586
+ size: a ? 15 : 18
587
+ })
588
+ });
589
+ }
590
+ function D({ label: e, name: n, options: r, value: o, optionsLabel: s, hint: u, narrow: d, onRename: f, onPick: p, onClose: h }) {
591
+ let g = a(), [_, v] = m(n), y = i(!0, h);
592
+ return /* @__PURE__ */ l("div", {
593
+ className: `agentmenu${d ? " narrow" : ""}`,
594
+ ref: y,
595
+ role: "dialog",
596
+ "aria-label": e,
597
+ children: [
598
+ /* @__PURE__ */ c("div", {
599
+ className: "lab",
600
+ children: g("agentName")
601
+ }),
602
+ /* @__PURE__ */ c("input", {
603
+ className: "inp",
604
+ value: _,
605
+ maxLength: 40,
606
+ autoFocus: !0,
607
+ "aria-label": g("agentName"),
608
+ onChange: (e) => v(e.target.value),
609
+ onBlur: () => {
610
+ let e = _.trim();
611
+ e && e !== n && f(e);
612
+ },
613
+ onKeyDown: (e) => e.key === "Enter" && e.target.blur()
614
+ }),
615
+ /* @__PURE__ */ c("div", {
616
+ className: "lab",
617
+ style: { marginTop: 14 },
618
+ children: s
619
+ }),
620
+ /* @__PURE__ */ c("div", {
621
+ className: "agentoptions",
622
+ children: r.map((e) => /* @__PURE__ */ l("button", {
623
+ type: "button",
624
+ className: `agentoption${o === e.key ? " on" : ""}`,
625
+ "aria-pressed": o === e.key,
626
+ onClick: () => p(e.key),
627
+ children: [/* @__PURE__ */ c("span", {
628
+ className: "ic",
629
+ children: /* @__PURE__ */ c(t, {
630
+ name: e.icon,
631
+ size: 15
632
+ })
633
+ }), e.label]
634
+ }, e.key))
635
+ }),
636
+ u && /* @__PURE__ */ c("div", {
637
+ className: "hint",
638
+ style: { marginTop: 10 },
639
+ children: u
640
+ })
641
+ ]
642
+ });
643
+ }
644
+ //#endregion
645
+ export { E as AgentButton, D as AgentMenu, S as Chat, w as ChatLayout, C as ConversationList, T as NewTopicModal, x as useRecorder };
574
646
 
575
647
  //# sourceMappingURL=chat.js.map
package/dist/chat.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"chat.js","names":["_Fragment","_Fragment","_Fragment","useToast","Icon","Modal","Markdown","useKitT","UIKIT_NS","intlLocale","lng","markTime","language","iso","visibleSteps","open","steps","STEP","MessageView","useTranslation","setOpen","useState","m","mark","i18n","atts","a","Fragment","t","waiting","v","s","onRetry","busy","useRecorder","supported","recording","setRecording","seconds","setSeconds","error","setError","rec","useRef","chunks","timer","stopped","cleanup","useCallback","tr","start","stream","r","blob","began","stop","resolve","useEffect","Chat","toast","text","setText","pending","setPending","uploading","setUploading","transcript","setTranscript","listRef","fileRef","recorder","props","first","messages","tail","last","tailText","tailSteps","lastLayout","el","was","send","msg","body","f","voice","attach","files","x","stopAndTranscribe","voiceKey","placeholder","c","y","ConversationList","creating","setCreating","onKey","onClose","item","currentId","onPick","inGroup","conversations","g","topics","archive","onCreate","NewTopicModal","ChatLayout","narrow","title","setTitle","create"],"sources":["../src/chat/MessageView.tsx","../src/chat/useRecorder.ts","../src/chat/Chat.tsx","../src/chat/ConversationList.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport { Icon } from \"../components/Icon\";\nimport { Markdown } from \"../components/Markdown\";\nimport { UIKIT_NS } from \"../i18n\";\nimport type { ChatMessage, ChatStep } from \"./types\";\n\n/** The locale of Intl formatting for a language code of the products. */\nconst intlLocale = (lng: string) => (lng === \"zh\" ? \"zh-CN\" : lng);\n\n/** The time of a caption: hours and minutes in the language of the interface. */\nexport function markTime(iso: string, language: string): string {\n return new Intl.DateTimeFormat(intlLocale(language), { hour: \"2-digit\", minute: \"2-digit\" }).format(new Date(iso));\n}\n\n/** More than three steps are folded to the last two (design §6.4). */\nexport function visibleSteps(steps: ChatStep[], open: boolean): ChatStep[] {\n return open || steps.length <= 3 ? steps : steps.slice(-2);\n}\n\n/** The icon of a step and its colour class by the state of the step. */\nconst STEP = {\n done: { icon: \"check\", cls: \"ok\" },\n error: { icon: \"alert\", cls: \"er\" },\n running: { icon: \"clock\", cls: \"run\" },\n} as const;\n\n/** One message of the conversation (design §6.4). */\nexport function MessageView({ m, busy, onRetry }: { m: ChatMessage; busy: boolean; onRetry?: () => void }) {\n const { t, i18n } = useTranslation(UIKIT_NS);\n const [open, setOpen] = useState(false);\n if (m.role === \"note\") return <div className=\"sysnote\">{m.text}</div>;\n const mark = m.mark && <div className=\"chan\"><Icon name={m.mark.icon} size={12} /> {m.mark.text} · {markTime(m.createdAt, i18n.language)}</div>;\n const atts = m.attachments && m.attachments.length > 0 && (\n <div className=\"atts\">\n {m.attachments.map((a) => <a key={a.id} href={a.href}><Icon name=\"clip\" size={12} /> {a.fileName}</a>)}\n </div>\n );\n if (m.role === \"user\") {\n return (\n <div className=\"m u\">\n {mark}\n {m.content}\n {m.text && <div className=\"bub\">{m.text}</div>}\n {atts}\n {m.wait && (\n <div className={`turnstate${m.wait.state === \"queued\" ? \" q\" : \"\"}`} role=\"status\">\n {m.wait.state === \"queued\"\n ? <><Icon name=\"clock\" size={12} />{t(\"chat.wait.queued\", { count: m.wait.position })}</>\n : <><span className=\"spin\" aria-hidden=\"true\" />{t(\"chat.wait.starting\")}</>}\n </div>\n )}\n </div>\n );\n }\n const steps = m.steps ?? [];\n const waiting = (m.status === \"streaming\" || m.status === \"pending\") && !m.text && !m.content;\n return (\n <div className=\"m a\">\n {mark}\n {steps.length > 3 && (\n <button className=\"tools-fold\" onClick={() => setOpen((v) => !v)} aria-expanded={open}>\n {open ? t(\"chat.hideSteps\") : t(\"chat.moreSteps\", { count: steps.length - 2 })}\n </button>\n )}\n {visibleSteps(steps, open).map((s) => (\n <div key={s.id} className=\"tool\" title={s.title}>\n <Icon name={STEP[s.status].icon} size={14} className={STEP[s.status].cls} />\n <span className=\"tx\">{s.text}</span>\n </div>\n ))}\n {waiting && <div className=\"bub\"><span className=\"typing\" aria-label={t(\"chat.typing\")}><i /><i /><i /></span></div>}\n {m.content ?? (m.text && <div className=\"bub\"><Markdown text={m.text} /></div>)}\n {atts}\n {m.error && (\n <div className={`llmerr${m.error.transient ? \" amber\" : \"\"}`} role=\"alert\" style={{ marginTop: 6 }}>\n <span>{m.error.text}</span>\n {onRetry && (\n <div className=\"row\">\n <button className=\"btn sm\" disabled={busy} onClick={onRetry}><Icon name=\"refresh\" size={14} />{t(\"chat.retry\")}</button>\n </div>\n )}\n </div>\n )}\n </div>\n );\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\n\n/** Push-to-talk recording with MediaRecorder. */\nexport function useRecorder() {\n const supported = typeof window !== \"undefined\" && \"MediaRecorder\" in window && !!navigator.mediaDevices?.getUserMedia;\n const [recording, setRecording] = useState(false);\n const [seconds, setSeconds] = useState(0);\n const [error, setError] = useState(false);\n const rec = useRef<MediaRecorder | null>(null);\n const chunks = useRef<Blob[]>([]);\n const timer = useRef<number | undefined>(undefined);\n const stopped = useRef<((b: Blob | null) => void) | null>(null);\n\n const cleanup = useCallback(() => {\n window.clearInterval(timer.current);\n rec.current?.stream.getTracks().forEach((tr) => tr.stop());\n rec.current = null;\n setRecording(false);\n }, []);\n\n const start = useCallback(async () => {\n if (!supported || rec.current) return;\n setError(false);\n try {\n const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n const r = new MediaRecorder(stream);\n chunks.current = [];\n r.ondataavailable = (e) => e.data.size > 0 && chunks.current.push(e.data);\n r.onstop = () => {\n const blob = chunks.current.length ? new Blob(chunks.current, { type: r.mimeType || \"audio/webm\" }) : null;\n cleanup();\n stopped.current?.(blob);\n stopped.current = null;\n };\n rec.current = r;\n r.start();\n setSeconds(0);\n setRecording(true);\n const began = Date.now();\n timer.current = window.setInterval(() => setSeconds(Math.floor((Date.now() - began) / 1000)), 250);\n } catch {\n setError(true);\n cleanup();\n }\n }, [supported, cleanup]);\n\n const stop = useCallback(() => new Promise<Blob | null>((resolve) => {\n const r = rec.current;\n if (!r || r.state === \"inactive\") return resolve(null);\n stopped.current = resolve;\n r.stop();\n }), []);\n\n useEffect(() => () => cleanup(), [cleanup]);\n\n const mm = Math.floor(seconds / 60);\n const ss = String(seconds % 60).padStart(2, \"0\");\n return { supported, recording, seconds: `${mm}:${ss}`, error, start, stop };\n}\n","import { useLayoutEffect, useRef, useState, type KeyboardEvent } from \"react\";\nimport { Icon } from \"../components/Icon\";\nimport { useToast } from \"../components/ui\";\nimport { useKitT } from \"../i18n\";\nimport { MessageView } from \"./MessageView\";\nimport type { ChatProps, ChatUpload } from \"./types\";\nimport { useRecorder } from \"./useRecorder\";\n\n/** The conversation: the messages and the composer (FTR.NAB.CMN-0005 R11–R13,\n * design §6). The behaviour is the one of the chat of Nabu. A control exists\n * only when the product gave its handler: no `onCancel` — no cancel button. */\nexport function Chat(props: ChatProps) {\n const t = useKitT();\n const toast = useToast();\n const [text, setText] = useState(\"\");\n const [pending, setPending] = useState<ChatUpload[]>([]);\n const [uploading, setUploading] = useState(false);\n const [transcript, setTranscript] = useState<string | null>(null);\n const listRef = useRef<HTMLDivElement>(null);\n const fileRef = useRef<HTMLInputElement>(null);\n const recorder = useRecorder();\n const { messages, busy = false } = props;\n\n const first = messages[0]?.id;\n const tail = messages[messages.length - 1];\n const last = tail?.id;\n const tailText = tail?.text.length;\n const tailSteps = tail?.steps?.length;\n // The ends and the height of the list at the last layout: earlier messages\n // loaded above it keep the place of the list.\n const lastLayout = useRef<{ first?: string; last?: string; height: number }>({ height: 0 });\n // The list follows new messages and the growth of the last one: the dependencies are the trigger.\n useLayoutEffect(() => {\n const el = listRef.current;\n if (!el) return;\n const was = lastLayout.current;\n // history is prepended when the start of the list changed and its end did not;\n // another conversation changes both\n const prepended = was.first !== undefined && first !== was.first && last === was.last;\n el.scrollTop = prepended ? el.scrollTop + (el.scrollHeight - was.height) : el.scrollHeight;\n lastLayout.current = { first, last, height: el.scrollHeight };\n // oxlint-disable-next-line react/exhaustive-effect-dependencies -- the length and the growth of the tail only trigger\n }, [messages.length, first, last, tailText, tailSteps]);\n\n const send = async (body: string, voice: boolean) => {\n const msg = body.trim();\n if ((!msg && pending.length === 0) || busy || props.readOnly) return;\n try {\n await props.onSend(msg, pending.map((f) => f.id), voice);\n setText(\"\");\n setPending([]);\n setTranscript(null);\n } catch {\n // the product has told the user; the text stays in the composer\n }\n };\n\n const attach = async (files: FileList | null) => {\n if (!files?.length || !props.onUpload) return;\n setUploading(true);\n for (const f of Array.from(files)) {\n if (props.upload && f.size > props.upload.maxBytes) {\n toast({ kind: \"error\", title: t(\"chat.tooLarge\"), text: f.name });\n continue;\n }\n const a = await props.onUpload(f).catch(() => null);\n if (a) setPending((x) => [...x, a]);\n }\n setUploading(false);\n if (fileRef.current) fileRef.current.value = \"\";\n };\n\n const stopAndTranscribe = async () => {\n const blob = await recorder.stop();\n if (!blob || !props.onTranscribe) return;\n const r = await props.onTranscribe(blob).catch(() => null);\n if (r === null) return;\n if (!r) toast({ kind: \"error\", title: t(\"chat.voice.notRecognized\") });\n else setTranscript(r);\n };\n // Without a pointer the same button starts and stops the recording (R18).\n const voiceKey = (e: KeyboardEvent) => {\n if (e.key !== \" \" && e.key !== \"Enter\") return;\n e.preventDefault();\n if (e.repeat) return;\n if (recorder.recording) void stopAndTranscribe();\n else void recorder.start();\n };\n\n const placeholder = t(\"chat.placeholder\", { name: props.agentName });\n return (\n <section className=\"nb-chat\" aria-label={t(\"chat.title\")}>\n {props.header}\n <div className=\"msgs\" ref={listRef} aria-live=\"polite\">\n {props.hasOlder && props.onLoadOlder && (\n <button className=\"btn ghost sm\" style={{ alignSelf: \"center\" }} disabled={props.loadingOlder} onClick={props.onLoadOlder}>\n {t(\"chat.loadOlder\")}\n </button>\n )}\n {props.historyError && <div className=\"err-text\">{props.historyError}</div>}\n {messages.length === 0 && !props.loading && !props.historyError && (\n <div className=\"empty\"><div className=\"ic\"><Icon name=\"spark\" /></div><b>{t(\"chat.emptyTitle\", { name: props.agentName })}</b>{props.emptyHint}</div>\n )}\n {messages.map((m) => <MessageView key={m.id} m={m} busy={busy} onRetry={props.onRetry && (() => props.onRetry?.(m.id))} />)}\n {props.onConfirm && (props.confirmations ?? []).map((c) => (\n <div className=\"confirm\" key={c.id} role=\"group\" aria-label={t(\"chat.confirm.title\")}>\n <div className=\"h\"><Icon name=\"alert\" size={16} />{t(\"chat.confirm.title\")}</div>\n <div>{t(\"chat.confirm.text\", { action: c.summary })}</div>\n {c.preview !== undefined && <pre>{c.preview}</pre>}\n <div className=\"acts\">\n {/* never tied to `busy`: the agent asks in the middle of its turn */}\n <button className=\"btn sm primary\" onClick={() => props.onConfirm?.(c.id, true)}>{t(\"chat.confirm.approve\")}</button>\n <button className=\"btn sm\" onClick={() => props.onConfirm?.(c.id, false)}>{t(\"chat.confirm.reject\")}</button>\n </div>\n </div>\n ))}\n </div>\n\n {props.readOnly ? (\n <div className=\"composer\" style={{ justifyContent: \"space-between\", alignItems: \"center\" }}>{props.readOnly}</div>\n ) : transcript !== null ? (\n <div className=\"transcript\" style={{ margin: 10 }}>\n <div className=\"small muted\" style={{ marginBottom: 4 }}>{t(\"chat.voice.review\")}</div>\n <textarea className=\"inp\" rows={3} value={transcript} onChange={(e) => setTranscript(e.target.value)} aria-label={t(\"chat.voice.review\")} />\n <div className=\"row2\" style={{ marginTop: 8 }}>\n <button className=\"btn ghost sm\" onClick={() => setTranscript(null)}>{t(\"delete\")}</button>\n <button className=\"btn primary sm\" disabled={!transcript.trim() || busy} onClick={() => send(transcript, true)}>{t(\"chat.send\")}</button>\n </div>\n </div>\n ) : (\n <>\n {props.aboveComposer}\n {pending.length > 0 && (\n <div className=\"pending-files\">\n {pending.map((f) => (\n <span key={f.id} className=\"chip\">\n <Icon name=\"clip\" size={12} />{f.fileName}\n <button className=\"iconbtn\" style={{ width: 18, height: 18 }} aria-label={t(\"remove\")}\n onClick={() => setPending((x) => x.filter((y) => y.id !== f.id))}><Icon name=\"x\" size={12} /></button>\n </span>\n ))}\n </div>\n )}\n <div className=\"composer\">\n {props.onUpload && (\n <>\n <button className=\"iconbtn\" aria-label={t(\"chat.attach\")} disabled={uploading} onClick={() => fileRef.current?.click()}><Icon name=\"clip\" /></button>\n <input ref={fileRef} type=\"file\" multiple hidden accept={props.upload?.accept} onChange={(e) => attach(e.target.files)} />\n </>\n )}\n {recorder.recording ? (\n <div className=\"rec-box\">{t(\"chat.voice.recording\", { seconds: recorder.seconds })}</div>\n ) : (\n <textarea rows={1} value={text} placeholder={placeholder} aria-label={placeholder} onChange={(e) => setText(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" && !e.shiftKey && !e.nativeEvent.isComposing) {\n e.preventDefault();\n void send(text, false);\n }\n }} />\n )}\n {props.onTranscribe && (\n <button className={`iconbtn${recorder.recording ? \" rec\" : \"\"}`} disabled={!recorder.supported}\n aria-label={recorder.recording ? t(\"chat.voice.stop\") : t(\"chat.voice.hold\")}\n title={recorder.supported ? t(\"chat.voice.hold\") : t(\"chat.voice.unsupported\")}\n onPointerDown={(e) => { e.preventDefault(); if (!recorder.recording) void recorder.start(); }}\n onPointerUp={() => recorder.recording && stopAndTranscribe()}\n onPointerLeave={() => recorder.recording && stopAndTranscribe()}\n onKeyDown={voiceKey}>\n <Icon name=\"mic\" />\n </button>\n )}\n {props.onCancel && busy ? (\n <button className=\"iconbtn fill\" aria-label={t(\"chat.stop\")} onClick={props.onCancel}><Icon name=\"stop\" /></button>\n ) : (\n <button className=\"iconbtn fill\" aria-label={t(\"chat.send\")} disabled={(!text.trim() && pending.length === 0) || busy} onClick={() => send(text, false)}>\n <Icon name=\"send\" />\n </button>\n )}\n </div>\n {recorder.error && <div className=\"err-text\" style={{ padding: \"0 12px 8px\" }}>{t(\"chat.voice.micDenied\")}</div>}\n </>\n )}\n </section>\n );\n}\n","import { useEffect, useState, type ReactNode } from \"react\";\nimport { Icon } from \"../components/Icon\";\nimport { Modal } from \"../components/ui\";\nimport { useKitT } from \"../i18n\";\nimport type { ChatConversation, ConversationListProps } from \"./types\";\n\n/** The conversations of the user: the main one, the topics, the archive (design\n * §6.2). In a wide place the list stands left of the messages; on a phone and in\n * a narrow place it lies over them while open and closes by a pick, a tap\n * beside it or Escape. */\nexport function ConversationList({ conversations, currentId, open, onPick, onClose, onCreate }: ConversationListProps) {\n const t = useKitT();\n const [creating, setCreating] = useState(false);\n useEffect(() => {\n if (!open) return;\n const onKey = (e: KeyboardEvent) => e.key === \"Escape\" && onClose();\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [open, onClose]);\n\n const item = (c: ChatConversation) => (\n <button key={c.id} className={`it${c.id === currentId ? \" on\" : \"\"}${c.group === \"archive\" ? \" arch\" : \"\"}`}\n onClick={() => onPick(c.id)} aria-current={c.id === currentId}>\n <Icon name={c.icon ?? \"msg2\"} size={16} />\n <span className=\"nm\">{c.title}</span>\n {!!c.unread && <span className=\"unread\" aria-label={t(\"chat.unread\", { count: c.unread })}>{c.unread}</span>}\n <span className=\"sub\">{c.when}</span>\n </button>\n );\n const inGroup = (g: ChatConversation[\"group\"]) => conversations.filter((c) => c.group === g);\n const topics = inGroup(\"topic\");\n const archive = inGroup(\"archive\");\n return (\n <>\n <nav className={`nb-side${open ? \" open\" : \"\"}`} aria-label={t(\"chat.conversations\")}>\n {onCreate && (\n <button className=\"btn sm\" style={{ width: \"100%\", justifyContent: \"center\" }} onClick={() => setCreating(true)}>\n <Icon name=\"plus\" size={15} />{t(\"chat.newTopic\")}\n </button>\n )}\n <div className=\"h\">{t(\"chat.conversation\")}</div>\n {inGroup(\"main\").map(item)}\n <div className=\"h\">{t(\"chat.topics\")}</div>\n {topics.length === 0 && <div className=\"small muted\" style={{ padding: \"0 10px\" }}>{t(\"chat.noTopics\")}</div>}\n {topics.map(item)}\n {archive.length > 0 && <div className=\"h\">{t(\"chat.archive\")}</div>}\n {archive.map(item)}\n </nav>\n {/* over the messages: a tap beside the list closes it */}\n {open && <button type=\"button\" className=\"nb-scrim\" aria-label={t(\"close\")} onClick={onClose} />}\n {creating && onCreate && <NewTopicModal onClose={() => setCreating(false)} onCreate={onCreate} />}\n </>\n );\n}\n\n/** The place of a chat: the list of conversations, the conversation and what the\n * product puts beside them. `narrow` is for a side panel: the list lies over the\n * messages as on a phone, whatever the width of the window. */\nexport function ChatLayout({ narrow, children }: { narrow?: boolean; children: ReactNode }) {\n return <div className={`nb-app${narrow ? \" narrow\" : \"\"}`}>{children}</div>;\n}\n\n/** The dialog of a new topic: one field, Enter creates. */\nexport function NewTopicModal({ onClose, onCreate }: { onClose: () => void; onCreate: (title: string) => Promise<void> }) {\n const t = useKitT();\n const [title, setTitle] = useState(\"\");\n const [pending, setPending] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const create = async () => {\n if (!title.trim() || pending) return;\n setPending(true);\n setError(null);\n try {\n await onCreate(title.trim());\n onClose();\n } catch (e) {\n setError(e instanceof Error ? e.message : String(e));\n setPending(false);\n }\n };\n return (\n <Modal title={t(\"chat.newTopic\")} onClose={onClose} footer={<>\n <button className=\"btn ghost\" onClick={onClose}>{t(\"cancel\")}</button>\n <button className=\"btn primary\" disabled={!title.trim() || pending} onClick={create}>{t(\"create\")}</button>\n </>}>\n <div className=\"field\">\n <label htmlFor=\"topic-title\">{t(\"chat.topicTitle\")}</label>\n <input id=\"topic-title\" className=\"inp\" value={title} maxLength={120} onChange={(e) => setTitle(e.target.value)}\n onKeyDown={(e) => e.key === \"Enter\" && create()} />\n <div className=\"hint\">{t(\"chat.topicHint\")}</div>\n </div>\n {error && <div className=\"err-text\">{error}</div>}\n </Modal>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"chat.js","names":["_Fragment","_Fragment","_Fragment","useToast","Icon","Modal","Markdown","useOutside","useKitT","UIKIT_NS","intlLocale","lng","markTime","language","iso","visibleSteps","open","steps","STEP","MessageView","useTranslation","setOpen","useState","m","mark","i18n","atts","a","Fragment","t","waiting","v","s","onRetry","busy","useRecorder","supported","recording","setRecording","seconds","setSeconds","error","setError","rec","useRef","chunks","timer","stopped","cleanup","useCallback","tr","start","stream","r","blob","began","stop","resolve","useEffect","Chat","toast","text","setText","pending","setPending","uploading","setUploading","transcript","setTranscript","listRef","fileRef","recorder","props","first","messages","tail","last","tailText","tailSteps","lastLayout","el","was","send","msg","body","f","voice","attach","files","x","stopAndTranscribe","voiceKey","placeholder","c","y","ConversationList","creating","setCreating","onKey","onClose","item","currentId","onPick","inGroup","conversations","g","topics","archive","onCreate","NewTopicModal","ChatLayout","narrow","title","setTitle","create","AgentButton","small","label","expanded","icon","AgentMenu","draft","setDraft","name","ref","next","onRename","optionsLabel","options","value","o","hint"],"sources":["../src/chat/MessageView.tsx","../src/chat/useRecorder.ts","../src/chat/Chat.tsx","../src/chat/ConversationList.tsx","../src/chat/AgentMenu.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport { Icon } from \"../components/Icon\";\nimport { Markdown } from \"../components/Markdown\";\nimport { UIKIT_NS } from \"../i18n\";\nimport type { ChatMessage, ChatStep } from \"./types\";\n\n/** The locale of Intl formatting for a language code of the products. */\nconst intlLocale = (lng: string) => (lng === \"zh\" ? \"zh-CN\" : lng);\n\n/** The time of a caption: hours and minutes in the language of the interface. */\nexport function markTime(iso: string, language: string): string {\n return new Intl.DateTimeFormat(intlLocale(language), { hour: \"2-digit\", minute: \"2-digit\" }).format(new Date(iso));\n}\n\n/** More than three steps are folded to the last two (design §6.4). */\nexport function visibleSteps(steps: ChatStep[], open: boolean): ChatStep[] {\n return open || steps.length <= 3 ? steps : steps.slice(-2);\n}\n\n/** The icon of a step and its colour class by the state of the step. */\nconst STEP = {\n done: { icon: \"check\", cls: \"ok\" },\n error: { icon: \"alert\", cls: \"er\" },\n running: { icon: \"clock\", cls: \"run\" },\n} as const;\n\n/** One message of the conversation (design §6.4). */\nexport function MessageView({ m, busy, onRetry }: { m: ChatMessage; busy: boolean; onRetry?: () => void }) {\n const { t, i18n } = useTranslation(UIKIT_NS);\n const [open, setOpen] = useState(false);\n if (m.role === \"note\") return <div className=\"sysnote\">{m.text}</div>;\n const mark = m.mark && <div className=\"chan\"><Icon name={m.mark.icon} size={12} /> {m.mark.text} · {markTime(m.createdAt, i18n.language)}</div>;\n const atts = m.attachments && m.attachments.length > 0 && (\n <div className=\"atts\">\n {m.attachments.map((a) => <a key={a.id} href={a.href}><Icon name=\"clip\" size={12} /> {a.fileName}</a>)}\n </div>\n );\n if (m.role === \"user\") {\n return (\n <div className=\"m u\">\n {mark}\n {m.content}\n {m.text && <div className=\"bub\">{m.text}</div>}\n {atts}\n {m.wait && (\n <div className={`turnstate${m.wait.state === \"queued\" ? \" q\" : \"\"}`} role=\"status\">\n {m.wait.state === \"queued\"\n ? <><Icon name=\"clock\" size={12} />{t(\"chat.wait.queued\", { count: m.wait.position })}</>\n : <><span className=\"spin\" aria-hidden=\"true\" />{t(\"chat.wait.starting\")}</>}\n </div>\n )}\n </div>\n );\n }\n const steps = m.steps ?? [];\n const waiting = (m.status === \"streaming\" || m.status === \"pending\") && !m.text && !m.content;\n return (\n <div className=\"m a\">\n {mark}\n {steps.length > 3 && (\n <button className=\"tools-fold\" onClick={() => setOpen((v) => !v)} aria-expanded={open}>\n {open ? t(\"chat.hideSteps\") : t(\"chat.moreSteps\", { count: steps.length - 2 })}\n </button>\n )}\n {visibleSteps(steps, open).map((s) => (\n <div key={s.id} className=\"tool\" title={s.title}>\n <Icon name={STEP[s.status].icon} size={14} className={STEP[s.status].cls} />\n <span className=\"tx\">{s.text}</span>\n </div>\n ))}\n {waiting && <div className=\"bub\"><span className=\"typing\" aria-label={t(\"chat.typing\")}><i /><i /><i /></span></div>}\n {m.content ?? (m.text && <div className=\"bub\"><Markdown text={m.text} /></div>)}\n {atts}\n {m.error && (\n <div className={`llmerr${m.error.transient ? \" amber\" : \"\"}`} role=\"alert\" style={{ marginTop: 6 }}>\n <span>{m.error.text}</span>\n {onRetry && (\n <div className=\"row\">\n <button className=\"btn sm\" disabled={busy} onClick={onRetry}><Icon name=\"refresh\" size={14} />{t(\"chat.retry\")}</button>\n </div>\n )}\n </div>\n )}\n </div>\n );\n}\n","import { useCallback, useEffect, useRef, useState } from \"react\";\n\n/** Push-to-talk recording with MediaRecorder. */\nexport function useRecorder() {\n const supported = typeof window !== \"undefined\" && \"MediaRecorder\" in window && !!navigator.mediaDevices?.getUserMedia;\n const [recording, setRecording] = useState(false);\n const [seconds, setSeconds] = useState(0);\n const [error, setError] = useState(false);\n const rec = useRef<MediaRecorder | null>(null);\n const chunks = useRef<Blob[]>([]);\n const timer = useRef<number | undefined>(undefined);\n const stopped = useRef<((b: Blob | null) => void) | null>(null);\n\n const cleanup = useCallback(() => {\n window.clearInterval(timer.current);\n rec.current?.stream.getTracks().forEach((tr) => tr.stop());\n rec.current = null;\n setRecording(false);\n }, []);\n\n const start = useCallback(async () => {\n if (!supported || rec.current) return;\n setError(false);\n try {\n const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n const r = new MediaRecorder(stream);\n chunks.current = [];\n r.ondataavailable = (e) => e.data.size > 0 && chunks.current.push(e.data);\n r.onstop = () => {\n const blob = chunks.current.length ? new Blob(chunks.current, { type: r.mimeType || \"audio/webm\" }) : null;\n cleanup();\n stopped.current?.(blob);\n stopped.current = null;\n };\n rec.current = r;\n r.start();\n setSeconds(0);\n setRecording(true);\n const began = Date.now();\n timer.current = window.setInterval(() => setSeconds(Math.floor((Date.now() - began) / 1000)), 250);\n } catch {\n setError(true);\n cleanup();\n }\n }, [supported, cleanup]);\n\n const stop = useCallback(() => new Promise<Blob | null>((resolve) => {\n const r = rec.current;\n if (!r || r.state === \"inactive\") return resolve(null);\n stopped.current = resolve;\n r.stop();\n }), []);\n\n useEffect(() => () => cleanup(), [cleanup]);\n\n const mm = Math.floor(seconds / 60);\n const ss = String(seconds % 60).padStart(2, \"0\");\n return { supported, recording, seconds: `${mm}:${ss}`, error, start, stop };\n}\n","import { useLayoutEffect, useRef, useState, type KeyboardEvent } from \"react\";\nimport { Icon } from \"../components/Icon\";\nimport { useToast } from \"../components/ui\";\nimport { useKitT } from \"../i18n\";\nimport { MessageView } from \"./MessageView\";\nimport type { ChatProps, ChatUpload } from \"./types\";\nimport { useRecorder } from \"./useRecorder\";\n\n/** The conversation: the messages and the composer (FTR.NAB.CMN-0005 R11–R13,\n * design §6). The behaviour is the one of the chat of Nabu. A control exists\n * only when the product gave its handler: no `onCancel` — no cancel button. */\nexport function Chat(props: ChatProps) {\n const t = useKitT();\n const toast = useToast();\n const [text, setText] = useState(\"\");\n const [pending, setPending] = useState<ChatUpload[]>([]);\n const [uploading, setUploading] = useState(false);\n const [transcript, setTranscript] = useState<string | null>(null);\n const listRef = useRef<HTMLDivElement>(null);\n const fileRef = useRef<HTMLInputElement>(null);\n const recorder = useRecorder();\n const { messages, busy = false } = props;\n\n const first = messages[0]?.id;\n const tail = messages[messages.length - 1];\n const last = tail?.id;\n const tailText = tail?.text.length;\n const tailSteps = tail?.steps?.length;\n // The ends and the height of the list at the last layout: earlier messages\n // loaded above it keep the place of the list.\n const lastLayout = useRef<{ first?: string; last?: string; height: number }>({ height: 0 });\n // The list follows new messages and the growth of the last one: the dependencies are the trigger.\n useLayoutEffect(() => {\n const el = listRef.current;\n if (!el) return;\n const was = lastLayout.current;\n // history is prepended when the start of the list changed and its end did not;\n // another conversation changes both\n const prepended = was.first !== undefined && first !== was.first && last === was.last;\n el.scrollTop = prepended ? el.scrollTop + (el.scrollHeight - was.height) : el.scrollHeight;\n lastLayout.current = { first, last, height: el.scrollHeight };\n // oxlint-disable-next-line react/exhaustive-effect-dependencies -- the length and the growth of the tail only trigger\n }, [messages.length, first, last, tailText, tailSteps]);\n\n const send = async (body: string, voice: boolean) => {\n const msg = body.trim();\n if ((!msg && pending.length === 0) || busy || props.readOnly) return;\n try {\n await props.onSend(msg, pending.map((f) => f.id), voice);\n setText(\"\");\n setPending([]);\n setTranscript(null);\n } catch {\n // the product has told the user; the text stays in the composer\n }\n };\n\n const attach = async (files: FileList | null) => {\n if (!files?.length || !props.onUpload) return;\n setUploading(true);\n for (const f of Array.from(files)) {\n if (props.upload && f.size > props.upload.maxBytes) {\n toast({ kind: \"error\", title: t(\"chat.tooLarge\"), text: f.name });\n continue;\n }\n const a = await props.onUpload(f).catch(() => null);\n if (a) setPending((x) => [...x, a]);\n }\n setUploading(false);\n if (fileRef.current) fileRef.current.value = \"\";\n };\n\n const stopAndTranscribe = async () => {\n const blob = await recorder.stop();\n if (!blob || !props.onTranscribe) return;\n const r = await props.onTranscribe(blob).catch(() => null);\n if (r === null) return;\n if (!r) toast({ kind: \"error\", title: t(\"chat.voice.notRecognized\") });\n else setTranscript(r);\n };\n // Without a pointer the same button starts and stops the recording (R18).\n const voiceKey = (e: KeyboardEvent) => {\n if (e.key !== \" \" && e.key !== \"Enter\") return;\n e.preventDefault();\n if (e.repeat) return;\n if (recorder.recording) void stopAndTranscribe();\n else void recorder.start();\n };\n\n const placeholder = t(\"chat.placeholder\", { name: props.agentName });\n return (\n <section className=\"nb-chat\" aria-label={t(\"chat.title\")}>\n {props.header}\n <div className=\"msgs\" ref={listRef} aria-live=\"polite\">\n {props.hasOlder && props.onLoadOlder && (\n <button className=\"btn ghost sm\" style={{ alignSelf: \"center\" }} disabled={props.loadingOlder} onClick={props.onLoadOlder}>\n {t(\"chat.loadOlder\")}\n </button>\n )}\n {props.historyError && <div className=\"err-text\">{props.historyError}</div>}\n {messages.length === 0 && !props.loading && !props.historyError && (\n <div className=\"empty\"><div className=\"ic\"><Icon name=\"spark\" /></div><b>{t(\"chat.emptyTitle\", { name: props.agentName })}</b>{props.emptyHint}</div>\n )}\n {messages.map((m) => <MessageView key={m.id} m={m} busy={busy} onRetry={props.onRetry && (() => props.onRetry?.(m.id))} />)}\n {props.onConfirm && (props.confirmations ?? []).map((c) => (\n <div className=\"confirm\" key={c.id} role=\"group\" aria-label={t(\"chat.confirm.title\")}>\n <div className=\"h\"><Icon name=\"alert\" size={16} />{t(\"chat.confirm.title\")}</div>\n <div>{t(\"chat.confirm.text\", { action: c.summary })}</div>\n {c.preview !== undefined && <pre>{c.preview}</pre>}\n <div className=\"acts\">\n {/* never tied to `busy`: the agent asks in the middle of its turn */}\n <button className=\"btn sm primary\" onClick={() => props.onConfirm?.(c.id, true)}>{t(\"chat.confirm.approve\")}</button>\n <button className=\"btn sm\" onClick={() => props.onConfirm?.(c.id, false)}>{t(\"chat.confirm.reject\")}</button>\n </div>\n </div>\n ))}\n </div>\n\n {props.readOnly ? (\n <div className=\"composer\" style={{ justifyContent: \"space-between\", alignItems: \"center\" }}>{props.readOnly}</div>\n ) : transcript !== null ? (\n <div className=\"transcript\" style={{ margin: 10 }}>\n <div className=\"small muted\" style={{ marginBottom: 4 }}>{t(\"chat.voice.review\")}</div>\n <textarea className=\"inp\" rows={3} value={transcript} onChange={(e) => setTranscript(e.target.value)} aria-label={t(\"chat.voice.review\")} />\n <div className=\"row2\" style={{ marginTop: 8 }}>\n <button className=\"btn ghost sm\" onClick={() => setTranscript(null)}>{t(\"delete\")}</button>\n <button className=\"btn primary sm\" disabled={!transcript.trim() || busy} onClick={() => send(transcript, true)}>{t(\"chat.send\")}</button>\n </div>\n </div>\n ) : (\n <>\n {props.aboveComposer}\n {pending.length > 0 && (\n <div className=\"pending-files\">\n {pending.map((f) => (\n <span key={f.id} className=\"chip\">\n <Icon name=\"clip\" size={12} />{f.fileName}\n <button className=\"iconbtn\" style={{ width: 18, height: 18 }} aria-label={t(\"remove\")}\n onClick={() => setPending((x) => x.filter((y) => y.id !== f.id))}><Icon name=\"x\" size={12} /></button>\n </span>\n ))}\n </div>\n )}\n <div className=\"composer\">\n {props.onUpload && (\n <>\n <button className=\"iconbtn\" aria-label={t(\"chat.attach\")} disabled={uploading} onClick={() => fileRef.current?.click()}><Icon name=\"clip\" /></button>\n <input ref={fileRef} type=\"file\" multiple hidden accept={props.upload?.accept} onChange={(e) => attach(e.target.files)} />\n </>\n )}\n {recorder.recording ? (\n <div className=\"rec-box\">{t(\"chat.voice.recording\", { seconds: recorder.seconds })}</div>\n ) : (\n <textarea rows={1} value={text} placeholder={placeholder} aria-label={placeholder} onChange={(e) => setText(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" && !e.shiftKey && !e.nativeEvent.isComposing) {\n e.preventDefault();\n void send(text, false);\n }\n }} />\n )}\n {props.onTranscribe && (\n <button className={`iconbtn${recorder.recording ? \" rec\" : \"\"}`} disabled={!recorder.supported}\n aria-label={recorder.recording ? t(\"chat.voice.stop\") : t(\"chat.voice.hold\")}\n title={recorder.supported ? t(\"chat.voice.hold\") : t(\"chat.voice.unsupported\")}\n onPointerDown={(e) => { e.preventDefault(); if (!recorder.recording) void recorder.start(); }}\n onPointerUp={() => recorder.recording && stopAndTranscribe()}\n onPointerLeave={() => recorder.recording && stopAndTranscribe()}\n onKeyDown={voiceKey}>\n <Icon name=\"mic\" />\n </button>\n )}\n {props.onCancel && busy ? (\n <button className=\"iconbtn fill\" aria-label={t(\"chat.stop\")} onClick={props.onCancel}><Icon name=\"stop\" /></button>\n ) : (\n <button className=\"iconbtn fill\" aria-label={t(\"chat.send\")} disabled={(!text.trim() && pending.length === 0) || busy} onClick={() => send(text, false)}>\n <Icon name=\"send\" />\n </button>\n )}\n </div>\n {recorder.error && <div className=\"err-text\" style={{ padding: \"0 12px 8px\" }}>{t(\"chat.voice.micDenied\")}</div>}\n </>\n )}\n </section>\n );\n}\n","import { useEffect, useState, type ReactNode } from \"react\";\nimport { Icon } from \"../components/Icon\";\nimport { Modal } from \"../components/ui\";\nimport { useKitT } from \"../i18n\";\nimport type { ChatConversation, ConversationListProps } from \"./types\";\n\n/** The conversations of the user: the main one, the topics, the archive (design\n * §6.2). In a wide place the list stands left of the messages; on a phone and in\n * a narrow place it lies over them while open and closes by a pick, a tap\n * beside it or Escape. */\nexport function ConversationList({ conversations, currentId, open, onPick, onClose, onCreate }: ConversationListProps) {\n const t = useKitT();\n const [creating, setCreating] = useState(false);\n useEffect(() => {\n if (!open) return;\n const onKey = (e: KeyboardEvent) => e.key === \"Escape\" && onClose();\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [open, onClose]);\n\n const item = (c: ChatConversation) => (\n <button key={c.id} className={`it${c.id === currentId ? \" on\" : \"\"}${c.group === \"archive\" ? \" arch\" : \"\"}`}\n onClick={() => onPick(c.id)} aria-current={c.id === currentId}>\n <Icon name={c.icon ?? \"msg2\"} size={16} />\n <span className=\"nm\">{c.title}</span>\n {!!c.unread && <span className=\"unread\" aria-label={t(\"chat.unread\", { count: c.unread })}>{c.unread}</span>}\n <span className=\"sub\">{c.when}</span>\n </button>\n );\n const inGroup = (g: ChatConversation[\"group\"]) => conversations.filter((c) => c.group === g);\n const topics = inGroup(\"topic\");\n const archive = inGroup(\"archive\");\n return (\n <>\n <nav className={`nb-side${open ? \" open\" : \"\"}`} aria-label={t(\"chat.conversations\")}>\n {onCreate && (\n <button className=\"btn sm\" style={{ width: \"100%\", justifyContent: \"center\" }} onClick={() => setCreating(true)}>\n <Icon name=\"plus\" size={15} />{t(\"chat.newTopic\")}\n </button>\n )}\n <div className=\"h\">{t(\"chat.conversation\")}</div>\n {inGroup(\"main\").map(item)}\n <div className=\"h\">{t(\"chat.topics\")}</div>\n {topics.length === 0 && <div className=\"small muted\" style={{ padding: \"0 10px\" }}>{t(\"chat.noTopics\")}</div>}\n {topics.map(item)}\n {archive.length > 0 && <div className=\"h\">{t(\"chat.archive\")}</div>}\n {archive.map(item)}\n </nav>\n {/* over the messages: a tap beside the list closes it */}\n {open && <button type=\"button\" className=\"nb-scrim\" aria-label={t(\"close\")} onClick={onClose} />}\n {creating && onCreate && <NewTopicModal onClose={() => setCreating(false)} onCreate={onCreate} />}\n </>\n );\n}\n\n/** The place of a chat: the list of conversations, the conversation and what the\n * product puts beside them. `narrow` is for a side panel: the list lies over the\n * messages as on a phone, whatever the width of the window. */\nexport function ChatLayout({ narrow, children }: { narrow?: boolean; children: ReactNode }) {\n return <div className={`nb-app${narrow ? \" narrow\" : \"\"}`}>{children}</div>;\n}\n\n/** The dialog of a new topic: one field, Enter creates. */\nexport function NewTopicModal({ onClose, onCreate }: { onClose: () => void; onCreate: (title: string) => Promise<void> }) {\n const t = useKitT();\n const [title, setTitle] = useState(\"\");\n const [pending, setPending] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const create = async () => {\n if (!title.trim() || pending) return;\n setPending(true);\n setError(null);\n try {\n await onCreate(title.trim());\n onClose();\n } catch (e) {\n setError(e instanceof Error ? e.message : String(e));\n setPending(false);\n }\n };\n return (\n <Modal title={t(\"chat.newTopic\")} onClose={onClose} footer={<>\n <button className=\"btn ghost\" onClick={onClose}>{t(\"cancel\")}</button>\n <button className=\"btn primary\" disabled={!title.trim() || pending} onClick={create}>{t(\"create\")}</button>\n </>}>\n <div className=\"field\">\n <label htmlFor=\"topic-title\">{t(\"chat.topicTitle\")}</label>\n <input id=\"topic-title\" className=\"inp\" value={title} maxLength={120} onChange={(e) => setTitle(e.target.value)}\n onKeyDown={(e) => e.key === \"Enter\" && create()} />\n <div className=\"hint\">{t(\"chat.topicHint\")}</div>\n </div>\n {error && <div className=\"err-text\">{error}</div>}\n </Modal>\n );\n}\n","import { useState, type ReactNode } from \"react\";\nimport { Icon, type IconName } from \"../components/Icon\";\nimport { useOutside } from \"../components/ui\";\nimport { useKitT } from \"../i18n\";\n\n/** One of the ways an agent can look and talk; what it means only the product knows (R7). */\nexport interface AgentOption { key: string; label: string; icon: IconName }\n\n/** The round button of the agent: its icon, and the menu behind it. */\nexport function AgentButton({ icon, label, title, expanded, small, disabled, onClick }: {\n icon: IconName; label: string; title?: string; expanded?: boolean; small?: boolean; disabled?: boolean; onClick?: () => void;\n}) {\n return (\n <button type=\"button\" className={`agentbtn${small ? \" s\" : \"\"}`} disabled={disabled} aria-label={label} aria-expanded={expanded} title={title}\n // the menu closes on an outside mousedown; keep this click a toggle\n onMouseDown={(e) => expanded && e.stopPropagation()} onClick={onClick}>\n <Icon name={icon} size={small ? 15 : 18} />\n </button>\n );\n}\n\n/** The menu of the agent (FTR.NAB.CMN-0005 R15, design §6.6): its name and a\n * choice among the options of the product, as tiles with icons. The name is\n * saved on Enter and on leaving the field; a pick is saved at once. */\nexport function AgentMenu({ label, name, options, value, optionsLabel, hint, narrow, onRename, onPick, onClose }: {\n /** The name of the dialog for a screen reader. */\n label: string;\n name: string;\n options: AgentOption[];\n value: string;\n optionsLabel: ReactNode;\n hint?: ReactNode;\n /** A narrow place — a side panel: the options go in one column. */\n narrow?: boolean;\n onRename: (name: string) => void;\n onPick: (key: string) => void;\n /** Must keep its identity between renders. */\n onClose: () => void;\n}) {\n const t = useKitT();\n const [draft, setDraft] = useState(name);\n const ref = useOutside<HTMLDivElement>(true, onClose);\n const save = () => {\n const next = draft.trim();\n if (next && next !== name) onRename(next);\n };\n return (\n <div className={`agentmenu${narrow ? \" narrow\" : \"\"}`} ref={ref} role=\"dialog\" aria-label={label}>\n <div className=\"lab\">{t(\"agentName\")}</div>\n <input className=\"inp\" value={draft} maxLength={40} autoFocus aria-label={t(\"agentName\")}\n onChange={(e) => setDraft(e.target.value)} onBlur={save}\n onKeyDown={(e) => e.key === \"Enter\" && (e.target as HTMLInputElement).blur()} />\n <div className=\"lab\" style={{ marginTop: 14 }}>{optionsLabel}</div>\n <div className=\"agentoptions\">\n {options.map((o) => (\n <button key={o.key} type=\"button\" className={`agentoption${value === o.key ? \" on\" : \"\"}`} aria-pressed={value === o.key} onClick={() => onPick(o.key)}>\n <span className=\"ic\"><Icon name={o.icon} size={15} /></span>\n {o.label}\n </button>\n ))}\n </div>\n {hint && <div className=\"hint\" style={{ marginTop: 10 }}>{hint}</div>}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -8,6 +8,7 @@ export declare const resources: {
8
8
  close: string;
9
9
  delete: string;
10
10
  remove: string;
11
+ agentName: string;
11
12
  topbar: {
12
13
  sections: string;
13
14
  profile: string;
@@ -62,6 +63,7 @@ export declare const resources: {
62
63
  close: string;
63
64
  delete: string;
64
65
  remove: string;
66
+ agentName: string;
65
67
  topbar: {
66
68
  sections: string;
67
69
  profile: string;
@@ -116,6 +118,7 @@ export declare const resources: {
116
118
  close: string;
117
119
  delete: string;
118
120
  remove: string;
121
+ agentName: string;
119
122
  topbar: {
120
123
  sections: string;
121
124
  profile: string;
@@ -170,6 +173,7 @@ export declare const resources: {
170
173
  close: string;
171
174
  delete: string;
172
175
  remove: string;
176
+ agentName: string;
173
177
  topbar: {
174
178
  sections: string;
175
179
  profile: string;
@@ -224,6 +228,7 @@ export declare const resources: {
224
228
  close: string;
225
229
  delete: string;
226
230
  remove: string;
231
+ agentName: string;
227
232
  topbar: {
228
233
  sections: string;
229
234
  profile: string;
package/dist/locales.js CHANGED
@@ -9,6 +9,7 @@ var t = {
9
9
  close: "Close",
10
10
  delete: "Delete",
11
11
  remove: "Remove",
12
+ agentName: "Agent name",
12
13
  topbar: {
13
14
  sections: "Sections",
14
15
  profile: "Profile"
@@ -63,6 +64,7 @@ var t = {
63
64
  close: "Закрыть",
64
65
  delete: "Удалить",
65
66
  remove: "Убрать",
67
+ agentName: "Имя агента",
66
68
  topbar: {
67
69
  sections: "Разделы",
68
70
  profile: "Профиль"
@@ -117,6 +119,7 @@ var t = {
117
119
  close: "Schließen",
118
120
  delete: "Löschen",
119
121
  remove: "Entfernen",
122
+ agentName: "Name des Agenten",
120
123
  topbar: {
121
124
  sections: "Bereiche",
122
125
  profile: "Profil"
@@ -171,6 +174,7 @@ var t = {
171
174
  close: "Cerrar",
172
175
  delete: "Eliminar",
173
176
  remove: "Quitar",
177
+ agentName: "Nombre del agente",
174
178
  topbar: {
175
179
  sections: "Secciones",
176
180
  profile: "Perfil"
@@ -225,6 +229,7 @@ var t = {
225
229
  close: "关闭",
226
230
  delete: "删除",
227
231
  remove: "移除",
232
+ agentName: "智能体名称",
228
233
  topbar: {
229
234
  sections: "栏目",
230
235
  profile: "个人资料"
@@ -1 +1 @@
1
- {"version":3,"file":"locales.js","names":["loading","cancel","create","close","remove","topbar","chat","loading","cancel","create","close","remove","topbar","chat","loading","cancel","create","close","remove","topbar","chat","loading","cancel","create","close","remove","topbar","chat","UIKIT_NS","resources","\"delete\"","\"sections\"","\"profile\"","\"title\"","\"conversations\"","\"conversation\"","\"topics\"","\"archive\"","\"noTopics\"","\"newTopic\"","\"topicTitle\"","\"topicHint\"","\"loadOlder\"","\"emptyTitle\"","\"placeholder\"","\"send\"","\"attach\"","\"typing\"","\"moreSteps\"","\"hideSteps\"","\"unread\"","\"retry\"","\"tooLarge\"","\"stop\"","\"voice\"","\"hold\"","\"recording\"","\"review\"","\"notRecognized\"","\"micDenied\"","\"unsupported\"","\"confirm\"","\"text\"","\"approve\"","\"reject\"","\"wait\"","\"starting\"","\"queued\""],"sources":["../src/locales/en.json","../src/locales/ru.json","../src/locales/de.json","../src/locales/es.json","../src/locales/zh.json","../src/locales/index.ts"],"sourcesContent":["{\n \"loading\": \"Loading…\",\n \"cancel\": \"Cancel\",\n \"create\": \"Create\",\n \"close\": \"Close\",\n \"delete\": \"Delete\",\n \"remove\": \"Remove\",\n \"topbar\": {\n \"sections\": \"Sections\",\n \"profile\": \"Profile\"\n },\n \"chat\": {\n \"title\": \"Chat\",\n \"conversations\": \"Conversations\",\n \"conversation\": \"Conversation\",\n \"topics\": \"Topics\",\n \"archive\": \"Archive\",\n \"noTopics\": \"No topics yet\",\n \"newTopic\": \"New topic\",\n \"topicTitle\": \"Topic\",\n \"topicHint\": \"A topic has its own history and the same memory and space.\",\n \"loadOlder\": \"Earlier messages\",\n \"emptyTitle\": \"Hi, I am {name}\",\n \"placeholder\": \"Message {name}…\",\n \"send\": \"Send\",\n \"attach\": \"Attach files\",\n \"typing\": \"The agent is typing\",\n \"moreSteps\": \"{count, plural, one {# more step} other {# more steps}}\",\n \"hideSteps\": \"Hide steps\",\n \"unread\": \"{count, plural, one {# unread answer} other {# unread answers}}\",\n \"retry\": \"Retry\",\n \"tooLarge\": \"The file is too large.\",\n \"stop\": \"Stop\",\n \"voice\": {\n \"hold\": \"Hold to record\",\n \"stop\": \"Stop recording\",\n \"recording\": \"Recording… {seconds} s\",\n \"review\": \"Check the transcript before sending\",\n \"notRecognized\": \"The speech was not recognized\",\n \"micDenied\": \"No access to the microphone\",\n \"unsupported\": \"Voice input is not supported in this browser\"\n },\n \"confirm\": {\n \"title\": \"Confirm the action\",\n \"text\": \"The agent wants to perform: {action}\",\n \"approve\": \"Execute\",\n \"reject\": \"Reject\"\n },\n \"wait\": {\n \"starting\": \"The agent is starting…\",\n \"queued\": \"{count, plural, =0 {In the queue · you are next} one {In the queue · # ahead of you} other {In the queue · # ahead of you}}\"\n }\n }\n}\n","{\n \"loading\": \"Загрузка…\",\n \"cancel\": \"Отмена\",\n \"create\": \"Создать\",\n \"close\": \"Закрыть\",\n \"delete\": \"Удалить\",\n \"remove\": \"Убрать\",\n \"topbar\": {\n \"sections\": \"Разделы\",\n \"profile\": \"Профиль\"\n },\n \"chat\": {\n \"title\": \"Чат\",\n \"conversations\": \"Разговоры\",\n \"conversation\": \"Разговор\",\n \"topics\": \"Темы\",\n \"archive\": \"Архив\",\n \"noTopics\": \"Тем пока нет\",\n \"newTopic\": \"Новая тема\",\n \"topicTitle\": \"Тема\",\n \"topicHint\": \"У темы своя история, а память и пространство — те же.\",\n \"loadOlder\": \"Более ранние сообщения\",\n \"emptyTitle\": \"Привет, я {name}\",\n \"placeholder\": \"Сообщение для {name}…\",\n \"send\": \"Отправить\",\n \"attach\": \"Прикрепить файлы\",\n \"typing\": \"Агент печатает\",\n \"moreSteps\": \"{count, plural, one {ещё # шаг} few {ещё # шага} many {ещё # шагов} other {ещё # шага}}\",\n \"hideSteps\": \"Скрыть шаги\",\n \"unread\": \"{count, plural, one {# непрочитанный ответ} few {# непрочитанных ответа} many {# непрочитанных ответов} other {# непрочитанных ответа}}\",\n \"retry\": \"Повторить\",\n \"tooLarge\": \"Файл слишком большой.\",\n \"stop\": \"Остановить\",\n \"voice\": {\n \"hold\": \"Удерживайте для записи\",\n \"stop\": \"Остановить запись\",\n \"recording\": \"Запись… {seconds} с\",\n \"review\": \"Проверьте расшифровку перед отправкой\",\n \"notRecognized\": \"Речь не распознана\",\n \"micDenied\": \"Нет доступа к микрофону\",\n \"unsupported\": \"Голосовой ввод не поддерживается этим браузером\"\n },\n \"confirm\": {\n \"title\": \"Подтвердите действие\",\n \"text\": \"Агент хочет выполнить: {action}\",\n \"approve\": \"Выполнить\",\n \"reject\": \"Отклонить\"\n },\n \"wait\": {\n \"starting\": \"Агент запускается…\",\n \"queued\": \"{count, plural, =0 {В очереди · вы следующий} other {В очереди · перед вами #}}\"\n }\n }\n}\n","{\n \"loading\": \"Wird geladen…\",\n \"cancel\": \"Abbrechen\",\n \"create\": \"Erstellen\",\n \"close\": \"Schließen\",\n \"delete\": \"Löschen\",\n \"remove\": \"Entfernen\",\n \"topbar\": {\n \"sections\": \"Bereiche\",\n \"profile\": \"Profil\"\n },\n \"chat\": {\n \"title\": \"Chat\",\n \"conversations\": \"Gespräche\",\n \"conversation\": \"Gespräch\",\n \"topics\": \"Themen\",\n \"archive\": \"Archiv\",\n \"noTopics\": \"Noch keine Themen\",\n \"newTopic\": \"Neues Thema\",\n \"topicTitle\": \"Thema\",\n \"topicHint\": \"Ein Thema hat einen eigenen Verlauf, aber dasselbe Gedächtnis und denselben Arbeitsbereich.\",\n \"loadOlder\": \"Frühere Nachrichten\",\n \"emptyTitle\": \"Hallo, ich bin {name}\",\n \"placeholder\": \"Nachricht an {name}…\",\n \"send\": \"Senden\",\n \"attach\": \"Dateien anhängen\",\n \"typing\": \"Der Agent schreibt\",\n \"moreSteps\": \"{count, plural, one {# weiterer Schritt} other {# weitere Schritte}}\",\n \"hideSteps\": \"Schritte ausblenden\",\n \"unread\": \"{count, plural, one {# ungelesene Antwort} other {# ungelesene Antworten}}\",\n \"retry\": \"Wiederholen\",\n \"tooLarge\": \"Die Datei ist zu groß.\",\n \"stop\": \"Stoppen\",\n \"voice\": {\n \"hold\": \"Zum Aufnehmen gedrückt halten\",\n \"stop\": \"Aufnahme beenden\",\n \"recording\": \"Aufnahme… {seconds} s\",\n \"review\": \"Prüfen Sie die Transkription vor dem Senden\",\n \"notRecognized\": \"Die Sprache wurde nicht erkannt\",\n \"micDenied\": \"Kein Zugriff auf das Mikrofon\",\n \"unsupported\": \"Spracheingabe wird von diesem Browser nicht unterstützt\"\n },\n \"confirm\": {\n \"title\": \"Aktion bestätigen\",\n \"text\": \"Der Agent möchte ausführen: {action}\",\n \"approve\": \"Ausführen\",\n \"reject\": \"Ablehnen\"\n },\n \"wait\": {\n \"starting\": \"Der Agent startet…\",\n \"queued\": \"{count, plural, =0 {In der Warteschlange · Sie sind als Nächstes dran} other {In der Warteschlange · # vor Ihnen}}\"\n }\n }\n}\n","{\n \"loading\": \"Cargando…\",\n \"cancel\": \"Cancelar\",\n \"create\": \"Crear\",\n \"close\": \"Cerrar\",\n \"delete\": \"Eliminar\",\n \"remove\": \"Quitar\",\n \"topbar\": {\n \"sections\": \"Secciones\",\n \"profile\": \"Perfil\"\n },\n \"chat\": {\n \"title\": \"Chat\",\n \"conversations\": \"Conversaciones\",\n \"conversation\": \"Conversación\",\n \"topics\": \"Temas\",\n \"archive\": \"Archivo\",\n \"noTopics\": \"Aún no hay temas\",\n \"newTopic\": \"Nuevo tema\",\n \"topicTitle\": \"Tema\",\n \"topicHint\": \"Un tema tiene su propio historial y la misma memoria y espacio.\",\n \"loadOlder\": \"Mensajes anteriores\",\n \"emptyTitle\": \"Hola, soy {name}\",\n \"placeholder\": \"Mensaje para {name}…\",\n \"send\": \"Enviar\",\n \"attach\": \"Adjuntar archivos\",\n \"typing\": \"El agente está escribiendo\",\n \"moreSteps\": \"{count, plural, one {# paso más} other {# pasos más}}\",\n \"hideSteps\": \"Ocultar pasos\",\n \"unread\": \"{count, plural, one {# respuesta sin leer} other {# respuestas sin leer}}\",\n \"retry\": \"Reintentar\",\n \"tooLarge\": \"El archivo es demasiado grande.\",\n \"stop\": \"Detener\",\n \"voice\": {\n \"hold\": \"Mantenga pulsado para grabar\",\n \"stop\": \"Detener la grabación\",\n \"recording\": \"Grabando… {seconds} s\",\n \"review\": \"Revise la transcripción antes de enviar\",\n \"notRecognized\": \"No se reconoció la voz\",\n \"micDenied\": \"Sin acceso al micrófono\",\n \"unsupported\": \"Este navegador no admite la entrada de voz\"\n },\n \"confirm\": {\n \"title\": \"Confirme la acción\",\n \"text\": \"El agente quiere ejecutar: {action}\",\n \"approve\": \"Ejecutar\",\n \"reject\": \"Rechazar\"\n },\n \"wait\": {\n \"starting\": \"El agente se está iniciando…\",\n \"queued\": \"{count, plural, =0 {En cola · usted es el siguiente} other {En cola · # por delante}}\"\n }\n }\n}\n","{\n \"loading\": \"加载中…\",\n \"cancel\": \"取消\",\n \"create\": \"创建\",\n \"close\": \"关闭\",\n \"delete\": \"删除\",\n \"remove\": \"移除\",\n \"topbar\": {\n \"sections\": \"栏目\",\n \"profile\": \"个人资料\"\n },\n \"chat\": {\n \"title\": \"聊天\",\n \"conversations\": \"对话\",\n \"conversation\": \"对话\",\n \"topics\": \"话题\",\n \"archive\": \"归档\",\n \"noTopics\": \"暂无话题\",\n \"newTopic\": \"新话题\",\n \"topicTitle\": \"话题\",\n \"topicHint\": \"话题有独立的历史记录,记忆和空间与主对话相同。\",\n \"loadOlder\": \"更早的消息\",\n \"emptyTitle\": \"你好,我是 {name}\",\n \"placeholder\": \"给 {name} 发消息…\",\n \"send\": \"发送\",\n \"attach\": \"附加文件\",\n \"typing\": \"智能体正在输入\",\n \"moreSteps\": \"{count, plural, other {另外 # 个步骤}}\",\n \"hideSteps\": \"隐藏步骤\",\n \"unread\": \"{count} 条未读回复\",\n \"retry\": \"重试\",\n \"tooLarge\": \"文件太大。\",\n \"stop\": \"停止\",\n \"voice\": {\n \"hold\": \"按住录音\",\n \"stop\": \"停止录音\",\n \"recording\": \"录音中… {seconds} 秒\",\n \"review\": \"发送前请检查转写文本\",\n \"notRecognized\": \"未能识别语音\",\n \"micDenied\": \"无法访问麦克风\",\n \"unsupported\": \"此浏览器不支持语音输入\"\n },\n \"confirm\": {\n \"title\": \"确认操作\",\n \"text\": \"代理想要执行:{action}\",\n \"approve\": \"执行\",\n \"reject\": \"拒绝\"\n },\n \"wait\": {\n \"starting\": \"代理正在启动…\",\n \"queued\": \"{count, plural, =0 {排队中 · 您是下一位} other {排队中 · 前面还有 # 位}}\"\n }\n }\n}\n","import en from \"./en.json\";\nimport ru from \"./ru.json\";\nimport de from \"./de.json\";\nimport es from \"./es.json\";\nimport zh from \"./zh.json\";\n\n/** Strings of the components in the five languages of the products. A product adds\n * them to its i18next instance under the namespace `uikit`. */\nexport const resources = { en, ru, de, es, zh };\nexport type KitLanguage = keyof typeof resources;\nexport { UIKIT_NS } from \"../i18n\";\n"],"mappings":";;;AKQA,IAAa6B,IAAY;CAAE,IAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAA;CAAI,IAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAA;CAAI,IAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAA;CAAI,IAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAA;CAAI,IAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAA;AAAG"}
1
+ {"version":3,"file":"locales.js","names":["loading","cancel","create","close","remove","agentName","topbar","chat","loading","cancel","create","close","remove","agentName","topbar","chat","loading","cancel","create","close","remove","agentName","topbar","chat","loading","cancel","create","close","remove","agentName","topbar","chat","UIKIT_NS","resources","\"delete\"","\"sections\"","\"profile\"","\"title\"","\"conversations\"","\"conversation\"","\"topics\"","\"archive\"","\"noTopics\"","\"newTopic\"","\"topicTitle\"","\"topicHint\"","\"loadOlder\"","\"emptyTitle\"","\"placeholder\"","\"send\"","\"attach\"","\"typing\"","\"moreSteps\"","\"hideSteps\"","\"unread\"","\"retry\"","\"tooLarge\"","\"stop\"","\"voice\"","\"hold\"","\"recording\"","\"review\"","\"notRecognized\"","\"micDenied\"","\"unsupported\"","\"confirm\"","\"text\"","\"approve\"","\"reject\"","\"wait\"","\"starting\"","\"queued\""],"sources":["../src/locales/en.json","../src/locales/ru.json","../src/locales/de.json","../src/locales/es.json","../src/locales/zh.json","../src/locales/index.ts"],"sourcesContent":["{\n \"loading\": \"Loading…\",\n \"cancel\": \"Cancel\",\n \"create\": \"Create\",\n \"close\": \"Close\",\n \"delete\": \"Delete\",\n \"remove\": \"Remove\",\n \"agentName\": \"Agent name\",\n \"topbar\": {\n \"sections\": \"Sections\",\n \"profile\": \"Profile\"\n },\n \"chat\": {\n \"title\": \"Chat\",\n \"conversations\": \"Conversations\",\n \"conversation\": \"Conversation\",\n \"topics\": \"Topics\",\n \"archive\": \"Archive\",\n \"noTopics\": \"No topics yet\",\n \"newTopic\": \"New topic\",\n \"topicTitle\": \"Topic\",\n \"topicHint\": \"A topic has its own history and the same memory and space.\",\n \"loadOlder\": \"Earlier messages\",\n \"emptyTitle\": \"Hi, I am {name}\",\n \"placeholder\": \"Message {name}…\",\n \"send\": \"Send\",\n \"attach\": \"Attach files\",\n \"typing\": \"The agent is typing\",\n \"moreSteps\": \"{count, plural, one {# more step} other {# more steps}}\",\n \"hideSteps\": \"Hide steps\",\n \"unread\": \"{count, plural, one {# unread answer} other {# unread answers}}\",\n \"retry\": \"Retry\",\n \"tooLarge\": \"The file is too large.\",\n \"stop\": \"Stop\",\n \"voice\": {\n \"hold\": \"Hold to record\",\n \"stop\": \"Stop recording\",\n \"recording\": \"Recording… {seconds} s\",\n \"review\": \"Check the transcript before sending\",\n \"notRecognized\": \"The speech was not recognized\",\n \"micDenied\": \"No access to the microphone\",\n \"unsupported\": \"Voice input is not supported in this browser\"\n },\n \"confirm\": {\n \"title\": \"Confirm the action\",\n \"text\": \"The agent wants to perform: {action}\",\n \"approve\": \"Execute\",\n \"reject\": \"Reject\"\n },\n \"wait\": {\n \"starting\": \"The agent is starting…\",\n \"queued\": \"{count, plural, =0 {In the queue · you are next} one {In the queue · # ahead of you} other {In the queue · # ahead of you}}\"\n }\n }\n}\n","{\n \"loading\": \"Загрузка…\",\n \"cancel\": \"Отмена\",\n \"create\": \"Создать\",\n \"close\": \"Закрыть\",\n \"delete\": \"Удалить\",\n \"remove\": \"Убрать\",\n \"agentName\": \"Имя агента\",\n \"topbar\": {\n \"sections\": \"Разделы\",\n \"profile\": \"Профиль\"\n },\n \"chat\": {\n \"title\": \"Чат\",\n \"conversations\": \"Разговоры\",\n \"conversation\": \"Разговор\",\n \"topics\": \"Темы\",\n \"archive\": \"Архив\",\n \"noTopics\": \"Тем пока нет\",\n \"newTopic\": \"Новая тема\",\n \"topicTitle\": \"Тема\",\n \"topicHint\": \"У темы своя история, а память и пространство — те же.\",\n \"loadOlder\": \"Более ранние сообщения\",\n \"emptyTitle\": \"Привет, я {name}\",\n \"placeholder\": \"Сообщение для {name}…\",\n \"send\": \"Отправить\",\n \"attach\": \"Прикрепить файлы\",\n \"typing\": \"Агент печатает\",\n \"moreSteps\": \"{count, plural, one {ещё # шаг} few {ещё # шага} many {ещё # шагов} other {ещё # шага}}\",\n \"hideSteps\": \"Скрыть шаги\",\n \"unread\": \"{count, plural, one {# непрочитанный ответ} few {# непрочитанных ответа} many {# непрочитанных ответов} other {# непрочитанных ответа}}\",\n \"retry\": \"Повторить\",\n \"tooLarge\": \"Файл слишком большой.\",\n \"stop\": \"Остановить\",\n \"voice\": {\n \"hold\": \"Удерживайте для записи\",\n \"stop\": \"Остановить запись\",\n \"recording\": \"Запись… {seconds} с\",\n \"review\": \"Проверьте расшифровку перед отправкой\",\n \"notRecognized\": \"Речь не распознана\",\n \"micDenied\": \"Нет доступа к микрофону\",\n \"unsupported\": \"Голосовой ввод не поддерживается этим браузером\"\n },\n \"confirm\": {\n \"title\": \"Подтвердите действие\",\n \"text\": \"Агент хочет выполнить: {action}\",\n \"approve\": \"Выполнить\",\n \"reject\": \"Отклонить\"\n },\n \"wait\": {\n \"starting\": \"Агент запускается…\",\n \"queued\": \"{count, plural, =0 {В очереди · вы следующий} other {В очереди · перед вами #}}\"\n }\n }\n}\n","{\n \"loading\": \"Wird geladen…\",\n \"cancel\": \"Abbrechen\",\n \"create\": \"Erstellen\",\n \"close\": \"Schließen\",\n \"delete\": \"Löschen\",\n \"remove\": \"Entfernen\",\n \"agentName\": \"Name des Agenten\",\n \"topbar\": {\n \"sections\": \"Bereiche\",\n \"profile\": \"Profil\"\n },\n \"chat\": {\n \"title\": \"Chat\",\n \"conversations\": \"Gespräche\",\n \"conversation\": \"Gespräch\",\n \"topics\": \"Themen\",\n \"archive\": \"Archiv\",\n \"noTopics\": \"Noch keine Themen\",\n \"newTopic\": \"Neues Thema\",\n \"topicTitle\": \"Thema\",\n \"topicHint\": \"Ein Thema hat einen eigenen Verlauf, aber dasselbe Gedächtnis und denselben Arbeitsbereich.\",\n \"loadOlder\": \"Frühere Nachrichten\",\n \"emptyTitle\": \"Hallo, ich bin {name}\",\n \"placeholder\": \"Nachricht an {name}…\",\n \"send\": \"Senden\",\n \"attach\": \"Dateien anhängen\",\n \"typing\": \"Der Agent schreibt\",\n \"moreSteps\": \"{count, plural, one {# weiterer Schritt} other {# weitere Schritte}}\",\n \"hideSteps\": \"Schritte ausblenden\",\n \"unread\": \"{count, plural, one {# ungelesene Antwort} other {# ungelesene Antworten}}\",\n \"retry\": \"Wiederholen\",\n \"tooLarge\": \"Die Datei ist zu groß.\",\n \"stop\": \"Stoppen\",\n \"voice\": {\n \"hold\": \"Zum Aufnehmen gedrückt halten\",\n \"stop\": \"Aufnahme beenden\",\n \"recording\": \"Aufnahme… {seconds} s\",\n \"review\": \"Prüfen Sie die Transkription vor dem Senden\",\n \"notRecognized\": \"Die Sprache wurde nicht erkannt\",\n \"micDenied\": \"Kein Zugriff auf das Mikrofon\",\n \"unsupported\": \"Spracheingabe wird von diesem Browser nicht unterstützt\"\n },\n \"confirm\": {\n \"title\": \"Aktion bestätigen\",\n \"text\": \"Der Agent möchte ausführen: {action}\",\n \"approve\": \"Ausführen\",\n \"reject\": \"Ablehnen\"\n },\n \"wait\": {\n \"starting\": \"Der Agent startet…\",\n \"queued\": \"{count, plural, =0 {In der Warteschlange · Sie sind als Nächstes dran} other {In der Warteschlange · # vor Ihnen}}\"\n }\n }\n}\n","{\n \"loading\": \"Cargando…\",\n \"cancel\": \"Cancelar\",\n \"create\": \"Crear\",\n \"close\": \"Cerrar\",\n \"delete\": \"Eliminar\",\n \"remove\": \"Quitar\",\n \"agentName\": \"Nombre del agente\",\n \"topbar\": {\n \"sections\": \"Secciones\",\n \"profile\": \"Perfil\"\n },\n \"chat\": {\n \"title\": \"Chat\",\n \"conversations\": \"Conversaciones\",\n \"conversation\": \"Conversación\",\n \"topics\": \"Temas\",\n \"archive\": \"Archivo\",\n \"noTopics\": \"Aún no hay temas\",\n \"newTopic\": \"Nuevo tema\",\n \"topicTitle\": \"Tema\",\n \"topicHint\": \"Un tema tiene su propio historial y la misma memoria y espacio.\",\n \"loadOlder\": \"Mensajes anteriores\",\n \"emptyTitle\": \"Hola, soy {name}\",\n \"placeholder\": \"Mensaje para {name}…\",\n \"send\": \"Enviar\",\n \"attach\": \"Adjuntar archivos\",\n \"typing\": \"El agente está escribiendo\",\n \"moreSteps\": \"{count, plural, one {# paso más} other {# pasos más}}\",\n \"hideSteps\": \"Ocultar pasos\",\n \"unread\": \"{count, plural, one {# respuesta sin leer} other {# respuestas sin leer}}\",\n \"retry\": \"Reintentar\",\n \"tooLarge\": \"El archivo es demasiado grande.\",\n \"stop\": \"Detener\",\n \"voice\": {\n \"hold\": \"Mantenga pulsado para grabar\",\n \"stop\": \"Detener la grabación\",\n \"recording\": \"Grabando… {seconds} s\",\n \"review\": \"Revise la transcripción antes de enviar\",\n \"notRecognized\": \"No se reconoció la voz\",\n \"micDenied\": \"Sin acceso al micrófono\",\n \"unsupported\": \"Este navegador no admite la entrada de voz\"\n },\n \"confirm\": {\n \"title\": \"Confirme la acción\",\n \"text\": \"El agente quiere ejecutar: {action}\",\n \"approve\": \"Ejecutar\",\n \"reject\": \"Rechazar\"\n },\n \"wait\": {\n \"starting\": \"El agente se está iniciando…\",\n \"queued\": \"{count, plural, =0 {En cola · usted es el siguiente} other {En cola · # por delante}}\"\n }\n }\n}\n","{\n \"loading\": \"加载中…\",\n \"cancel\": \"取消\",\n \"create\": \"创建\",\n \"close\": \"关闭\",\n \"delete\": \"删除\",\n \"remove\": \"移除\",\n \"agentName\": \"智能体名称\",\n \"topbar\": {\n \"sections\": \"栏目\",\n \"profile\": \"个人资料\"\n },\n \"chat\": {\n \"title\": \"聊天\",\n \"conversations\": \"对话\",\n \"conversation\": \"对话\",\n \"topics\": \"话题\",\n \"archive\": \"归档\",\n \"noTopics\": \"暂无话题\",\n \"newTopic\": \"新话题\",\n \"topicTitle\": \"话题\",\n \"topicHint\": \"话题有独立的历史记录,记忆和空间与主对话相同。\",\n \"loadOlder\": \"更早的消息\",\n \"emptyTitle\": \"你好,我是 {name}\",\n \"placeholder\": \"给 {name} 发消息…\",\n \"send\": \"发送\",\n \"attach\": \"附加文件\",\n \"typing\": \"智能体正在输入\",\n \"moreSteps\": \"{count, plural, other {另外 # 个步骤}}\",\n \"hideSteps\": \"隐藏步骤\",\n \"unread\": \"{count} 条未读回复\",\n \"retry\": \"重试\",\n \"tooLarge\": \"文件太大。\",\n \"stop\": \"停止\",\n \"voice\": {\n \"hold\": \"按住录音\",\n \"stop\": \"停止录音\",\n \"recording\": \"录音中… {seconds} 秒\",\n \"review\": \"发送前请检查转写文本\",\n \"notRecognized\": \"未能识别语音\",\n \"micDenied\": \"无法访问麦克风\",\n \"unsupported\": \"此浏览器不支持语音输入\"\n },\n \"confirm\": {\n \"title\": \"确认操作\",\n \"text\": \"代理想要执行:{action}\",\n \"approve\": \"执行\",\n \"reject\": \"拒绝\"\n },\n \"wait\": {\n \"starting\": \"代理正在启动…\",\n \"queued\": \"{count, plural, =0 {排队中 · 您是下一位} other {排队中 · 前面还有 # 位}}\"\n }\n }\n}\n","import en from \"./en.json\";\nimport ru from \"./ru.json\";\nimport de from \"./de.json\";\nimport es from \"./es.json\";\nimport zh from \"./zh.json\";\n\n/** Strings of the components in the five languages of the products. A product adds\n * them to its i18next instance under the namespace `uikit`. */\nexport const resources = { en, ru, de, es, zh };\nexport type KitLanguage = keyof typeof resources;\nexport { UIKIT_NS } from \"../i18n\";\n"],"mappings":";;;AKQA,IAAaiC,IAAY;CAAE,IAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAA;CAAI,IAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAA;CAAI,IAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAA;CAAI,IAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAA;CAAI,IAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAAA;AAAG"}
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.1.4",
2
+ "version": "1.0.1",
3
3
  "classes": [
4
4
  "a",
5
5
  "action",
@@ -7,6 +7,10 @@
7
7
  "acts",
8
8
  "admin",
9
9
  "agent",
10
+ "agentbtn",
11
+ "agentmenu",
12
+ "agentoption",
13
+ "agentoptions",
10
14
  "amber",
11
15
  "app",
12
16
  "arch",
@@ -148,6 +152,10 @@
148
152
  ],
149
153
  "roots": [
150
154
  "admin",
155
+ "agentbtn",
156
+ "agentmenu",
157
+ "agentoption",
158
+ "agentoptions",
151
159
  "app",
152
160
  "avatar",
153
161
  "brand",
package/dist/styles.css CHANGED
@@ -1222,7 +1222,9 @@ select.inp.inherit {
1222
1222
  .ok-t{color:var(--green)}
1223
1223
  .err-t{color:var(--red)}
1224
1224
  .warn-t{color:var(--amber)}
1225
- .page{padding:24px 28px;max-width:1180px;width:100%;margin:0 auto;overflow-y:auto}
1225
+ /* The page scrolls across the whole width, so its scrollbar stands at the edge of the window;
1226
+ the content keeps its column of 1180px (1320px when wide) by the side padding. */
1227
+ .page{padding:24px max(28px,calc((100% - 1180px) / 2 + 28px));width:100%;overflow-y:auto}
1226
1228
  .page h1{font-size:22px;font-weight:600;margin:0 0 4px;letter-spacing:-.01em}
1227
1229
  .page .lead{color:var(--text-2);margin:0 0 18px;font-size:14px}
1228
1230
  .hintbox{display:flex;gap:10px;align-items:flex-start;padding:12px 14px;border-radius:var(--r-m);background:var(--violet-soft);color:var(--violet-text);font-size:14px;margin-bottom:16px}
@@ -1264,7 +1266,7 @@ select.inp.inherit {
1264
1266
  @keyframes nb-spin{to{transform:rotate(360deg)}}
1265
1267
  @media (prefers-reduced-motion:reduce){
1266
1268
  .spin{animation:none}}
1267
- .page.wide{max-width:1320px}
1269
+ .page.wide{padding-inline:max(28px,calc((100% - 1320px) / 2 + 28px))}
1268
1270
  .sel{border:1px solid var(--border-strong);border-radius:var(--r-m);padding:6px 8px;font-size:13px;background:var(--surface);color:var(--text);max-width:200px}
1269
1271
  .panel{border:1px solid var(--border);border-radius:var(--r-m);background:var(--surface);box-shadow:var(--shadow);padding:16px;font-size:14px;position:sticky;top:0;max-height:calc(100vh - 150px);overflow-y:auto}
1270
1272
  .panel-h{display:flex;align-items:flex-start;gap:6px}
@@ -1279,6 +1281,22 @@ select.inp.inherit {
1279
1281
  .panel{position:fixed;inset:0;z-index:40;max-height:none;border-radius:0;border:0}
1280
1282
  .kvrow{grid-template-columns:minmax(0,1fr)}}
1281
1283
 
1284
+ /* The agent: its round button and its menu — the name and the options of the product as tiles. */
1285
+ .agentbtn{width:38px;height:38px;border-radius:50%;display:grid;place-items:center;color:#fff;background:linear-gradient(135deg,var(--violet),#12A876);border:0;cursor:pointer;box-shadow:0 0 0 3px var(--violet-soft);flex:none}
1286
+ .agentbtn.s{width:28px;height:28px;box-shadow:none}
1287
+ .agentmenu{position:absolute;top:62px;left:16px;width:340px;max-width:calc(100% - 32px);background:var(--surface);border:1px solid var(--border);border-radius:var(--r-l);box-shadow:var(--shadow-l, 0 12px 32px rgba(0,0,0,.18));padding:14px;z-index:20}
1288
+ .agentmenu .lab{font-size:12px;color:var(--muted);margin:0 0 6px}
1289
+ .agentoptions{display:grid;grid-template-columns:1fr 1fr;gap:8px}
1290
+ /* a narrow place — a side panel: the tiles go in one column, or a long label would not fit */
1291
+ .agentmenu.narrow .agentoptions{grid-template-columns:1fr}
1292
+ .agentoption{display:flex;align-items:center;gap:8px;padding:9px 10px;border:1px solid var(--border);border-radius:var(--r-m);font-size:14px;background:var(--surface);color:var(--text)}
1293
+ .agentoption.on{border-color:var(--violet);box-shadow:0 0 0 1px var(--violet);color:var(--violet-text)}
1294
+ .agentoption .ic{width:26px;height:26px;border-radius:50%;display:grid;place-items:center;color:#fff;background:linear-gradient(135deg,var(--violet),#12A876);flex:none}
1295
+
1296
+ /* A long unbreakable line — a path in a tool step — never widens the list of messages. */
1297
+ .msgs{overflow-x:hidden}
1298
+ .m{min-width:0}
1299
+
1282
1300
  /* A narrow place — a side panel: the same as on a phone, asked for by the product
1283
1301
  with ChatLayout narrow (FTR.NAB.CMN-0005 design §6.2). */
1284
1302
  .nb-app.narrow{grid-template-columns:1fr}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@green-on-grey/ui-kit",
3
- "version": "0.1.4",
3
+ "version": "1.0.1",
4
4
  "description": "UI kit of the Nabu and Hammurapi products: styles, components, the chat and the sign-in screen",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {