@comitor/ui 3.11.0 → 3.12.0

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.
Files changed (88) hide show
  1. package/README.md +10 -5
  2. package/dist/chat/chat-composer.d.ts +36 -0
  3. package/dist/chat/chat-composer.js +85 -0
  4. package/dist/chat/chat-composer.js.map +1 -0
  5. package/dist/chat/chat-constants.d.ts +63 -0
  6. package/dist/chat/chat-constants.js +1 -0
  7. package/dist/chat/chat-constants.js.map +1 -0
  8. package/dist/chat/chat-message.d.ts +51 -0
  9. package/dist/chat/chat-message.js +137 -0
  10. package/dist/chat/chat-message.js.map +1 -0
  11. package/dist/chat/chat-sources.d.ts +25 -0
  12. package/dist/chat/chat-sources.js +97 -0
  13. package/dist/chat/chat-sources.js.map +1 -0
  14. package/dist/chat/chat-steps.d.ts +20 -0
  15. package/dist/chat/chat-steps.js +84 -0
  16. package/dist/chat/chat-steps.js.map +1 -0
  17. package/dist/chat/chat-thread.d.ts +21 -0
  18. package/dist/chat/chat-thread.js +87 -0
  19. package/dist/chat/chat-thread.js.map +1 -0
  20. package/dist/chat/index.d.ts +12 -0
  21. package/dist/chat/index.js +14 -0
  22. package/dist/chat/index.js.map +1 -0
  23. package/dist/components/composite/gantt-constants.d.ts +127 -0
  24. package/dist/components/composite/gantt-constants.js +5 -0
  25. package/dist/components/composite/gantt-constants.js.map +1 -0
  26. package/dist/components/composite/gantt-utils.d.ts +106 -0
  27. package/dist/components/composite/gantt-utils.js +196 -0
  28. package/dist/components/composite/gantt-utils.js.map +1 -0
  29. package/dist/components/composite/gantt.d.ts +93 -0
  30. package/dist/components/composite/gantt.js +1137 -0
  31. package/dist/components/composite/gantt.js.map +1 -0
  32. package/dist/components/composite/stat-card.d.ts +1 -1
  33. package/dist/components/composite/tree-view-constants.d.ts +96 -0
  34. package/dist/components/composite/tree-view-constants.js +85 -0
  35. package/dist/components/composite/tree-view-constants.js.map +1 -0
  36. package/dist/components/composite/tree-view.d.ts +58 -0
  37. package/dist/components/composite/tree-view.js +385 -0
  38. package/dist/components/composite/tree-view.js.map +1 -0
  39. package/dist/components/ui/badge.d.ts +1 -1
  40. package/dist/components/ui/button.d.ts +1 -1
  41. package/dist/data-grid/data-grid-cell-wrapper.d.ts +1 -1
  42. package/dist/data-grid/data-grid-cell-wrapper.js +11 -5
  43. package/dist/data-grid/data-grid-cell-wrapper.js.map +1 -1
  44. package/dist/data-grid/data-grid-column-header.js +12 -12
  45. package/dist/data-grid/data-grid-column-header.js.map +1 -1
  46. package/dist/data-grid/messages-en.d.ts +2 -0
  47. package/dist/data-grid/messages-en.js +3 -1
  48. package/dist/data-grid/messages-en.js.map +1 -1
  49. package/dist/data-grid/messages-vi.js +2 -1
  50. package/dist/data-grid/messages-vi.js.map +1 -1
  51. package/dist/data-grid/messages.d.ts +2 -0
  52. package/dist/data-grid/use-data-grid.d.ts +1 -0
  53. package/dist/data-grid/utils.d.ts +13 -1
  54. package/dist/data-grid/utils.js +13 -0
  55. package/dist/data-grid/utils.js.map +1 -1
  56. package/dist/editor/bubble-toolbar.d.ts +1 -1
  57. package/dist/editor/editor-core.d.ts +2 -2
  58. package/dist/editor/editor-core.js +6 -0
  59. package/dist/editor/editor-core.js.map +1 -1
  60. package/dist/editor/editor-types.d.ts +1 -1
  61. package/dist/editor/fixed-toolbar.d.ts +1 -1
  62. package/dist/editor/image-upload.d.ts +1 -1
  63. package/dist/editor/index.d.ts +1 -1
  64. package/dist/editor/markdown-editor.d.ts +1 -1
  65. package/dist/editor/markdown-extensions.d.ts +3 -1
  66. package/dist/editor/markdown-extensions.js +3 -1
  67. package/dist/editor/markdown-extensions.js.map +1 -1
  68. package/dist/editor/rich-text-editor.d.ts +1 -1
  69. package/dist/editor/slash-commands.d.ts +1 -1
  70. package/dist/editor/toolbar-actions.d.ts +1 -1
  71. package/dist/{editor-types-CSrMeuSw.d.ts → editor-types-BacXD2VN.d.ts} +7 -1
  72. package/dist/i18n/locale-provider.d.ts +3 -0
  73. package/dist/i18n/messages.d.ts +6 -0
  74. package/dist/i18n/messages.js +11 -2
  75. package/dist/i18n/messages.js.map +1 -1
  76. package/dist/i18n/namespaces/chat.d.ts +8 -0
  77. package/dist/i18n/namespaces/chat.js +47 -0
  78. package/dist/i18n/namespaces/chat.js.map +1 -0
  79. package/dist/i18n/namespaces/gantt.d.ts +10 -0
  80. package/dist/i18n/namespaces/gantt.js +57 -0
  81. package/dist/i18n/namespaces/gantt.js.map +1 -0
  82. package/dist/i18n/namespaces/tree-view.d.ts +8 -0
  83. package/dist/i18n/namespaces/tree-view.js +21 -0
  84. package/dist/i18n/namespaces/tree-view.js.map +1 -0
  85. package/dist/index.d.ts +5 -0
  86. package/dist/index.js +4 -0
  87. package/dist/index.js.map +1 -1
  88. package/package.json +9 -2
package/README.md CHANGED
@@ -120,7 +120,7 @@ buộc ở thẻ `<html>`: https://design-system.comitor.ai/shell
120
120
 
121
121
  ## 3. Entry point
122
122
 
123
- Package có **8 entry**, tách theo peer optional mà mỗi tầng kéo theo:
123
+ Package có **9 entry**, tách theo peer optional mà mỗi tầng kéo theo:
124
124
 
125
125
  | Entry | Nội dung | Peer optional kéo theo |
126
126
  |---|---|---|
@@ -130,6 +130,7 @@ Package có **8 entry**, tách theo peer optional mà mỗi tầng kéo theo:
130
130
  | `@comitor/ui/data-grid` | `DataGrid` — lưới sửa trực tiếp kiểu bảng tính: ảo hoá dòng, chọn vùng bằng bàn phím, copy/paste với Excel, ghim cột, lọc/sắp xếp nhiều tiêu chí, hoàn tác/làm lại | `@tanstack/react-table` (peer); kèm `@tanstack/react-virtual` + `@dnd-kit/*` (dependency của gói) |
131
131
  | `@comitor/ui/uploader` | `FileUpload` (tệp tài liệu) · `ImageUploadField` (ảnh đại diện, logo) · `useFileUpload` | `@uppy/core`, `@uppy/xhr-upload` (~13KB gzip) |
132
132
  | `@comitor/ui/editor` | `MarkdownEditor` — trình soạn kiểu Linear, lưu bằng markdown · `RichTextEditor` — lưu HTML, thanh công cụ cố định kiểu CMS. Chung: gõ tắt, menu `/`, `@` nhắc người (và việc, dự án… qua `kind`), ảnh, bảng, khối mã tô màu, `toolbar="bubble" \| "fixed"` | `@tiptap/*` + `lowlight` (~234KB gzip — nạp lười nếu trang không cần ngay) |
