@green-on-grey/ui-kit 0.0.0-stage → 0.1.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/LICENSE +201 -0
- package/README.md +75 -2
- package/bin/ui-kit-check.d.mts +6 -0
- package/bin/ui-kit-check.mjs +130 -0
- package/dist/chat/Chat.d.ts +5 -0
- package/dist/chat/ConversationList.d.ts +19 -0
- package/dist/chat/MessageView.d.ts +11 -0
- package/dist/chat/index.d.ts +4 -0
- package/dist/chat/types.d.ts +104 -0
- package/dist/chat/useRecorder.d.ts +9 -0
- package/dist/chat.js +575 -0
- package/dist/chat.js.map +1 -0
- package/dist/components/Button.d.ts +15 -0
- package/dist/components/Icon.d.ts +83 -0
- package/dist/components/Markdown.d.ts +3 -0
- package/dist/components/Menu.d.ts +10 -0
- package/dist/components/Tabs.d.ts +11 -0
- package/dist/components/TopBar.d.ts +32 -0
- package/dist/components/ui.d.ts +50 -0
- package/dist/i18n-BJ8rsBp6.js +7 -0
- package/dist/i18n-BJ8rsBp6.js.map +1 -0
- package/dist/i18n.d.ts +5 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +144 -0
- package/dist/index.js.map +1 -0
- package/dist/locales/index.d.ts +276 -0
- package/dist/locales.js +279 -0
- package/dist/locales.js.map +1 -0
- package/dist/login/LoginScreen.d.ts +24 -0
- package/dist/login/index.d.ts +1 -0
- package/dist/login.js +48 -0
- package/dist/login.js.map +1 -0
- package/dist/manifest.json +266 -0
- package/dist/styles.css +1288 -0
- package/dist/tokens.css +80 -0
- package/dist/ui-CGgZtEVm.js +307 -0
- package/dist/ui-CGgZtEVm.js.map +1 -0
- package/package.json +75 -4
package/dist/chat.js
ADDED
|
@@ -0,0 +1,575 @@
|
|
|
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";
|
|
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), {
|
|
10
|
+
hour: "2-digit",
|
|
11
|
+
minute: "2-digit"
|
|
12
|
+
}).format(new Date(e));
|
|
13
|
+
}
|
|
14
|
+
function _(e, t) {
|
|
15
|
+
return t || e.length <= 3 ? e : e.slice(-2);
|
|
16
|
+
}
|
|
17
|
+
var v = {
|
|
18
|
+
done: {
|
|
19
|
+
icon: "check",
|
|
20
|
+
cls: "ok"
|
|
21
|
+
},
|
|
22
|
+
error: {
|
|
23
|
+
icon: "alert",
|
|
24
|
+
cls: "er"
|
|
25
|
+
},
|
|
26
|
+
running: {
|
|
27
|
+
icon: "clock",
|
|
28
|
+
cls: "run"
|
|
29
|
+
}
|
|
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", {
|
|
34
|
+
className: "sysnote",
|
|
35
|
+
children: e.text
|
|
36
|
+
});
|
|
37
|
+
let h = e.mark && /* @__PURE__ */ c("div", {
|
|
38
|
+
className: "chan",
|
|
39
|
+
children: [
|
|
40
|
+
/* @__PURE__ */ s(t, {
|
|
41
|
+
name: e.mark.icon,
|
|
42
|
+
size: 12
|
|
43
|
+
}),
|
|
44
|
+
" ",
|
|
45
|
+
e.mark.text,
|
|
46
|
+
" · ",
|
|
47
|
+
g(e.createdAt, u.language)
|
|
48
|
+
]
|
|
49
|
+
}), y = e.attachments && e.attachments.length > 0 && /* @__PURE__ */ s("div", {
|
|
50
|
+
className: "atts",
|
|
51
|
+
children: e.attachments.map((e) => /* @__PURE__ */ c("a", {
|
|
52
|
+
href: e.href,
|
|
53
|
+
children: [
|
|
54
|
+
/* @__PURE__ */ s(t, {
|
|
55
|
+
name: "clip",
|
|
56
|
+
size: 12
|
|
57
|
+
}),
|
|
58
|
+
" ",
|
|
59
|
+
e.fileName
|
|
60
|
+
]
|
|
61
|
+
}, e.id))
|
|
62
|
+
});
|
|
63
|
+
if (e.role === "user") return /* @__PURE__ */ c("div", {
|
|
64
|
+
className: "m u",
|
|
65
|
+
children: [
|
|
66
|
+
h,
|
|
67
|
+
e.content,
|
|
68
|
+
e.text && /* @__PURE__ */ s("div", {
|
|
69
|
+
className: "bub",
|
|
70
|
+
children: e.text
|
|
71
|
+
}),
|
|
72
|
+
y,
|
|
73
|
+
e.wait && /* @__PURE__ */ s("div", {
|
|
74
|
+
className: `turnstate${e.wait.state === "queued" ? " q" : ""}`,
|
|
75
|
+
role: "status",
|
|
76
|
+
children: e.wait.state === "queued" ? /* @__PURE__ */ c(o, { children: [/* @__PURE__ */ s(t, {
|
|
77
|
+
name: "clock",
|
|
78
|
+
size: 12
|
|
79
|
+
}), l("chat.wait.queued", { count: e.wait.position })] }) : /* @__PURE__ */ c(o, { children: [/* @__PURE__ */ s("span", {
|
|
80
|
+
className: "spin",
|
|
81
|
+
"aria-hidden": "true"
|
|
82
|
+
}), l("chat.wait.starting")] })
|
|
83
|
+
})
|
|
84
|
+
]
|
|
85
|
+
});
|
|
86
|
+
let b = e.steps ?? [], x = (e.status === "streaming" || e.status === "pending") && !e.text && !e.content;
|
|
87
|
+
return /* @__PURE__ */ c("div", {
|
|
88
|
+
className: "m a",
|
|
89
|
+
children: [
|
|
90
|
+
h,
|
|
91
|
+
b.length > 3 && /* @__PURE__ */ s("button", {
|
|
92
|
+
className: "tools-fold",
|
|
93
|
+
onClick: () => f((e) => !e),
|
|
94
|
+
"aria-expanded": d,
|
|
95
|
+
children: d ? l("chat.hideSteps") : l("chat.moreSteps", { count: b.length - 2 })
|
|
96
|
+
}),
|
|
97
|
+
_(b, d).map((e) => /* @__PURE__ */ c("div", {
|
|
98
|
+
className: "tool",
|
|
99
|
+
title: e.title,
|
|
100
|
+
children: [/* @__PURE__ */ s(t, {
|
|
101
|
+
name: v[e.status].icon,
|
|
102
|
+
size: 14,
|
|
103
|
+
className: v[e.status].cls
|
|
104
|
+
}), /* @__PURE__ */ s("span", {
|
|
105
|
+
className: "tx",
|
|
106
|
+
children: e.text
|
|
107
|
+
})]
|
|
108
|
+
}, e.id)),
|
|
109
|
+
x && /* @__PURE__ */ s("div", {
|
|
110
|
+
className: "bub",
|
|
111
|
+
children: /* @__PURE__ */ c("span", {
|
|
112
|
+
className: "typing",
|
|
113
|
+
"aria-label": l("chat.typing"),
|
|
114
|
+
children: [
|
|
115
|
+
/* @__PURE__ */ s("i", {}),
|
|
116
|
+
/* @__PURE__ */ s("i", {}),
|
|
117
|
+
/* @__PURE__ */ s("i", {})
|
|
118
|
+
]
|
|
119
|
+
})
|
|
120
|
+
}),
|
|
121
|
+
e.content ?? (e.text && /* @__PURE__ */ s("div", {
|
|
122
|
+
className: "bub",
|
|
123
|
+
children: /* @__PURE__ */ s(r, { text: e.text })
|
|
124
|
+
})),
|
|
125
|
+
y,
|
|
126
|
+
e.error && /* @__PURE__ */ c("div", {
|
|
127
|
+
className: `llmerr${e.error.transient ? " amber" : ""}`,
|
|
128
|
+
role: "alert",
|
|
129
|
+
style: { marginTop: 6 },
|
|
130
|
+
children: [/* @__PURE__ */ s("span", { children: e.error.text }), i && /* @__PURE__ */ s("div", {
|
|
131
|
+
className: "row",
|
|
132
|
+
children: /* @__PURE__ */ c("button", {
|
|
133
|
+
className: "btn sm",
|
|
134
|
+
disabled: n,
|
|
135
|
+
onClick: i,
|
|
136
|
+
children: [/* @__PURE__ */ s(t, {
|
|
137
|
+
name: "refresh",
|
|
138
|
+
size: 14
|
|
139
|
+
}), l("chat.retry")]
|
|
140
|
+
})
|
|
141
|
+
})]
|
|
142
|
+
})
|
|
143
|
+
]
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
//#endregion
|
|
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 () => {
|
|
152
|
+
if (e && !s.current) {
|
|
153
|
+
o(!1);
|
|
154
|
+
try {
|
|
155
|
+
let e = await navigator.mediaDevices.getUserMedia({ audio: !0 }), t = new MediaRecorder(e);
|
|
156
|
+
c.current = [], t.ondataavailable = (e) => e.data.size > 0 && c.current.push(e.data), t.onstop = () => {
|
|
157
|
+
let e = c.current.length ? new Blob(c.current, { type: t.mimeType || "audio/webm" }) : null;
|
|
158
|
+
h(), m.current?.(e), m.current = null;
|
|
159
|
+
}, s.current = t, t.start(), i(0), n(!0);
|
|
160
|
+
let r = Date.now();
|
|
161
|
+
d.current = window.setInterval(() => i(Math.floor((Date.now() - r) / 1e3)), 250);
|
|
162
|
+
} catch {
|
|
163
|
+
o(!0), h();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}, [e, h]), _ = l(() => new Promise((e) => {
|
|
167
|
+
let t = s.current;
|
|
168
|
+
if (!t || t.state === "inactive") return e(null);
|
|
169
|
+
m.current = e, t.stop();
|
|
170
|
+
}), []);
|
|
171
|
+
return u(() => () => h(), [h]), {
|
|
172
|
+
supported: e,
|
|
173
|
+
recording: t,
|
|
174
|
+
seconds: `${Math.floor(r / 60)}:${String(r % 60).padStart(2, "0")}`,
|
|
175
|
+
error: a,
|
|
176
|
+
start: g,
|
|
177
|
+
stop: _
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
//#endregion
|
|
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?.text.length, A = O?.steps?.length, j = f({ height: 0 });
|
|
184
|
+
d(() => {
|
|
185
|
+
let e = S.current;
|
|
186
|
+
if (!e) return;
|
|
187
|
+
let t = j.current;
|
|
188
|
+
e.scrollTop = t.first !== void 0 && D !== t.first && T.some((e) => e.id === t.first) ? e.scrollTop + (e.scrollHeight - t.height) : e.scrollHeight, j.current = {
|
|
189
|
+
first: D,
|
|
190
|
+
height: e.scrollHeight
|
|
191
|
+
};
|
|
192
|
+
}, [
|
|
193
|
+
T.length,
|
|
194
|
+
D,
|
|
195
|
+
k,
|
|
196
|
+
A
|
|
197
|
+
]);
|
|
198
|
+
let M = async (e, t) => {
|
|
199
|
+
let r = e.trim();
|
|
200
|
+
if (!(!r && m.length === 0 || E || n.readOnly)) try {
|
|
201
|
+
await n.onSend(r, m.map((e) => e.id), t), u(""), h([]), x(null);
|
|
202
|
+
} catch {}
|
|
203
|
+
}, N = async (e) => {
|
|
204
|
+
if (e?.length && n.onUpload) {
|
|
205
|
+
_(!0);
|
|
206
|
+
for (let t of Array.from(e)) {
|
|
207
|
+
if (n.upload && t.size > n.upload.maxBytes) {
|
|
208
|
+
a({
|
|
209
|
+
kind: "error",
|
|
210
|
+
title: r("chat.tooLarge"),
|
|
211
|
+
text: t.name
|
|
212
|
+
});
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
let e = await n.onUpload(t).catch(() => null);
|
|
216
|
+
e && h((t) => [...t, e]);
|
|
217
|
+
}
|
|
218
|
+
_(!1), C.current && (C.current.value = "");
|
|
219
|
+
}
|
|
220
|
+
}, P = async () => {
|
|
221
|
+
let e = await w.stop();
|
|
222
|
+
if (!e || !n.onTranscribe) return;
|
|
223
|
+
let t = await n.onTranscribe(e).catch(() => null);
|
|
224
|
+
t !== null && (t ? x(t) : a({
|
|
225
|
+
kind: "error",
|
|
226
|
+
title: r("chat.voice.notRecognized")
|
|
227
|
+
}));
|
|
228
|
+
}, F = (e) => {
|
|
229
|
+
(e.key === " " || e.key === "Enter") && (e.preventDefault(), !e.repeat && (w.recording ? P() : w.start()));
|
|
230
|
+
}, I = r("chat.placeholder", { name: n.agentName });
|
|
231
|
+
return /* @__PURE__ */ c("section", {
|
|
232
|
+
className: "nb-chat",
|
|
233
|
+
"aria-label": r("chat.title"),
|
|
234
|
+
children: [
|
|
235
|
+
n.header,
|
|
236
|
+
/* @__PURE__ */ c("div", {
|
|
237
|
+
className: "msgs",
|
|
238
|
+
ref: S,
|
|
239
|
+
"aria-live": "polite",
|
|
240
|
+
children: [
|
|
241
|
+
n.hasOlder && n.onLoadOlder && /* @__PURE__ */ s("button", {
|
|
242
|
+
className: "btn ghost sm",
|
|
243
|
+
style: { alignSelf: "center" },
|
|
244
|
+
disabled: n.loadingOlder,
|
|
245
|
+
onClick: n.onLoadOlder,
|
|
246
|
+
children: r("chat.loadOlder")
|
|
247
|
+
}),
|
|
248
|
+
n.historyError && /* @__PURE__ */ s("div", {
|
|
249
|
+
className: "err-text",
|
|
250
|
+
children: n.historyError
|
|
251
|
+
}),
|
|
252
|
+
T.length === 0 && !n.loading && !n.historyError && /* @__PURE__ */ c("div", {
|
|
253
|
+
className: "empty",
|
|
254
|
+
children: [
|
|
255
|
+
/* @__PURE__ */ s("div", {
|
|
256
|
+
className: "ic",
|
|
257
|
+
children: /* @__PURE__ */ s(t, { name: "spark" })
|
|
258
|
+
}),
|
|
259
|
+
/* @__PURE__ */ s("b", { children: r("chat.emptyTitle", { name: n.agentName }) }),
|
|
260
|
+
n.emptyHint
|
|
261
|
+
]
|
|
262
|
+
}),
|
|
263
|
+
T.map((e) => /* @__PURE__ */ s(y, {
|
|
264
|
+
m: e,
|
|
265
|
+
busy: E,
|
|
266
|
+
onRetry: n.onRetry && (() => n.onRetry?.(e.id))
|
|
267
|
+
}, e.id)),
|
|
268
|
+
n.onConfirm && (n.confirmations ?? []).map((e) => /* @__PURE__ */ c("div", {
|
|
269
|
+
className: "confirm",
|
|
270
|
+
role: "group",
|
|
271
|
+
"aria-label": r("chat.confirm.title"),
|
|
272
|
+
children: [
|
|
273
|
+
/* @__PURE__ */ c("div", {
|
|
274
|
+
className: "h",
|
|
275
|
+
children: [/* @__PURE__ */ s(t, {
|
|
276
|
+
name: "alert",
|
|
277
|
+
size: 16
|
|
278
|
+
}), r("chat.confirm.title")]
|
|
279
|
+
}),
|
|
280
|
+
/* @__PURE__ */ s("div", { children: r("chat.confirm.text", { action: e.summary }) }),
|
|
281
|
+
e.preview !== void 0 && /* @__PURE__ */ s("pre", { children: e.preview }),
|
|
282
|
+
/* @__PURE__ */ c("div", {
|
|
283
|
+
className: "acts",
|
|
284
|
+
children: [/* @__PURE__ */ s("button", {
|
|
285
|
+
className: "btn sm primary",
|
|
286
|
+
disabled: E,
|
|
287
|
+
onClick: () => n.onConfirm?.(e.id, !0),
|
|
288
|
+
children: r("chat.confirm.approve")
|
|
289
|
+
}), /* @__PURE__ */ s("button", {
|
|
290
|
+
className: "btn sm",
|
|
291
|
+
disabled: E,
|
|
292
|
+
onClick: () => n.onConfirm?.(e.id, !1),
|
|
293
|
+
children: r("chat.confirm.reject")
|
|
294
|
+
})]
|
|
295
|
+
})
|
|
296
|
+
]
|
|
297
|
+
}, e.id))
|
|
298
|
+
]
|
|
299
|
+
}),
|
|
300
|
+
n.readOnly ? /* @__PURE__ */ s("div", {
|
|
301
|
+
className: "composer",
|
|
302
|
+
style: {
|
|
303
|
+
justifyContent: "space-between",
|
|
304
|
+
alignItems: "center"
|
|
305
|
+
},
|
|
306
|
+
children: n.readOnly
|
|
307
|
+
}) : v === null ? /* @__PURE__ */ c(o, { children: [
|
|
308
|
+
n.aboveComposer,
|
|
309
|
+
m.length > 0 && /* @__PURE__ */ s("div", {
|
|
310
|
+
className: "pending-files",
|
|
311
|
+
children: m.map((e) => /* @__PURE__ */ c("span", {
|
|
312
|
+
className: "chip",
|
|
313
|
+
children: [
|
|
314
|
+
/* @__PURE__ */ s(t, {
|
|
315
|
+
name: "clip",
|
|
316
|
+
size: 12
|
|
317
|
+
}),
|
|
318
|
+
e.fileName,
|
|
319
|
+
/* @__PURE__ */ s("button", {
|
|
320
|
+
className: "iconbtn",
|
|
321
|
+
style: {
|
|
322
|
+
width: 18,
|
|
323
|
+
height: 18
|
|
324
|
+
},
|
|
325
|
+
"aria-label": r("remove"),
|
|
326
|
+
onClick: () => h((t) => t.filter((t) => t.id !== e.id)),
|
|
327
|
+
children: /* @__PURE__ */ s(t, {
|
|
328
|
+
name: "x",
|
|
329
|
+
size: 12
|
|
330
|
+
})
|
|
331
|
+
})
|
|
332
|
+
]
|
|
333
|
+
}, e.id))
|
|
334
|
+
}),
|
|
335
|
+
/* @__PURE__ */ c("div", {
|
|
336
|
+
className: "composer",
|
|
337
|
+
children: [
|
|
338
|
+
n.onUpload && /* @__PURE__ */ c(o, { children: [/* @__PURE__ */ s("button", {
|
|
339
|
+
className: "iconbtn",
|
|
340
|
+
"aria-label": r("chat.attach"),
|
|
341
|
+
disabled: g,
|
|
342
|
+
onClick: () => C.current?.click(),
|
|
343
|
+
children: /* @__PURE__ */ s(t, { name: "clip" })
|
|
344
|
+
}), /* @__PURE__ */ s("input", {
|
|
345
|
+
ref: C,
|
|
346
|
+
type: "file",
|
|
347
|
+
multiple: !0,
|
|
348
|
+
hidden: !0,
|
|
349
|
+
accept: n.upload?.accept,
|
|
350
|
+
onChange: (e) => N(e.target.files)
|
|
351
|
+
})] }),
|
|
352
|
+
w.recording ? /* @__PURE__ */ s("div", {
|
|
353
|
+
className: "rec-box",
|
|
354
|
+
children: r("chat.voice.recording", { seconds: w.seconds })
|
|
355
|
+
}) : /* @__PURE__ */ s("textarea", {
|
|
356
|
+
rows: 1,
|
|
357
|
+
value: l,
|
|
358
|
+
placeholder: I,
|
|
359
|
+
"aria-label": I,
|
|
360
|
+
onChange: (e) => u(e.target.value),
|
|
361
|
+
onKeyDown: (e) => {
|
|
362
|
+
e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing && (e.preventDefault(), M(l, !1));
|
|
363
|
+
}
|
|
364
|
+
}),
|
|
365
|
+
n.onTranscribe && /* @__PURE__ */ s("button", {
|
|
366
|
+
className: `iconbtn${w.recording ? " rec" : ""}`,
|
|
367
|
+
disabled: !w.supported,
|
|
368
|
+
"aria-label": w.recording ? r("chat.voice.stop") : r("chat.voice.hold"),
|
|
369
|
+
title: w.supported ? r("chat.voice.hold") : r("chat.voice.unsupported"),
|
|
370
|
+
onPointerDown: (e) => {
|
|
371
|
+
e.preventDefault(), w.recording || w.start();
|
|
372
|
+
},
|
|
373
|
+
onPointerUp: () => w.recording && P(),
|
|
374
|
+
onPointerLeave: () => w.recording && P(),
|
|
375
|
+
onKeyDown: F,
|
|
376
|
+
children: /* @__PURE__ */ s(t, { name: "mic" })
|
|
377
|
+
}),
|
|
378
|
+
n.onCancel && E ? /* @__PURE__ */ s("button", {
|
|
379
|
+
className: "iconbtn fill",
|
|
380
|
+
"aria-label": r("chat.stop"),
|
|
381
|
+
onClick: n.onCancel,
|
|
382
|
+
children: /* @__PURE__ */ s(t, { name: "stop" })
|
|
383
|
+
}) : /* @__PURE__ */ s("button", {
|
|
384
|
+
className: "iconbtn fill",
|
|
385
|
+
"aria-label": r("chat.send"),
|
|
386
|
+
disabled: !l.trim() && m.length === 0 || E,
|
|
387
|
+
onClick: () => M(l, !1),
|
|
388
|
+
children: /* @__PURE__ */ s(t, { name: "send" })
|
|
389
|
+
})
|
|
390
|
+
]
|
|
391
|
+
}),
|
|
392
|
+
w.error && /* @__PURE__ */ s("div", {
|
|
393
|
+
className: "err-text",
|
|
394
|
+
style: { padding: "0 12px 8px" },
|
|
395
|
+
children: r("chat.voice.micDenied")
|
|
396
|
+
})
|
|
397
|
+
] }) : /* @__PURE__ */ c("div", {
|
|
398
|
+
className: "transcript",
|
|
399
|
+
style: { margin: 10 },
|
|
400
|
+
children: [
|
|
401
|
+
/* @__PURE__ */ s("div", {
|
|
402
|
+
className: "small muted",
|
|
403
|
+
style: { marginBottom: 4 },
|
|
404
|
+
children: r("chat.voice.review")
|
|
405
|
+
}),
|
|
406
|
+
/* @__PURE__ */ s("textarea", {
|
|
407
|
+
className: "inp",
|
|
408
|
+
rows: 3,
|
|
409
|
+
value: v,
|
|
410
|
+
onChange: (e) => x(e.target.value),
|
|
411
|
+
"aria-label": r("chat.voice.review")
|
|
412
|
+
}),
|
|
413
|
+
/* @__PURE__ */ c("div", {
|
|
414
|
+
className: "row2",
|
|
415
|
+
style: { marginTop: 8 },
|
|
416
|
+
children: [/* @__PURE__ */ s("button", {
|
|
417
|
+
className: "btn ghost sm",
|
|
418
|
+
onClick: () => x(null),
|
|
419
|
+
children: r("delete")
|
|
420
|
+
}), /* @__PURE__ */ s("button", {
|
|
421
|
+
className: "btn primary sm",
|
|
422
|
+
disabled: !v.trim() || E,
|
|
423
|
+
onClick: () => M(v, !0),
|
|
424
|
+
children: r("chat.send")
|
|
425
|
+
})]
|
|
426
|
+
})
|
|
427
|
+
]
|
|
428
|
+
})
|
|
429
|
+
]
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
//#endregion
|
|
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(() => {
|
|
437
|
+
if (!r) return;
|
|
438
|
+
let e = (e) => e.key === "Escape" && l();
|
|
439
|
+
return document.addEventListener("keydown", e), () => document.removeEventListener("keydown", e);
|
|
440
|
+
}, [r, l]);
|
|
441
|
+
let g = (e) => /* @__PURE__ */ c("button", {
|
|
442
|
+
className: `it${e.id === n ? " on" : ""}${e.group === "archive" ? " arch" : ""}`,
|
|
443
|
+
onClick: () => a(e.id),
|
|
444
|
+
"aria-current": e.id === n,
|
|
445
|
+
children: [
|
|
446
|
+
/* @__PURE__ */ s(t, {
|
|
447
|
+
name: e.icon ?? "msg2",
|
|
448
|
+
size: 16
|
|
449
|
+
}),
|
|
450
|
+
/* @__PURE__ */ s("span", {
|
|
451
|
+
className: "nm",
|
|
452
|
+
children: e.title
|
|
453
|
+
}),
|
|
454
|
+
!!e.unread && /* @__PURE__ */ s("span", {
|
|
455
|
+
className: "unread",
|
|
456
|
+
"aria-label": f("chat.unread", { count: e.unread }),
|
|
457
|
+
children: e.unread
|
|
458
|
+
}),
|
|
459
|
+
/* @__PURE__ */ s("span", {
|
|
460
|
+
className: "sub",
|
|
461
|
+
children: e.when
|
|
462
|
+
})
|
|
463
|
+
]
|
|
464
|
+
}, e.id), _ = (t) => e.filter((e) => e.group === t), v = _("topic"), y = _("archive");
|
|
465
|
+
return /* @__PURE__ */ c(o, { children: [
|
|
466
|
+
/* @__PURE__ */ c("nav", {
|
|
467
|
+
className: `nb-side${r ? " open" : ""}`,
|
|
468
|
+
"aria-label": f("chat.conversations"),
|
|
469
|
+
children: [
|
|
470
|
+
d && /* @__PURE__ */ c("button", {
|
|
471
|
+
className: "btn sm",
|
|
472
|
+
style: {
|
|
473
|
+
width: "100%",
|
|
474
|
+
justifyContent: "center"
|
|
475
|
+
},
|
|
476
|
+
onClick: () => h(!0),
|
|
477
|
+
children: [/* @__PURE__ */ s(t, {
|
|
478
|
+
name: "plus",
|
|
479
|
+
size: 15
|
|
480
|
+
}), f("chat.newTopic")]
|
|
481
|
+
}),
|
|
482
|
+
/* @__PURE__ */ s("div", {
|
|
483
|
+
className: "h",
|
|
484
|
+
children: f("chat.conversation")
|
|
485
|
+
}),
|
|
486
|
+
_("main").map(g),
|
|
487
|
+
/* @__PURE__ */ s("div", {
|
|
488
|
+
className: "h",
|
|
489
|
+
children: f("chat.topics")
|
|
490
|
+
}),
|
|
491
|
+
v.length === 0 && /* @__PURE__ */ s("div", {
|
|
492
|
+
className: "small muted",
|
|
493
|
+
style: { padding: "0 10px" },
|
|
494
|
+
children: f("chat.noTopics")
|
|
495
|
+
}),
|
|
496
|
+
v.map(g),
|
|
497
|
+
y.length > 0 && /* @__PURE__ */ s("div", {
|
|
498
|
+
className: "h",
|
|
499
|
+
children: f("chat.archive")
|
|
500
|
+
}),
|
|
501
|
+
y.map(g)
|
|
502
|
+
]
|
|
503
|
+
}),
|
|
504
|
+
r && /* @__PURE__ */ s("button", {
|
|
505
|
+
type: "button",
|
|
506
|
+
className: "nb-scrim",
|
|
507
|
+
"aria-label": f("close"),
|
|
508
|
+
onClick: l
|
|
509
|
+
}),
|
|
510
|
+
m && d && /* @__PURE__ */ s(w, {
|
|
511
|
+
onClose: () => h(!1),
|
|
512
|
+
onCreate: d
|
|
513
|
+
})
|
|
514
|
+
] });
|
|
515
|
+
}
|
|
516
|
+
function C({ narrow: e, children: t }) {
|
|
517
|
+
return /* @__PURE__ */ s("div", {
|
|
518
|
+
className: `nb-app${e ? " narrow" : ""}`,
|
|
519
|
+
children: t
|
|
520
|
+
});
|
|
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);
|
|
526
|
+
try {
|
|
527
|
+
await t(a.trim()), e();
|
|
528
|
+
} catch (e) {
|
|
529
|
+
m(e instanceof Error ? e.message : String(e)), d(!1);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
return /* @__PURE__ */ c(n, {
|
|
534
|
+
title: r("chat.newTopic"),
|
|
535
|
+
onClose: e,
|
|
536
|
+
footer: /* @__PURE__ */ c(o, { children: [/* @__PURE__ */ s("button", {
|
|
537
|
+
className: "btn ghost",
|
|
538
|
+
onClick: e,
|
|
539
|
+
children: r("cancel")
|
|
540
|
+
}), /* @__PURE__ */ s("button", {
|
|
541
|
+
className: "btn primary",
|
|
542
|
+
disabled: !a.trim() || u,
|
|
543
|
+
onClick: h,
|
|
544
|
+
children: r("create")
|
|
545
|
+
})] }),
|
|
546
|
+
children: [/* @__PURE__ */ c("div", {
|
|
547
|
+
className: "field",
|
|
548
|
+
children: [
|
|
549
|
+
/* @__PURE__ */ s("label", {
|
|
550
|
+
htmlFor: "topic-title",
|
|
551
|
+
children: r("chat.topicTitle")
|
|
552
|
+
}),
|
|
553
|
+
/* @__PURE__ */ s("input", {
|
|
554
|
+
id: "topic-title",
|
|
555
|
+
className: "inp",
|
|
556
|
+
value: a,
|
|
557
|
+
maxLength: 120,
|
|
558
|
+
onChange: (e) => l(e.target.value),
|
|
559
|
+
onKeyDown: (e) => e.key === "Enter" && h()
|
|
560
|
+
}),
|
|
561
|
+
/* @__PURE__ */ s("div", {
|
|
562
|
+
className: "hint",
|
|
563
|
+
children: r("chat.topicHint")
|
|
564
|
+
})
|
|
565
|
+
]
|
|
566
|
+
}), f && /* @__PURE__ */ s("div", {
|
|
567
|
+
className: "err-text",
|
|
568
|
+
children: f
|
|
569
|
+
})]
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
//#endregion
|
|
573
|
+
export { x as Chat, C as ChatLayout, S as ConversationList, w as NewTopicModal, b as useRecorder };
|
|
574
|
+
|
|
575
|
+
//# sourceMappingURL=chat.js.map
|
package/dist/chat.js.map
ADDED
|
@@ -0,0 +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","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 tailText = tail?.text.length;\n const tailSteps = tail?.steps?.length;\n // The first message and the height of the list at the last layout: earlier\n // messages loaded above it keep the place of the list.\n const lastLayout = useRef<{ first?: 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 message that was first is still there, lower\n const prepended = was.first !== undefined && first !== was.first && messages.some((m) => m.id === was.first);\n el.scrollTop = prepended ? el.scrollTop + (el.scrollHeight - was.height) : el.scrollHeight;\n lastLayout.current = { first, height: el.scrollHeight };\n }, [messages.length, first, tailText, tailSteps]); // oxlint-disable-line react/exhaustive-effect-dependencies\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 <button className=\"btn sm primary\" disabled={busy} onClick={() => props.onConfirm?.(c.id, true)}>{t(\"chat.confirm.approve\")}</button>\n <button className=\"btn sm\" disabled={busy} 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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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ButtonHTMLAttributes } from "react";
|
|
2
|
+
type Native = ButtonHTMLAttributes<HTMLButtonElement>;
|
|
3
|
+
/** `button.btn` with its variants. The classes stay a legitimate way to use a
|
|
4
|
+
* button (FTR.NAB.CMN-0005 tech, decision 1); the component is for new code. */
|
|
5
|
+
export declare function Button({ variant, size, className, type, ...rest }: Native & {
|
|
6
|
+
variant?: "primary" | "ghost" | "danger";
|
|
7
|
+
size?: "sm";
|
|
8
|
+
}): import("react").JSX.Element;
|
|
9
|
+
/** `button.iconbtn`: a button with an icon only, so the label is required. */
|
|
10
|
+
export declare function IconButton({ label, fill, on, className, type, ...rest }: Omit<Native, "aria-label"> & {
|
|
11
|
+
label: string;
|
|
12
|
+
fill?: boolean;
|
|
13
|
+
on?: boolean;
|
|
14
|
+
}): import("react").JSX.Element;
|
|
15
|
+
export {};
|