Composer
Ô để viết tin nhắn, dưới một MessageList: tệp đính kèm, ô văn bản giãn theo nội dung, công cụ của bạn và nút gửi. Enter để gửi và Shift+Enter để xuống dòng. Khi một câu trả lời đang tới, nút gửi có thể thành nút dừng.
Xem trước
import { IconPaperclip } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"
import { Composer } from "@/components/ui/composer"
const sendMessage = (text: string) => { /* Thêm tin nhắn vào cuộc trò chuyện. */ }
<Composer label="Tin nhắn" placeholder="Viết tin nhắn" onSubmit={sendMessage} labels={{ send: "Gửi", stop: "Dừng", replyingTo: "Đang trả lời", cancelReply: "Hủy trả lời" }} actions={<Button size="sm" variant="ghost" iconOnly aria-label="Đính kèm tệp">
<IconPaperclip size={16} />
</Button>} className="w-full max-w-md" />Cách dùng
Khi nào dùng
- Viết và gửi tin nhắn dưới một cuộc trò chuyện, với tệp đính kèm và vài công cụ.
- Hỏi trợ lý một điều, và dừng câu trả lời khi nó đang được gửi tới.
Khi nào không dùng
- Một field trong form được gửi cùng các field khác. Dùng Textarea trong Field.
- Tìm trong danh sách hoặc ứng dụng. Dùng FilterBar hoặc Command.
- Một giá trị một dòng. Dùng Input.
Ví dụ
Tin nhắn
import { IconPaperclip } from "@tabler/icons-react"
import { Button } from "@/components/ui/button"
import { Composer } from "@/components/ui/composer"
const sendMessage = (text: string) => { /* Thêm tin nhắn vào cuộc trò chuyện. */ }
<Composer label="Tin nhắn" placeholder="Viết tin nhắn" onSubmit={sendMessage} actions={<Button size="sm" variant="ghost" iconOnly aria-label="Đính kèm tệp">
<IconPaperclip size={16} />
</Button>} className="max-w-md" />Có tệp đính kèm
Tệp hiện phía trên nội dung khi đang tải lên; canSubmit cho phép gửi tin không kèm chữ.
import { IconPaperclip } from "@tabler/icons-react"
import { AttachmentPreview } from "@/components/ui/attachment-preview"
import { Button } from "@/components/ui/button"
import { Composer } from "@/components/ui/composer"
const sendMessage = (text: string) => { /* Thêm tin nhắn vào cuộc trò chuyện. */ }
const removeFile = () => { /* Gỡ tệp khỏi tin nhắn. */ }
<Composer canSubmit label="Tin nhắn" placeholder="Thêm lời nhắn" onSubmit={sendMessage} attachments={<AttachmentPreview name="brief.pdf" type="application/pdf" fileSize={482000} onRemove={removeFile} />} actions={<Button size="sm" variant="ghost" iconOnly aria-label="Đính kèm tệp">
<IconPaperclip size={16} />
</Button>} className="max-w-md" />Dừng câu trả lời
Khi trợ lý đang trả lời, nút gửi thành nút dừng.
import { Composer } from "@/components/ui/composer"
const sendMessage = (text: string) => { /* Thêm tin nhắn vào cuộc trò chuyện. */ }
const stopReply = () => { /* Hủy câu trả lời đang được gửi tới. */ }
<Composer pending label="Tin nhắn" placeholder="Hỏi bất cứ điều gì" onSubmit={sendMessage} onStop={stopReply} className="max-w-md" />Trả lời
Nút Trả lời trong MessageActions đưa tin vào replyTo; thanh nằm trên nội dung cho tới khi tin được gửi hoặc bị hủy.
import { Composer } from "@/components/ui/composer"
const sendMessage = (text: string) => { /* Thêm tin nhắn vào cuộc trò chuyện. */ }
const cancelReply = () => { /* Xóa replyTo của Composer. */ }
const sampleReply = { author: "Linh Tran", text: "Bạn gửi bản brief trước buổi gọi được không?" }
<Composer label="Tin nhắn" placeholder="Viết câu trả lời" replyTo={sampleReply} onCancelReply={cancelReply} onSubmit={sendMessage} className="max-w-md" />Báo cho người khác bạn đang gõ
onTypingChange báo khi bắt đầu và khi dừng gõ; gửi nó qua kênh realtime của bạn.
import { Composer } from "@/components/ui/composer"
const sendMessage = (text: string) => { /* Thêm tin nhắn vào cuộc trò chuyện. */ }
const sendTyping = (typing: boolean) => { /* Báo cho những người khác trong cuộc trò chuyện, như qua websocket. */ }
<Composer label="Tin nhắn" placeholder="Viết tin nhắn" onTypingChange={sendTyping} onSubmit={sendMessage} className="max-w-md" />Nên và không nên
Nên
- Đặt Composer dưới MessageList của nó, và giữ focus trong nó sau khi gửi.
- Bật pending khi câu trả lời của trợ lý đang được gửi tới, và truyền onStop để người đọc hủy được.
- Chỉ đặt vài công cụ trong actions, như đính kèm và chọn model hay định dạng.
Không nên
- Đừng xóa composer được kiểm soát trước khi onSubmit nhận nội dung; xóa nó trong onSubmit.
- Đừng dùng placeholder làm tên; label đặt tên cho ô.
- Đừng thêm nút gửi thứ hai bên ngoài ô.
Props
| Tên | Kiểu | Giá trị | Mặc định | Mô tả |
|---|---|---|---|---|
| label bắt buộc | string | - | - | Tên của ô văn bản cho trình đọc màn hình, như Tin nhắn. |
| placeholder | string | - | - | Gợi ý trong ô trống, như Viết tin nhắn. |
| value | string | - | - | Nội dung, cho composer được kiểm soát. Tự xóa nó trong onSubmit. |
| defaultValue | string | - | - | Nội dung ở lần render đầu, cho composer không kiểm soát. Nó tự xóa sau khi gửi. |
| onValueChange | (value: string) => void | - | - | Được gọi với nội dung mỗi khi thay đổi. |
| onSubmit | (value: string) => void | - | - | Được gọi với nội dung khi tin nhắn được gửi. |
| canSubmit | boolean | - | - | Tin nhắn có gửi được không. Mặc định: nội dung không trống. Truyền true để gửi tệp đính kèm không kèm chữ. |
| pending | boolean | - | false | Một câu trả lời đang tới: không gửi được gì, và với onStop nút gửi thành nút dừng. Vẫn gõ được. |
| onStop | () => void | - | - | Được gọi bởi nút dừng khi pending, như để hủy một câu trả lời đang được gửi tới. |
| disabled | boolean | - | false | Không gõ hay gửi được gì, như trong cuộc trò chuyện người đọc đã rời. |
| maxRows | number | - | 8 | Ô văn bản giãn theo nội dung tới số dòng này, rồi cuộn. |
| submitOnEnter | boolean | - | true | Enter để gửi và Shift+Enter để xuống dòng. Với false, Enter xuống dòng và chỉ nút mới gửi. |
| actions | node | - | - | Công cụ ở đầu thanh công cụ, như nút đính kèm. Dùng Button icon ghost cỡ sm. |
| attachments | node | - | - | Các ô AttachmentPreview phía trên nội dung. |
| replyTo | { author: React.ReactNode; text: React.ReactNode } | - | - | Tin đang được trả lời, hiện trong một thanh phía trên nội dung và được đọc cùng ô văn bản. |
| onCancelReply | () => void | - | - | Hiện một nút trong thanh trả lời để hủy trả lời. |
| onTypingChange | (typing: boolean) => void | - | - | Được gọi với true khi người đọc bắt đầu gõ và false khi họ dừng: sau typingTimeout không có phím nào, khi nội dung bị xóa hoặc được gửi, khi ô mất focus, và khi unmount. Gửi nó cho những người khác, họ hiện nó bằng typingUsers của MessageList. |
| typingTimeout | number | - | 3000 | Thời gian không gõ, tính bằng ms, trước khi gọi onTypingChange(false). |
| name | string | - | - | Tên của ô văn bản trong form được gửi. |
| autoFocus | boolean | - | false | Focus vào ô văn bản khi mount, như khi một cuộc trò chuyện mở ra. |
| labels | { send?: string; stop?: string; replyingTo?: string; cancelReply?: string } | - | - | Tên các nút và chữ trước một tin trả lời. Mặc định: Send, Stop, Replying to, Cancel reply. Tiếng Việt: Gửi, Dừng, Đang trả lời, Hủy trả lời. |
Cấu tạo
| Phần | Mô tả |
|---|---|
| root | Form, được vẽ như hộp field nổi của Textarea: tệp đính kèm, nội dung và thanh công cụ xếp chồng. |
| reply | Thanh của tin đang được trả lời, đầu tiên trong hộp: icon trả lời, người gửi và một dòng của tin, và nút hủy. |
| attachments | Hàng tùy chọn các ô AttachmentPreview phía trên nội dung. Nó xuống dòng khi hẹp. |
| input | Ô văn bản. Ban đầu một dòng; nó giãn theo nội dung tới maxRows dòng, rồi cuộn. |
| placeholder | Gợi ý trong ô trống, bằng chữ phụ. |
| toolbar | Hàng dưới nội dung: công cụ của bạn ở đầu, nút gửi hoặc dừng ở cuối. |
| actions | Công cụ của bạn, như nút đính kèm. Button icon ghost cỡ sm. |
| send | Button icon solid cỡ sm để gửi, hoặc nút dừng secondary khi pending. |
Khả năng truy cập
Role: textbox
| Phím | Hành động |
|---|---|
| Enter | Gửi tin nhắn, trừ khi submitOnEnter là false. Enter xác nhận chữ của bộ gõ không bao giờ gửi. |
| Shift + Enter | Xuống dòng. |
| Tab | Chuyển từ ô văn bản tới tệp đính kèm, công cụ và nút gửi. |
- Một form với ô văn bản nhiều dòng được đặt tên bằng label (EN `Message`, VI `Tin nhắn`). Placeholder là gợi ý, không phải tên.
- Nút gửi được đặt tên bằng labels.send (EN `Send`, VI `Gửi`). Khi không có gì để gửi, nó là aria-disabled: vẫn giữ chỗ trong thứ tự tab và không làm gì.
- Khi pending có onStop, nút gửi thành nút dừng được đặt tên bằng labels.stop (EN `Stop`, VI `Dừng`).
- Gửi xong, focus vẫn ở ô văn bản, để gõ tin tiếp theo ngay.
- Hộp field chỉ hiện quầng focus khi ô văn bản có focus; một công cụ có focus chỉ giữ vòng focus của riêng nó.
- Trên điện thoại, phím Enter hiện Gửi (enterkeyhint).
- Khi đang trả lời, ô văn bản được mô tả bằng thanh trả lời, nên trình đọc màn hình đọc EN `Replying to Linh Tran` cùng tin đó.
- onTypingChange được gọi một lần khi bắt đầu gõ và một lần khi dừng, không phải mỗi phím, nên số sự kiện gửi đi luôn ít.
Cài đặt
npx werewolf-ui add composerChạy npx werewolf-ui init một lần trong app trước khi thêm component. Init cài token, thêm CSS import và luật cho agent. Add cài component này cùng các dependency và giữ nguyên theme của bạn. Xem Cài đặt để biết yêu cầu và cách cài qua registry.