133
+ | `@comitor/ui/chat` | Khung hội thoại cho trợ lý AI / tác tử (từ `3.12.0`): `ChatThread` · `ChatMessage` (nhãn "do AI tạo") · `ChatSteps` · `ChatSources` + `ChatCitation` · `ChatComposer` (`@`, Enter gửi). Tin nhắn vẽ markdown bằng chính `MarkdownEditor` chỉ-đọc | cùng bộ peer của `/editor` (`@tiptap/*` + `lowlight`) |
133
134
  | `@comitor/ui/form` | Binding shadcn ⇄ react-hook-form | `react-hook-form` |
134
135
  | `@comitor/ui/tokens` | Token dạng object TS — cho nơi không có Tailwind: canvas, email HTML, PDF | không có |
135
136
 
@@ -176,18 +177,19 @@ https://design-system.comitor.ai/foundations/server-client
176
177
 
177
178
  ## 5. Bảng component
178
179
 
179
- Số file nguồn hiện tại: **79 primitive · 36 composite · 26 shell · 4 chart · 32 data-grid · 5 uploader · 17 editor · 45 i18n**. Mỗi component có trang
180
+ Số file nguồn hiện tại: **79 primitive · 41 composite · 26 shell · 4 chart · 32 data-grid · 5 uploader · 17 editor · 7 chat · 48 i18n**. Mỗi component có trang
180
181
  riêng kèm demo, bảng prop và cạm bẫy của nó:
181
182
 
182
183
  | Tầng | Là gì | Trang |
183
184
  |---|---|---|
184
185
  | **Tier 1 — primitive** · 79 file · `@comitor/ui` | shadcn/ui style `new-york`, đã tùy biến token theo brand Comitor — cộng 18 component port từ Dice UI ở `2.1.0`: `Kanban`, `Stepper`, `Timeline`, `TimePicker`, `TagsInput`, `Mention`, `ActionBar`, `PhoneInput`, `MaskInput`, `Rating`, `CircularProgress`, `Banner`, `ResponsiveDialog`, `Tour`, `KeyValue`, `Cropper`, `ScrollSpy`, `BadgeOverflow` | https://design-system.comitor.ai/components · vì sao chọn chúng, và mọi chỗ đã đổi so với thượng nguồn: [docs/diceui.md](docs/diceui.md) |
185
- | **Tier 2 — composite** · 36 file · `@comitor/ui` | Giá trị thật của gói: pattern lặp ở mọi app, ghép từ nhiều primitive (`DataTable`, `PageHeader`, `Combobox`, `DatePicker`, `StatCard`…). Từ `3.7.0`: phím tắt chuỗi `useKeySequences` + bảng `?` `KeyboardShortcutsDialog` | https://design-system.comitor.ai/composites |
186
+ | **Tier 2 — composite** · 41 file · `@comitor/ui` | Giá trị thật của gói: pattern lặp ở mọi app, ghép từ nhiều primitive (`DataTable`, `PageHeader`, `Combobox`, `DatePicker`, `StatCard`…). Từ `3.7.0`: phím tắt chuỗi `useKeySequences` + bảng `?` `KeyboardShortcutsDialog`. Từ `3.12.0`: **`Gantt`** — trục thời gian / Gantt cho cả lộ trình dự án lẫn việc (thanh theo ngày, mũi tên phụ thuộc, kéo dời/giãn, bàn phím, vạch hôm nay, ngày nghỉ, đường găng, 5 bậc ngày→năm, ảo hoá) và **`TreeView`** — cây dàn ý cho WBS (Tab/Shift+Tab/Alt+↑↓, kéo thả đổi cha, số hiệu `1.2.3`, cột cộng dồn, ảo hoá; lõi Headless Tree). Cả hai CONTROLLED: dữ liệu ra qua callback, gói không giữ nghiệp vụ | https://design-system.comitor.ai/composites · [docs/gantt.md](docs/gantt.md) · [docs/tree-view.md](docs/tree-view.md) |
186
187
  | **Tier 3 — shell** · 26 file · `@comitor/ui/shell` | Khung ứng dụng workspace × app: sidebar, header, theme, ba trục hiển thị (tương phản · mật độ · cỡ chữ). `AppShell` tự có skip link (WCAG 2.4.1) từ `3.7.0`; `DisplayPrefsSeedScript` gieo mặc định hiển thị của tài khoản trước lượt sơn đầu | https://design-system.comitor.ai/shell |
187
188
  | **chart** · 4 file · `@comitor/ui/chart` | Lớp shadcn bọc recharts, cùng ba bản dựng sẵn theo brand. ⚠ Ba wrapper `Comitor*Chart` **mặc định TẮT** hiệu ứng vẽ dần (`isAnimationActive = false`) — bật lại là biểu đồ kẹt ở khung đầu: có trục, có lưới, KHÔNG có dữ liệu. Mặc định đó KHÔNG áp cho `<ChartContainer>` + recharts do app tự lắp | https://design-system.comitor.ai/components/chart |
188
189
  | **data-grid** · 32 file · `@comitor/ui/data-grid` | `DataGrid` — lưới sửa trực tiếp kiểu bảng tính, cho màn nhập liệu hàng loạt. Port từ [tablecn](https://www.tablecn.com/data-grid) (MIT) sang primitive + token của gói. ⚠ Là tầng DUY NHẤT có peer **`@tanstack/react-table`**, có thêm prop `locale` riêng (xem dưới), và mọi thứ trong nó là client component | [docs/data-grid.md](docs/data-grid.md) |
189
190
  | **uploader** · 5 file · `@comitor/ui/uploader` | `FileUpload` (tệp) · `ImageUploadField` (ảnh) · `useFileUpload` — kéo thả, xem trước, tiến độ. Peer optional: `@uppy/core` + `@uppy/xhr-upload` | https://design-system.comitor.ai/components |
190
191
  | **editor** · 17 file · `@comitor/ui/editor` | `MarkdownEditor` — soạn trực tiếp trên bản đã định dạng, giá trị vào/ra là markdown (GFM); `RichTextEditor` — cùng lõi, giá trị là HTML, lược đồ có thêm gạch chân và ô bảng nhiều khối, mặc định thanh cố định. Dựng trên Tiptap 3 (ProseMirror). ⚠ Định dạng LƯU là hợp đồng: nhắc người là `[@Tên](mention:<id>)` / `<span data-type="mention">`, nhắc thực thể khác (từ `3.7.0`, `kind`) là `[@KEY-1](mention:task/<id>)` / thêm `data-kind`, ảnh lưu `src` app trả về (không phải URL đã ký), markdown cố ý không có gạch chân. ⚠ HTML đã lưu hiển thị lại bằng `RichTextEditor editable={false}`, không `dangerouslySetInnerHTML`. Peer optional: bộ `@tiptap/*` + `lowlight` | [docs/editor.md](docs/editor.md) |
192
+ | **chat** · 7 file · `@comitor/ui/chat` | Khung hội thoại: danh sách tin tự bám đáy, bước tiến trình của tác tử (đang làm / xong / lỗi), nguồn trích dẫn bấm được, ô nhập có `@`, chữ trả về dần. ⚠ Gói không giữ hội thoại, không gọi mô hình — app đẩy chữ đang về vào `content` và bật `busy`/`streaming`. Peer optional: như `/editor` | [docs/chat.md](docs/chat.md) |
191
193
  | **form** · `@comitor/ui/form` | Binding shadcn ⇄ react-hook-form | https://design-system.comitor.ai/components/form |
192
194
 
193
195
  **Ngôn ngữ — mặc định TIẾNG ANH, tiếng Việt dựng sẵn (từ `3.0.0`).** Mọi chuỗi hiển thị của gói —
@@ -289,7 +291,7 @@ https://design-system.comitor.ai/foundations/colors
289
291
 
290
292
  Mỗi bản publish đi qua cùng một chuỗi cổng (`prepublishOnly` nối chúng lại): typecheck → build →
291
293
  `verify:directives` → `smoke:entries` → `check:exports` → `test:nav-active` → `test:grid-contract`
292
- → `test:grid-render` → `test:editor-markdown` → `test:a11y-gate` → `lint:a11y`. Những cổng đáng để người dùng gói biết là chúng tồn tại:
294
+ → `test:grid-render` → `test:editor-markdown` → `test:gantt-tree` → `test:a11y-gate` → `lint:a11y`. Những cổng đáng để người dùng gói biết là chúng tồn tại:
293
295
 
294
296
  - **`lint:a11y` — hợp đồng màu.** Quét **toàn bộ** `src/**/*.{ts,tsx}` theo QUY TẮC (không theo danh
295
297
  sách phát hiện) và đối chiếu với `styles.css`: token TÔ NỀN đem làm màu chữ/icon/viền là sai
@@ -309,12 +311,15 @@ Mỗi bản publish đi qua cùng một chuỗi cổng (`prepublishOnly` nối c
309
311
  - **`verify:directives` — ranh giới server/client.** File nguồn nào mở đầu bằng `"use client"` thì
310
312
  file `dist/` tương ứng phải mở đầu y hệt (thiếu file build cũng tính là hỏng), và không hằng dữ
311
313
  liệu thuần nào được kẹt trong file client (§4.2).
312
- - **`smoke:entries`** nạp cả 8 entry bằng Node ESM thuần, qua đúng `exports` map (§3).
314
+ - **`smoke:entries`** nạp cả 9 entry bằng Node ESM thuần, qua đúng `exports` map (§3).
313
315
  - **`test:editor-markdown`** — markdown là định dạng LƯU của `MarkdownEditor`, nên phép đo là tài liệu →
314
316
  markdown → tài liệu phải ra đúng tài liệu ban đầu, trên CHÍNH lược đồ trong `dist/`. Lỗi loại này không lộ
315
317
  ra lúc gõ mà ở lần mở sau. Trên lược đồ gốc của Tiptap (chưa vá) cổng trượt 16/46 — [docs/editor.md](docs/editor.md) §5.
316
318
  Từ `3.6.0` nó canh cả lược đồ HTML của `RichTextEditor`: thứ GHI xuống (`src` gốc, tên chụp) không được lẫn
317
319
  thứ chỉ để VẼ (URL đã ký, tên sống).
320
+ - **`test:gantt-tree`** (từ `3.12.0`) — toán NGÀY của `Gantt` (lệch một ngày không ném, chỉ vẽ thanh lệch một
321
+ ô: cuối tháng, năm nhuận, đầu tuần), luật vi phạm phụ thuộc, đường đi của mũi tên, hợp đồng `moveTreeItems`
322
+ của `TreeView` (index tính SAU khi gỡ mục đang chuyển), cộng dồn, đánh số, và render server của ba tầng mới.
318
323
  - **`test:a11y-gate`** tự kiểm chính cổng màu (cổng còn bắt đủ không); **`test:nav-active`** khoá
319
324
  toàn bộ luật active matching của sidebar dưới dạng hàm thuần (§4.1).
320
325
  - **`test:grid-render`** render THẬT `DataGrid` bằng `react-dom/server` rồi soi HTML ra được. Nó
@@ -0,0 +1,36 @@
1
+ import * as React from 'react';
2
+ import { d as MarkdownEditorMentions } from '../editor-types-BacXD2VN.js';
3
+ import { MarkdownEditorProps, MarkdownEditorHandle } from '../editor/markdown-editor.js';
4
+ import { ChatLabels } from './chat-constants.js';
5
+ import '@tiptap/core';
6
+ import '../components/composite/empty-state.js';
7
+ import '../editor/markdown-editor-constants.js';
8
+
9
+ interface ChatComposerProps extends Omit<React.ComponentProps<"div">, "onSubmit" | "onChange"> {
10
+ /** Markdown đang soạn (controlled). Gửi xong app đặt lại `""`. */
11
+ value: string;
12
+ onChange: (markdown: string) => void;
13
+ /** Gửi: Enter, hoặc nút gửi. Không gọi khi rỗng, khi ảnh còn đang tải, hay khi đang `streaming`. */
14
+ onSubmit: (markdown: string) => void;
15
+ /** Tác tử đang trả lời — nút gửi thành nút DỪNG (gọi `onStop`), Enter không gửi tin mới. */
16
+ streaming?: boolean;
17
+ onStop?: () => void;
18
+ disabled?: boolean;
19
+ placeholder?: string;
20
+ /** Bật `@` — cùng cấu hình `mentions` của `MarkdownEditor`. */
21
+ mentions?: MarkdownEditorMentions;
22
+ /** Hàng công cụ bên trái nút gửi (đính kèm, chọn mô hình…). */
23
+ toolbar?: React.ReactNode;
24
+ /** Prop thêm cho `MarkdownEditor` bên trong (`onImageUpload`, `extensions`…). */
25
+ editorProps?: Omit<MarkdownEditorProps, "value" | "onChange" | "onSubmit" | "submitOnEnter" | "mentions" | "placeholder" | "variant" | "ref">;
26
+ /** Handle của trình soạn (focus, clear, isUploading). */
27
+ editorRef?: React.Ref<MarkdownEditorHandle>;
28
+ labels?: Partial<ChatLabels>;
29
+ }
30
+ /**
31
+ * Ô nhập của khung hội thoại — `MarkdownEditor` (cùng lược đồ, cùng `@` nhắc tên, cùng ảnh dán vào)
32
+ * với Enter để gửi, Shift+Enter xuống dòng, và nút gửi/dừng.
33
+ */
34
+ declare function ChatComposer({ value, onChange, onSubmit, streaming, onStop, disabled, placeholder, mentions, toolbar, editorProps, editorRef, labels, className, ...props }: ChatComposerProps): React.JSX.Element;
35
+
36
+ export { ChatComposer, type ChatComposerProps };
@@ -0,0 +1,85 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { ArrowUp, Square } from "lucide-react";
4
+ import * as React from "react";
5
+ import { Button } from "../components/ui/button.js";
6
+ import { MarkdownEditor } from "../editor/markdown-editor.js";
7
+ import { useComitorLabels } from "../i18n/locale-provider.js";
8
+ import { cn } from "../lib/cn.js";
9
+ function ChatComposer({
10
+ value,
11
+ onChange,
12
+ onSubmit,
13
+ streaming = false,
14
+ onStop,
15
+ disabled = false,
16
+ placeholder,
17
+ mentions,
18
+ toolbar,
19
+ editorProps,
20
+ editorRef,
21
+ labels,
22
+ className,
23
+ ...props
24
+ }) {
25
+ const t = useComitorLabels("chat", labels);
26
+ const innerRef = React.useRef(null);
27
+ React.useImperativeHandle(editorRef, () => innerRef.current, []);
28
+ const empty = value.trim() === "";
29
+ const submit = () => {
30
+ if (disabled || streaming || empty || innerRef.current?.isUploading()) return;
31
+ onSubmit(value);
32
+ };
33
+ return /* @__PURE__ */ jsxs(
34
+ "div",
35
+ {
36
+ "data-slot": "chat-composer",
37
+ "data-disabled": disabled || void 0,
38
+ className: cn(
39
+ "flex flex-col rounded-xl border border-input bg-card shadow-control focus-within:border-control-edge-strong",
40
+ // Khoá thì nền lùi về bậc tắt — KHÔNG dùng `opacity`: khung vẫn phải đọc được viền (lint:a11y).
41
+ disabled && "bg-muted",
42
+ className
43
+ ),
44
+ ...props,
45
+ children: [
46
+ /* @__PURE__ */ jsx(
47
+ MarkdownEditor,
48
+ {
49
+ ...editorProps,
50
+ ref: innerRef,
51
+ variant: "ghost",
52
+ value,
53
+ onChange,
54
+ onSubmit: submit,
55
+ submitOnEnter: true,
56
+ editable: !disabled,
57
+ mentions,
58
+ placeholder: placeholder ?? t.composerPlaceholder,
59
+ "aria-label": editorProps?.["aria-label"] ?? t.composerLabel,
60
+ contentClassName: cn("max-h-48 min-h-10 overflow-y-auto px-3 pt-3 pb-1", editorProps?.contentClassName)
61
+ }
62
+ ),
63
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-2 pb-2", children: [
64
+ toolbar && /* @__PURE__ */ jsx("div", { className: "flex min-w-0 flex-1 items-center gap-1", children: toolbar }),
65
+ /* @__PURE__ */ jsx("div", { className: "ms-auto", children: streaming ? /* @__PURE__ */ jsx(
66
+ Button,
67
+ {
68
+ type: "button",
69
+ variant: "secondary",
70
+ size: "icon-sm",
71
+ "aria-label": t.stop,
72
+ onClick: onStop,
73
+ disabled: !onStop,
74
+ children: /* @__PURE__ */ jsx(Square, { "aria-hidden": "true", className: "size-3.5" })
75
+ }
76
+ ) : /* @__PURE__ */ jsx(Button, { type: "button", size: "icon-sm", "aria-label": t.send, onClick: submit, disabled: disabled || empty, children: /* @__PURE__ */ jsx(ArrowUp, { "aria-hidden": "true", className: "size-4" }) }) })
77
+ ] })
78
+ ]
79
+ }
80
+ );
81
+ }
82
+ export {
83
+ ChatComposer
84
+ };
85
+ //# sourceMappingURL=chat-composer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/chat/chat-composer.tsx"],"sourcesContent":["\"use client\";\n\nimport { ArrowUp, Square } from \"lucide-react\";\nimport * as React from \"react\";\nimport { Button } from \"../components/ui/button.js\";\nimport type { MarkdownEditorMentions } from \"../editor/editor-types.js\";\nimport { MarkdownEditor, type MarkdownEditorHandle, type MarkdownEditorProps } from \"../editor/markdown-editor.js\";\nimport { useComitorLabels } from \"../i18n/locale-provider.js\";\nimport { cn } from \"../lib/cn.js\";\nimport type { ChatLabels } from \"./chat-constants.js\";\n\nexport interface ChatComposerProps extends Omit<React.ComponentProps<\"div\">, \"onSubmit\" | \"onChange\"> {\n /** Markdown đang soạn (controlled). Gửi xong app đặt lại `\"\"`. */\n value: string;\n onChange: (markdown: string) => void;\n /** Gửi: Enter, hoặc nút gửi. Không gọi khi rỗng, khi ảnh còn đang tải, hay khi đang `streaming`. */\n onSubmit: (markdown: string) => void;\n /** Tác tử đang trả lời — nút gửi thành nút DỪNG (gọi `onStop`), Enter không gửi tin mới. */\n streaming?: boolean;\n onStop?: () => void;\n disabled?: boolean;\n placeholder?: string;\n /** Bật `@` — cùng cấu hình `mentions` của `MarkdownEditor`. */\n mentions?: MarkdownEditorMentions;\n /** Hàng công cụ bên trái nút gửi (đính kèm, chọn mô hình…). */\n toolbar?: React.ReactNode;\n /** Prop thêm cho `MarkdownEditor` bên trong (`onImageUpload`, `extensions`…). */\n editorProps?: Omit<\n MarkdownEditorProps,\n \"value\" | \"onChange\" | \"onSubmit\" | \"submitOnEnter\" | \"mentions\" | \"placeholder\" | \"variant\" | \"ref\"\n >;\n /** Handle của trình soạn (focus, clear, isUploading). */\n editorRef?: React.Ref<MarkdownEditorHandle>;\n labels?: Partial<ChatLabels>;\n}\n\n/**\n * Ô nhập của khung hội thoại — `MarkdownEditor` (cùng lược đồ, cùng `@` nhắc tên, cùng ảnh dán vào)\n * với Enter để gửi, Shift+Enter xuống dòng, và nút gửi/dừng.\n */\nexport function ChatComposer({\n value,\n onChange,\n onSubmit,\n streaming = false,\n onStop,\n disabled = false,\n placeholder,\n mentions,\n toolbar,\n editorProps,\n editorRef,\n labels,\n className,\n ...props\n}: ChatComposerProps) {\n const t = useComitorLabels(\"chat\", labels);\n const innerRef = React.useRef<MarkdownEditorHandle>(null);\n React.useImperativeHandle(editorRef, () => innerRef.current as MarkdownEditorHandle, []);\n const empty = value.trim() === \"\";\n\n const submit = () => {\n if (disabled || streaming || empty || innerRef.current?.isUploading()) return;\n onSubmit(value);\n };\n\n return (\n <div\n data-slot=\"chat-composer\"\n data-disabled={disabled || undefined}\n className={cn(\n \"flex flex-col rounded-xl border border-input bg-card shadow-control focus-within:border-control-edge-strong\",\n // Khoá thì nền lùi về bậc tắt — KHÔNG dùng `opacity`: khung vẫn phải đọc được viền (lint:a11y).\n disabled && \"bg-muted\",\n className\n )}\n {...props}\n >\n <MarkdownEditor\n {...editorProps}\n ref={innerRef}\n variant=\"ghost\"\n value={value}\n onChange={onChange}\n onSubmit={submit}\n submitOnEnter\n editable={!disabled}\n mentions={mentions}\n placeholder={placeholder ?? t.composerPlaceholder}\n aria-label={editorProps?.[\"aria-label\"] ?? t.composerLabel}\n contentClassName={cn(\"max-h-48 min-h-10 overflow-y-auto px-3 pt-3 pb-1\", editorProps?.contentClassName)}\n />\n <div className=\"flex items-center gap-2 px-2 pb-2\">\n {toolbar && <div className=\"flex min-w-0 flex-1 items-center gap-1\">{toolbar}</div>}\n <div className=\"ms-auto\">\n {streaming ? (\n <Button\n type=\"button\"\n variant=\"secondary\"\n size=\"icon-sm\"\n aria-label={t.stop}\n onClick={onStop}\n disabled={!onStop}\n >\n <Square aria-hidden=\"true\" className=\"size-3.5\" />\n </Button>\n ) : (\n <Button type=\"button\" size=\"icon-sm\" aria-label={t.send} onClick={submit} disabled={disabled || empty}>\n <ArrowUp aria-hidden=\"true\" className=\"size-4\" />\n </Button>\n )}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";AA8EM,cAcA,YAdA;AA5EN,SAAS,SAAS,cAAc;AAChC,YAAY,WAAW;AACvB,SAAS,cAAc;AAEvB,SAAS,sBAA2E;AACpF,SAAS,wBAAwB;AACjC,SAAS,UAAU;AAgCZ,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAsB;AACpB,QAAM,IAAI,iBAAiB,QAAQ,MAAM;AACzC,QAAM,WAAW,MAAM,OAA6B,IAAI;AACxD,QAAM,oBAAoB,WAAW,MAAM,SAAS,SAAiC,CAAC,CAAC;AACvF,QAAM,QAAQ,MAAM,KAAK,MAAM;AAE/B,QAAM,SAAS,MAAM;AACnB,QAAI,YAAY,aAAa,SAAS,SAAS,SAAS,YAAY,EAAG;AACvE,aAAS,KAAK;AAAA,EAChB;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,iBAAe,YAAY;AAAA,MAC3B,WAAW;AAAA,QACT;AAAA;AAAA,QAEA,YAAY;AAAA,QACZ;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEJ;AAAA;AAAA,UAAC;AAAA;AAAA,YACE,GAAG;AAAA,YACJ,KAAK;AAAA,YACL,SAAQ;AAAA,YACR;AAAA,YACA;AAAA,YACA,UAAU;AAAA,YACV,eAAa;AAAA,YACb,UAAU,CAAC;AAAA,YACX;AAAA,YACA,aAAa,eAAe,EAAE;AAAA,YAC9B,cAAY,cAAc,YAAY,KAAK,EAAE;AAAA,YAC7C,kBAAkB,GAAG,oDAAoD,aAAa,gBAAgB;AAAA;AAAA,QACxG;AAAA,QACA,qBAAC,SAAI,WAAU,qCACZ;AAAA,qBAAW,oBAAC,SAAI,WAAU,0CAA0C,mBAAQ;AAAA,UAC7E,oBAAC,SAAI,WAAU,WACZ,sBACC;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,cAAY,EAAE;AAAA,cACd,SAAS;AAAA,cACT,UAAU,CAAC;AAAA,cAEX,8BAAC,UAAO,eAAY,QAAO,WAAU,YAAW;AAAA;AAAA,UAClD,IAEA,oBAAC,UAAO,MAAK,UAAS,MAAK,WAAU,cAAY,EAAE,MAAM,SAAS,QAAQ,UAAU,YAAY,OAC9F,8BAAC,WAAQ,eAAY,QAAO,WAAU,UAAS,GACjD,GAEJ;AAAA,WACF;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -0,0 +1,63 @@
1
+ import * as React from 'react';
2
+
3
+ /**
4
+ * Kiểu dữ liệu của khung hội thoại — module THUẦN, không `"use client"` (cùng luật mọi file
5
+ * `*-constants.ts` của gói: xem `src/chart/chart-colors.ts`).
6
+ */
7
+
8
+ /** Ai nói: người dùng, tác tử AI, hay dòng hệ thống (vd "Cuộc trò chuyện đã được chia sẻ"). */
9
+ type ChatRole = "user" | "assistant" | "system";
10
+ /** Trạng thái một bước của tác tử. */
11
+ type ChatStepStatus = "pending" | "running" | "done" | "error";
12
+ interface ChatStepItem {
13
+ id: string;
14
+ /** Việc tác tử đang/đã làm — "Đọc 12 việc của dự án", "Tạo bản nháp kế hoạch". */
15
+ label: React.ReactNode;
16
+ status: ChatStepStatus;
17
+ /** Chi tiết phụ (kết quả, thông điệp lỗi) — chữ nhỏ dưới nhãn. */
18
+ detail?: React.ReactNode;
19
+ }
20
+ interface ChatSource {
21
+ id: string;
22
+ title: string;
23
+ /** Địa chỉ mở nguồn — trang trong app (`/tasks/KEY-12`) hay liên kết ngoài. */
24
+ href?: string;
25
+ /** Trích đoạn / mô tả ngắn. */
26
+ description?: string;
27
+ }
28
+ /** Mọi chữ của khung hội thoại. Theo `ComitorLocaleProvider` (namespace `chat`). */
29
+ interface ChatLabels {
30
+ /** Tên vùng `role="log"` khi app không truyền `aria-label`. */
31
+ log: string;
32
+ /** Nút về tin mới nhất (hiện khi người dùng đã cuộn lên). */
33
+ scrollToLatest: string;
34
+ /** Nhãn "do AI tạo" trên mỗi tin của tác tử (FR-AI-018). */
35
+ aiGenerated: string;
36
+ /** Câu giải thích đi kèm nhãn AI — trong `title` và cho trình đọc màn hình. */
37
+ aiGeneratedHint: string;
38
+ /** Tên mặc định của người dùng / tác tử khi tin không có `author`. */
39
+ you: string;
40
+ assistant: string;
41
+ /** Chữ hiện khi tác tử đang nghĩ mà chưa có chữ nào. */
42
+ thinking: string;
43
+ /** Dòng tóm tắt của nhóm bước. */
44
+ stepsSummary: (done: number, total: number) => string;
45
+ /** Trạng thái một bước, đọc cho trình đọc màn hình. */
46
+ stepPending: string;
47
+ stepRunning: string;
48
+ stepDone: string;
49
+ stepError: string;
50
+ /** Tiêu đề danh sách nguồn. */
51
+ sources: (count: number) => string;
52
+ /** Tên của một chip trích dẫn: "Nguồn 2: Kế hoạch quý". */
53
+ citation: (index: number, title: string) => string;
54
+ /** Gắn sau liên kết mở ra tab mới. */
55
+ opensInNewTab: string;
56
+ /** Ô nhập. */
57
+ composerLabel: string;
58
+ composerPlaceholder: string;
59
+ send: string;
60
+ stop: string;
61
+ }
62
+
63
+ export type { ChatLabels, ChatRole, ChatSource, ChatStepItem, ChatStepStatus };
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=chat-constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,51 @@
1
+ import * as React from 'react';
2
+ import { MarkdownEditorProps } from '../editor/markdown-editor.js';
3
+ import { ChatRole, ChatLabels } from './chat-constants.js';
4
+ import '../editor-types-BacXD2VN.js';
5
+ import '@tiptap/core';
6
+ import '../components/composite/empty-state.js';
7
+ import '../editor/markdown-editor-constants.js';
8
+
9
+ interface ChatMessageProps extends Omit<React.ComponentProps<"article">, "content" | "role"> {
10
+ /** Ai nói. `assistant` mặc định mang nhãn "do AI tạo". */
11
+ from: ChatRole;
12
+ /** Nội dung MARKDOWN — cùng định dạng lưu của `MarkdownEditor` (nhắc tên `[@Tên](mention:id)`…). */
13
+ content?: string;
14
+ /** Người/tác tử nói. Thiếu thì dùng khoá `you` / `assistant`. */
15
+ author?: {
16
+ name: string;
17
+ avatarUrl?: string | null;
18
+ };
19
+ /** Phụ đề cạnh tên (giờ gửi — vd `<RelativeTime />`). */
20
+ meta?: React.ReactNode;
21
+ /**
22
+ * Gắn nhãn "do AI tạo" (FR-AI-018). Mặc định `true` với `assistant`, `false` với người. Đặt `true` cho
23
+ * tin của NGƯỜI mà nội dung do AI soạn hộ.
24
+ */
25
+ aiGenerated?: boolean;
26
+ /** Chữ đang về dần: hiện "Đang suy nghĩ…" khi chưa có chữ, và dấu nhịp ở cuối khi đã có. */
27
+ streaming?: boolean;
28
+ /** Các bước của tác tử (`<ChatSteps />`) — vẽ TRƯỚC câu trả lời. */
29
+ steps?: React.ReactNode;
30
+ /** Thao tác dưới tin (chép, thích/không thích, làm lại). */
31
+ actions?: React.ReactNode;
32
+ /**
33
+ * Prop chuyển thẳng cho `MarkdownEditor` chỉ-đọc vẽ nội dung — `mentions` (`resolveLabel`,
34
+ * `getHref`), `resolveImageSrc`…
35
+ */
36
+ markdownProps?: Omit<MarkdownEditorProps, "value" | "defaultValue" | "onChange" | "editable" | "variant">;
37
+ labels?: Partial<ChatLabels>;
38
+ /** Phần sau câu trả lời: nguồn (`<ChatSources />`), tệp đính kèm… */
39
+ children?: React.ReactNode;
40
+ }
41
+ /**
42
+ * Một tin trong `ChatThread`. Người nói: bong bóng bên phải. Tác tử: không bong bóng, trải cả cột,
43
+ * avatar + tên + nhãn "do AI tạo" — kiểu ChatGPT. Dòng hệ thống: chữ nhỏ giữa cột.
44
+ *
45
+ * Markdown vẽ bằng `MarkdownEditor` chỉ-đọc — CHÍNH lược đồ mà tin được soạn ra, nên nhắc tên, bảng,
46
+ * khối mã, danh sách việc hiện đúng như ở mọi chỗ khác của Comitor (không có bộ vẽ markdown thứ hai
47
+ * để lệch). Chữ đang về cứ đổi `content`: trình soạn nạp lại khi giá trị KHÁC (docs/editor.md §6).
48
+ */
49
+ declare function ChatMessage({ from, content, author, meta, aiGenerated, streaming, steps, actions, markdownProps, labels, children, className, ...props }: ChatMessageProps): React.JSX.Element;
50
+
51
+ export { ChatMessage, type ChatMessageProps };
@@ -0,0 +1,137 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { Sparkles } from "lucide-react";
4
+ import * as React from "react";
5
+ import { LetterAvatar } from "../components/composite/letter-avatar.js";
6
+ import { MarkdownEditor } from "../editor/markdown-editor.js";
7
+ import { useComitorLabels } from "../i18n/locale-provider.js";
8
+ import { cn } from "../lib/cn.js";
9
+ function ChatMessage({
10
+ from,
11
+ content = "",
12
+ author,
13
+ meta,
14
+ aiGenerated,
15
+ streaming = false,
16
+ steps,
17
+ actions,
18
+ markdownProps,
19
+ labels,
20
+ children,
21
+ className,
22
+ ...props
23
+ }) {
24
+ const t = useComitorLabels("chat", labels);
25
+ const nameId = React.useId();
26
+ const hintId = React.useId();
27
+ const isAi = aiGenerated ?? from === "assistant";
28
+ const name = author?.name ?? (from === "assistant" ? t.assistant : t.you);
29
+ const hasContent = content.trim() !== "";
30
+ if (from === "system") {
31
+ return /* @__PURE__ */ jsxs(
32
+ "article",
33
+ {
34
+ "data-slot": "chat-message",
35
+ "data-from": "system",
36
+ className: cn("text-center text-muted-foreground text-xs", className),
37
+ ...props,
38
+ children: [
39
+ content,
40
+ children
41
+ ]
42
+ }
43
+ );
44
+ }
45
+ const body = hasContent ? /* @__PURE__ */ jsx(
46
+ MarkdownEditor,
47
+ {
48
+ ...markdownProps,
49
+ variant: "ghost",
50
+ editable: false,
51
+ value: content,
52
+ "aria-labelledby": nameId,
53
+ className: cn("min-w-0", markdownProps?.className)
54
+ }
55
+ ) : null;
56
+ const aiBadge = isAi && /* @__PURE__ */ jsxs(
57
+ "span",
58
+ {
59
+ title: t.aiGeneratedHint,
60
+ "aria-describedby": hintId,
61
+ "data-slot": "chat-ai-label",
62
+ className: "inline-flex items-center gap-1 rounded-sm bg-secondary px-1.5 py-0.5 font-medium text-[10px] text-secondary-foreground",
63
+ children: [
64
+ /* @__PURE__ */ jsx(Sparkles, { "aria-hidden": "true", className: "size-3" }),
65
+ t.aiGenerated,
66
+ /* @__PURE__ */ jsx("span", { id: hintId, className: "sr-only", children: t.aiGeneratedHint })
67
+ ]
68
+ }
69
+ );
70
+ if (from === "user") {
71
+ return /* @__PURE__ */ jsxs(
72
+ "article",
73
+ {
74
+ "data-slot": "chat-message",
75
+ "data-from": "user",
76
+ "aria-labelledby": nameId,
77
+ className: cn("flex flex-col items-end gap-1", className),
78
+ ...props,
79
+ children: [
80
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-muted-foreground text-xs", children: [
81
+ /* @__PURE__ */ jsx("span", { id: nameId, className: "sr-only", children: name }),
82
+ aiBadge,
83
+ meta
84
+ ] }),
85
+ /* @__PURE__ */ jsx("div", { className: "max-w-[85%] rounded-2xl rounded-br-md bg-secondary px-4 py-2.5 text-secondary-foreground", children: body }),
86
+ children,
87
+ actions && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1", children: actions })
88
+ ]
89
+ }
90
+ );
91
+ }
92
+ return /* @__PURE__ */ jsxs(
93
+ "article",
94
+ {
95
+ "data-slot": "chat-message",
96
+ "data-from": "assistant",
97
+ "aria-labelledby": nameId,
98
+ "aria-busy": streaming || void 0,
99
+ className: cn("flex gap-3", className),
100
+ ...props,
101
+ children: [
102
+ author ? /* @__PURE__ */ jsx(LetterAvatar, { name: author.name, src: author.avatarUrl, size: "sm", className: "mt-0.5 shrink-0" }) : /* @__PURE__ */ jsx(
103
+ "span",
104
+ {
105
+ "aria-hidden": "true",
106
+ className: "mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground",
107
+ children: /* @__PURE__ */ jsx(Sparkles, { className: "size-3.5" })
108
+ }
109
+ ),
110
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-2", children: [
111
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 text-xs", children: [
112
+ /* @__PURE__ */ jsx("span", { id: nameId, className: "font-semibold text-foreground text-sm", children: name }),
113
+ aiBadge,
114
+ meta && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: meta })
115
+ ] }),
116
+ steps,
117
+ body,
118
+ streaming && !hasContent && /* @__PURE__ */ jsx("p", { "data-slot": "chat-thinking", className: "animate-pulse text-muted-foreground text-sm", children: t.thinking }),
119
+ streaming && hasContent && /* @__PURE__ */ jsx(
120
+ "span",
121
+ {
122
+ "aria-hidden": "true",
123
+ "data-slot": "chat-caret",
124
+ className: "block h-4 w-1.5 animate-pulse rounded-sm bg-foreground"
125
+ }
126
+ ),
127
+ children,
128
+ actions && !streaming && /* @__PURE__ */ jsx("div", { className: "-ms-1.5 flex items-center gap-1", children: actions })
129
+ ] })
130
+ ]
131
+ }
132
+ );
133
+ }
134
+ export {
135
+ ChatMessage
136
+ };
137
+ //# sourceMappingURL=chat-message.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/chat/chat-message.tsx"],"sourcesContent":["\"use client\";\n\nimport { Sparkles } from \"lucide-react\";\nimport * as React from \"react\";\nimport { LetterAvatar } from \"../components/composite/letter-avatar.js\";\nimport { MarkdownEditor, type MarkdownEditorProps } from \"../editor/markdown-editor.js\";\nimport { useComitorLabels } from \"../i18n/locale-provider.js\";\nimport { cn } from \"../lib/cn.js\";\nimport type { ChatLabels, ChatRole } from \"./chat-constants.js\";\n\nexport interface ChatMessageProps extends Omit<React.ComponentProps<\"article\">, \"content\" | \"role\"> {\n /** Ai nói. `assistant` mặc định mang nhãn \"do AI tạo\". */\n from: ChatRole;\n /** Nội dung MARKDOWN — cùng định dạng lưu của `MarkdownEditor` (nhắc tên `[@Tên](mention:id)`…). */\n content?: string;\n /** Người/tác tử nói. Thiếu thì dùng khoá `you` / `assistant`. */\n author?: { name: string; avatarUrl?: string | null };\n /** Phụ đề cạnh tên (giờ gửi — vd `<RelativeTime />`). */\n meta?: React.ReactNode;\n /**\n * Gắn nhãn \"do AI tạo\" (FR-AI-018). Mặc định `true` với `assistant`, `false` với người. Đặt `true` cho\n * tin của NGƯỜI mà nội dung do AI soạn hộ.\n */\n aiGenerated?: boolean;\n /** Chữ đang về dần: hiện \"Đang suy nghĩ…\" khi chưa có chữ, và dấu nhịp ở cuối khi đã có. */\n streaming?: boolean;\n /** Các bước của tác tử (`<ChatSteps />`) — vẽ TRƯỚC câu trả lời. */\n steps?: React.ReactNode;\n /** Thao tác dưới tin (chép, thích/không thích, làm lại). */\n actions?: React.ReactNode;\n /**\n * Prop chuyển thẳng cho `MarkdownEditor` chỉ-đọc vẽ nội dung — `mentions` (`resolveLabel`,\n * `getHref`), `resolveImageSrc`…\n */\n markdownProps?: Omit<MarkdownEditorProps, \"value\" | \"defaultValue\" | \"onChange\" | \"editable\" | \"variant\">;\n labels?: Partial<ChatLabels>;\n /** Phần sau câu trả lời: nguồn (`<ChatSources />`), tệp đính kèm… */\n children?: React.ReactNode;\n}\n\n/**\n * Một tin trong `ChatThread`. Người nói: bong bóng bên phải. Tác tử: không bong bóng, trải cả cột,\n * avatar + tên + nhãn \"do AI tạo\" — kiểu ChatGPT. Dòng hệ thống: chữ nhỏ giữa cột.\n *\n * Markdown vẽ bằng `MarkdownEditor` chỉ-đọc — CHÍNH lược đồ mà tin được soạn ra, nên nhắc tên, bảng,\n * khối mã, danh sách việc hiện đúng như ở mọi chỗ khác của Comitor (không có bộ vẽ markdown thứ hai\n * để lệch). Chữ đang về cứ đổi `content`: trình soạn nạp lại khi giá trị KHÁC (docs/editor.md §6).\n */\nexport function ChatMessage({\n from,\n content = \"\",\n author,\n meta,\n aiGenerated,\n streaming = false,\n steps,\n actions,\n markdownProps,\n labels,\n children,\n className,\n ...props\n}: ChatMessageProps) {\n const t = useComitorLabels(\"chat\", labels);\n const nameId = React.useId();\n const hintId = React.useId();\n const isAi = aiGenerated ?? from === \"assistant\";\n const name = author?.name ?? (from === \"assistant\" ? t.assistant : t.you);\n const hasContent = content.trim() !== \"\";\n\n if (from === \"system\") {\n return (\n <article\n data-slot=\"chat-message\"\n data-from=\"system\"\n className={cn(\"text-center text-muted-foreground text-xs\", className)}\n {...props}\n >\n {content}\n {children}\n </article>\n );\n }\n\n const body = hasContent ? (\n <MarkdownEditor\n {...markdownProps}\n variant=\"ghost\"\n editable={false}\n value={content}\n aria-labelledby={nameId}\n className={cn(\"min-w-0\", markdownProps?.className)}\n />\n ) : null;\n\n const aiBadge = isAi && (\n <span\n title={t.aiGeneratedHint}\n aria-describedby={hintId}\n data-slot=\"chat-ai-label\"\n className=\"inline-flex items-center gap-1 rounded-sm bg-secondary px-1.5 py-0.5 font-medium text-[10px] text-secondary-foreground\"\n >\n <Sparkles aria-hidden=\"true\" className=\"size-3\" />\n {t.aiGenerated}\n <span id={hintId} className=\"sr-only\">\n {t.aiGeneratedHint}\n </span>\n </span>\n );\n\n if (from === \"user\") {\n return (\n <article\n data-slot=\"chat-message\"\n data-from=\"user\"\n aria-labelledby={nameId}\n className={cn(\"flex flex-col items-end gap-1\", className)}\n {...props}\n >\n <div className=\"flex items-center gap-2 text-muted-foreground text-xs\">\n <span id={nameId} className=\"sr-only\">\n {name}\n </span>\n {aiBadge}\n {meta}\n </div>\n <div className=\"max-w-[85%] rounded-2xl rounded-br-md bg-secondary px-4 py-2.5 text-secondary-foreground\">\n {body}\n </div>\n {children}\n {actions && <div className=\"flex items-center gap-1\">{actions}</div>}\n </article>\n );\n }\n\n return (\n <article\n data-slot=\"chat-message\"\n data-from=\"assistant\"\n aria-labelledby={nameId}\n aria-busy={streaming || undefined}\n className={cn(\"flex gap-3\", className)}\n {...props}\n >\n {author ? (\n <LetterAvatar name={author.name} src={author.avatarUrl} size=\"sm\" className=\"mt-0.5 shrink-0\" />\n ) : (\n <span\n aria-hidden=\"true\"\n className=\"mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground\"\n >\n <Sparkles className=\"size-3.5\" />\n </span>\n )}\n <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n <div className=\"flex flex-wrap items-center gap-2 text-xs\">\n <span id={nameId} className=\"font-semibold text-foreground text-sm\">\n {name}\n </span>\n {aiBadge}\n {meta && <span className=\"text-muted-foreground\">{meta}</span>}\n </div>\n {steps}\n {body}\n {streaming && !hasContent && (\n <p data-slot=\"chat-thinking\" className=\"animate-pulse text-muted-foreground text-sm\">\n {t.thinking}\n </p>\n )}\n {streaming && hasContent && (\n <span\n aria-hidden=\"true\"\n data-slot=\"chat-caret\"\n className=\"block h-4 w-1.5 animate-pulse rounded-sm bg-foreground\"\n />\n )}\n {children}\n {actions && !streaming && <div className=\"-ms-1.5 flex items-center gap-1\">{actions}</div>}\n </div>\n </article>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,25 @@
1
+ import * as React from 'react';
2
+ import { ChatSource, ChatLabels } from './chat-constants.js';
3
+
4
+ interface ChatSourcesProps extends Omit<React.ComponentProps<"section">, "children"> {
5
+ sources: ChatSource[];
6
+ labels?: Partial<ChatLabels>;
7
+ }
8
+ /**
9
+ * Danh sách nguồn dưới câu trả lời — đánh số khớp với `ChatCitation` trong câu. Mỗi nguồn có
10
+ * `href` là một liên kết thật (bấm được, mở bằng bàn phím, sao chép được).
11
+ */
12
+ declare function ChatSources({ sources, labels, className, ...props }: ChatSourcesProps): React.JSX.Element | null;
13
+ interface ChatCitationProps extends Omit<React.ComponentProps<"a">, "children" | "href"> {
14
+ /** Số thứ tự hiển thị (đếm từ 1) — khớp với thứ tự trong `ChatSources`. */
15
+ index: number;
16
+ source: ChatSource;
17
+ labels?: Partial<ChatLabels>;
18
+ }
19
+ /**
20
+ * Chip trích dẫn `[n]` trong câu: bấm mở nguồn, rê chuột/focus xem nhanh tiêu đề + trích đoạn. Dùng
21
+ * khi app tự vẽ câu trả lời; câu trả lời là markdown thì đưa nguồn vào `ChatSources` bên dưới.
22
+ */
23
+ declare function ChatCitation({ index, source, labels, className, ...props }: ChatCitationProps): React.JSX.Element;
24
+
25
+ export { ChatCitation, type ChatCitationProps, ChatSources, type ChatSourcesProps };
@@ -0,0 +1,97 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { ExternalLink } from "lucide-react";
4
+ import { HoverCard, HoverCardContent, HoverCardTrigger } from "../components/ui/hover-card.js";
5
+ import { useComitorLabels } from "../i18n/locale-provider.js";
6
+ import { cn } from "../lib/cn.js";
7
+ function isExternal(href) {
8
+ return /^https?:\/\//i.test(href);
9
+ }
10
+ function hostOf(href) {
11
+ try {
12
+ return new URL(href).hostname.replace(/^www\./, "");
13
+ } catch {
14
+ return null;
15
+ }
16
+ }
17
+ function ChatSources({ sources, labels, className, ...props }) {
18
+ const t = useComitorLabels("chat", labels);
19
+ if (sources.length === 0) return null;
20
+ return /* @__PURE__ */ jsxs(
21
+ "section",
22
+ {
23
+ "data-slot": "chat-sources",
24
+ "aria-label": t.sources(sources.length),
25
+ className: cn("flex flex-col gap-1.5", className),
26
+ ...props,
27
+ children: [
28
+ /* @__PURE__ */ jsx("div", { className: "font-medium text-muted-foreground text-xs", children: t.sources(sources.length) }),
29
+ /* @__PURE__ */ jsx("ol", { className: "grid gap-1.5 sm:grid-cols-2", children: sources.map((source, index) => {
30
+ const external = source.href ? isExternal(source.href) : false;
31
+ const host = source.href && external ? hostOf(source.href) : null;
32
+ const inner = /* @__PURE__ */ jsxs(Fragment, { children: [
33
+ /* @__PURE__ */ jsx(
34
+ "span",
35
+ {
36
+ "aria-hidden": "true",
37
+ className: "flex size-5 shrink-0 items-center justify-center rounded-sm bg-secondary font-medium text-[10px] text-secondary-foreground tabular-nums",
38
+ children: index + 1
39
+ }
40
+ ),
41
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
42
+ /* @__PURE__ */ jsx("span", { className: "block truncate font-medium text-foreground text-sm", children: source.title }),
43
+ (source.description || host) && /* @__PURE__ */ jsx("span", { className: "block truncate text-muted-foreground text-xs", children: source.description ?? host })
44
+ ] }),
45
+ external && /* @__PURE__ */ jsx(ExternalLink, { "aria-hidden": "true", className: "mt-0.5 size-3.5 shrink-0 text-muted-foreground" })
46
+ ] });
47
+ return /* @__PURE__ */ jsx("li", { id: `chat-source-${source.id}`, children: source.href ? /* @__PURE__ */ jsxs(
48
+ "a",
49
+ {
50
+ href: source.href,
51
+ target: external ? "_blank" : void 0,
52
+ rel: external ? "noreferrer noopener" : void 0,
53
+ className: "flex items-start gap-2 rounded-md border bg-card px-2.5 py-2 hover:border-control-edge-strong hover:bg-muted",
54
+ children: [
55
+ inner,
56
+ external && /* @__PURE__ */ jsx("span", { className: "sr-only", children: t.opensInNewTab })
57
+ ]
58
+ }
59
+ ) : /* @__PURE__ */ jsx("div", { className: "flex items-start gap-2 rounded-md border bg-card px-2.5 py-2", children: inner }) }, source.id);
60
+ }) })
61
+ ]
62
+ }
63
+ );
64
+ }
65
+ function ChatCitation({ index, source, labels, className, ...props }) {
66
+ const t = useComitorLabels("chat", labels);
67
+ const href = source.href ?? `#chat-source-${source.id}`;
68
+ const external = isExternal(href);
69
+ return /* @__PURE__ */ jsxs(HoverCard, { openDelay: 200, closeDelay: 100, children: [
70
+ /* @__PURE__ */ jsx(HoverCardTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
71
+ "a",
72
+ {
73
+ href,
74
+ target: external ? "_blank" : void 0,
75
+ rel: external ? "noreferrer noopener" : void 0,
76
+ "aria-label": `${t.citation(index, source.title)}${external ? ` ${t.opensInNewTab}` : ""}`,
77
+ "data-slot": "chat-citation",
78
+ className: cn(
79
+ "mx-0.5 inline-flex h-4 min-w-4 items-center justify-center rounded-sm bg-secondary px-1 align-text-top font-medium text-[10px] text-secondary-foreground tabular-nums no-underline hover:bg-accent hover:text-accent-foreground",
80
+ className
81
+ ),
82
+ ...props,
83
+ children: index
84
+ }
85
+ ) }),
86
+ /* @__PURE__ */ jsxs(HoverCardContent, { className: "w-72 p-3", children: [
87
+ /* @__PURE__ */ jsx("div", { className: "font-medium text-foreground text-sm", children: source.title }),
88
+ source.description && /* @__PURE__ */ jsx("p", { className: "mt-1 line-clamp-3 text-muted-foreground text-xs", children: source.description }),
89
+ source.href && /* @__PURE__ */ jsx("p", { className: "mt-1.5 truncate text-link-ink text-xs", children: hostOf(source.href) ?? source.href })
90
+ ] })
91
+ ] });
92
+ }
93
+ export {
94
+ ChatCitation,
95
+ ChatSources
96
+ };
97
+ //# sourceMappingURL=chat-sources.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/chat/chat-sources.tsx"],"sourcesContent":["\"use client\";\n\nimport { ExternalLink } from \"lucide-react\";\nimport type * as React from \"react\";\nimport { HoverCard, HoverCardContent, HoverCardTrigger } from \"../components/ui/hover-card.js\";\nimport { useComitorLabels } from \"../i18n/locale-provider.js\";\nimport { cn } from \"../lib/cn.js\";\nimport type { ChatLabels, ChatSource } from \"./chat-constants.js\";\n\n/** Liên kết ra ngoài trang (khác gốc) thì mở tab mới; đường dẫn trong app thì đi như liên kết thường. */\nfunction isExternal(href: string): boolean {\n return /^https?:\\/\\//i.test(href);\n}\n\nfunction hostOf(href: string): string | null {\n try {\n return new URL(href).hostname.replace(/^www\\./, \"\");\n } catch {\n return null;\n }\n}\n\nexport interface ChatSourcesProps extends Omit<React.ComponentProps<\"section\">, \"children\"> {\n sources: ChatSource[];\n labels?: Partial<ChatLabels>;\n}\n\n/**\n * Danh sách nguồn dưới câu trả lời — đánh số khớp với `ChatCitation` trong câu. Mỗi nguồn có\n * `href` là một liên kết thật (bấm được, mở bằng bàn phím, sao chép được).\n */\nexport function ChatSources({ sources, labels, className, ...props }: ChatSourcesProps) {\n const t = useComitorLabels(\"chat\", labels);\n if (sources.length === 0) return null;\n return (\n <section\n data-slot=\"chat-sources\"\n aria-label={t.sources(sources.length)}\n className={cn(\"flex flex-col gap-1.5\", className)}\n {...props}\n >\n <div className=\"font-medium text-muted-foreground text-xs\">{t.sources(sources.length)}</div>\n <ol className=\"grid gap-1.5 sm:grid-cols-2\">\n {sources.map((source, index) => {\n const external = source.href ? isExternal(source.href) : false;\n const host = source.href && external ? hostOf(source.href) : null;\n const inner = (\n <>\n <span\n aria-hidden=\"true\"\n className=\"flex size-5 shrink-0 items-center justify-center rounded-sm bg-secondary font-medium text-[10px] text-secondary-foreground tabular-nums\"\n >\n {index + 1}\n </span>\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate font-medium text-foreground text-sm\">{source.title}</span>\n {(source.description || host) && (\n <span className=\"block truncate text-muted-foreground text-xs\">{source.description ?? host}</span>\n )}\n </span>\n {external && (\n <ExternalLink aria-hidden=\"true\" className=\"mt-0.5 size-3.5 shrink-0 text-muted-foreground\" />\n )}\n </>\n );\n return (\n <li key={source.id} id={`chat-source-${source.id}`}>\n {source.href ? (\n <a\n href={source.href}\n target={external ? \"_blank\" : undefined}\n rel={external ? \"noreferrer noopener\" : undefined}\n className=\"flex items-start gap-2 rounded-md border bg-card px-2.5 py-2 hover:border-control-edge-strong hover:bg-muted\"\n >\n {inner}\n {external && <span className=\"sr-only\">{t.opensInNewTab}</span>}\n </a>\n ) : (\n <div className=\"flex items-start gap-2 rounded-md border bg-card px-2.5 py-2\">{inner}</div>\n )}\n </li>\n );\n })}\n </ol>\n </section>\n );\n}\n\nexport interface ChatCitationProps extends Omit<React.ComponentProps<\"a\">, \"children\" | \"href\"> {\n /** Số thứ tự hiển thị (đếm từ 1) — khớp với thứ tự trong `ChatSources`. */\n index: number;\n source: ChatSource;\n labels?: Partial<ChatLabels>;\n}\n\n/**\n * Chip trích dẫn `[n]` trong câu: bấm mở nguồn, rê chuột/focus xem nhanh tiêu đề + trích đoạn. Dùng\n * khi app tự vẽ câu trả lời; câu trả lời là markdown thì đưa nguồn vào `ChatSources` bên dưới.\n */\nexport function ChatCitation({ index, source, labels, className, ...props }: ChatCitationProps) {\n const t = useComitorLabels(\"chat\", labels);\n const href = source.href ?? `#chat-source-${source.id}`;\n const external = isExternal(href);\n return (\n <HoverCard openDelay={200} closeDelay={100}>\n <HoverCardTrigger asChild>\n <a\n href={href}\n target={external ? \"_blank\" : undefined}\n rel={external ? \"noreferrer noopener\" : undefined}\n aria-label={`${t.citation(index, source.title)}${external ? ` ${t.opensInNewTab}` : \"\"}`}\n data-slot=\"chat-citation\"\n className={cn(\n \"mx-0.5 inline-flex h-4 min-w-4 items-center justify-center rounded-sm bg-secondary px-1 align-text-top font-medium text-[10px] text-secondary-foreground tabular-nums no-underline hover:bg-accent hover:text-accent-foreground\",\n className\n )}\n {...props}\n >\n {index}\n </a>\n </HoverCardTrigger>\n <HoverCardContent className=\"w-72 p-3\">\n <div className=\"font-medium text-foreground text-sm\">{source.title}</div>\n {source.description && <p className=\"mt-1 line-clamp-3 text-muted-foreground text-xs\">{source.description}</p>}\n {source.href && <p className=\"mt-1.5 truncate text-link-ink text-xs\">{hostOf(source.href) ?? source.href}</p>}\n </HoverCardContent>\n </HoverCard>\n );\n}\n"],"mappings":"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","names":[]}