MessageList
სასტუმროს კონსიერჟის სრული საუბარი: კონტროლირებადი შეტყობინებები და პასუხის სიმულაცია.
მაგალითი
სასტუმრო ირისი
AI კონსიერჟი · სადემონსტრაციო საუბარი
"use client";
import { useEffect, useRef, useState } from "react";
import { Avatar, AvatarFallback } from "@llc-conciergia/ui/avatar";
import { Message } from "@llc-conciergia/ui/message";
import { MessageList } from "@llc-conciergia/ui/message-list";
import { Composer } from "@llc-conciergia/ui/composer";
import { StreamingText } from "@llc-conciergia/ui/streaming-text";
import { TypingIndicator } from "@llc-conciergia/ui/typing-indicator";
import { SuggestionChips } from "@llc-conciergia/ui/suggestion-chips";
type Turn = { id: number; role: "user" | "assistant"; text: string };
const suggestions = [
{ id: "breakfast", label: "საუზმის დრო" },
{ id: "transfer", label: "აეროპორტის ტრანსფერი" },
{ id: "checkout", label: "გვიანი გასვლა" },
];
function answer(question: string) {
if (question.includes("საუზმ"))
return "საუზმე პირველ სართულზე, ტერასის რესტორანში, 08:00-დან 11:00-მდეა. მენიუში გვაქვს ქართული ყველი, სეზონური ხილი და ცხელი კერძები. გსურთ ვეგეტარიანული არჩევანის გაცნობაც?";
if (question.includes("ტრანსფერ"))
return "სიამოვნებით დაგეხმარებით აეროპორტის ტრანსფერში. მოგვწერეთ ფრენის ნომერი, ჩაფრენის დრო და მგზავრების რაოდენობა. ფასსა და ხელმისაწვდომობას მიმღები დაგიდასტურებთ.";
if (question.includes("გასვლა"))
return "ნომრის დატოვების დრო 12:00-ია. გვიანი გასვლა თავისუფალ ნომრებზეა დამოკიდებული. რომელ საათამდე გსურთ დარჩენა? მიმღებს გადავცემთ მოთხოვნას.";
return "მადლობა შეტყობინებისთვის! ეს სასტუმრო ირისის კონსიერჟის სადემონსტრაციო საუბარია. შეგიძლიათ გვკითხოთ საუზმის, აეროპორტის ტრანსფერის ან გვიანი გასვლის შესახებ.";
}
export function ConciergeDemo() {
const [messages, setMessages] = useState<Turn[]>([
{
id: 0,
role: "assistant",
text: "გამარჯობა, ნინო! კეთილი იყოს თქვენი მობრძანება სასტუმრო ირისში. მე თქვენი AI კონსიერჟი ვარ — რით დაგეხმაროთ?",
},
]);
const [value, setValue] = useState("");
const [stream, setStream] = useState<{ id: number; full: string; text: string } | null>(null);
const sequence = useRef(0);
useEffect(() => {
if (!stream) return;
const timer = setTimeout(
() => {
const text = stream.full.slice(0, stream.text.length + 8);
if (text === stream.full) {
setMessages((current) =>
current.map((message) => (message.id === stream.id ? { ...message, text } : message)),
);
setStream(null);
} else setStream({ ...stream, text });
},
stream.text ? 65 : 650,
);
return () => clearTimeout(timer);
}, [stream]);
const send = (text = value) => {
if (!text.trim() || stream) return;
const id = ++sequence.current;
const replyId = ++sequence.current;
setMessages((current) => [
...current,
{ id, role: "user", text },
{ id: replyId, role: "assistant", text: "" },
]);
setValue("");
setStream({ id: replyId, full: answer(text), text: "" });
};
const stop = () => {
if (!stream) return;
setMessages((current) =>
current.map((message) =>
message.id === stream.id
? { ...message, text: stream.text || "პასუხი შეჩერებულია." }
: message,
),
);
setStream(null);
};
return (
<section
aria-label="სასტუმრო ირისის ჩატის დემო"
className="w-full max-w-xl overflow-hidden rounded-xl border border-border bg-background"
>
<header className="flex items-center gap-3 border-b border-border bg-card p-4">
<Avatar size="sm">
<AvatarFallback name="ირისი" />
</Avatar>
<div>
<h3 className="font-medium">სასტუმრო ირისი</h3>
<p className="text-xs/[1.5] text-muted-foreground">
AI კონსიერჟი · სადემონსტრაციო საუბარი
</p>
</div>
</header>
<MessageList className="h-80" isStreaming={!!stream}>
{messages.map((message) => (
<Message
key={message.id}
messageRole={message.role}
status={message.role === "user" ? "sent" : undefined}
avatar={
message.role === "assistant" ? (
<Avatar size="xs">
<AvatarFallback name="კონსიერჟი" />
</Avatar>
) : undefined
}
>
{message.role === "assistant" ? (
<>
<StreamingText
text={message.id === stream?.id ? stream.text : message.text}
active={message.id === stream?.id}
/>
{message.id === stream?.id && !stream.text && (
<span aria-hidden="true">
<TypingIndicator />
</span>
)}
</>
) : (
message.text
)}
</Message>
))}
</MessageList>
<div className="space-y-2 border-t border-border p-3">
<SuggestionChips
suggestions={suggestions}
onSelect={(suggestion) => send(suggestion.label)}
disabled={!!stream}
/>
<Composer
value={value}
onValueChange={setValue}
onSend={() => send()}
isStreaming={!!stream}
onStop={stop}
/>
</div>
</section>
);
}ინსტალაცია
pnpm add @llc-conciergia/uiimport { MessageList } from "@llc-conciergia/ui/message-list";npx @llc-conciergia/cli add message-listnpx shadcn@latest add https://ui.conciergia.co/r/message-list.jsonშენიშვნები
ქვედა კიდესთან ყოფნისას ახალი ტექსტი ავტომატურად ჩანს. ზემოთ ასქროლვისას პოზიცია რჩება; ბოლო შეტყობინებაზე გადასვლა კლავიატურითაც შეიძლება. მაგალითი ლოკალური სიმულაციაა — მოდელის, ჯავშნის ან ქსელის კავშირი არ აქვს.
Props
| Prop | ტიპი | ნაგულისხმევი | აღწერა |
|---|---|---|---|
| children | ReactNode | — | Message კომპონენტები სტაბილური key-ებით. |
| isStreaming | boolean | — | სტრიმინგისას log-ის ხმოვანი შეტყობინებები ითიშება. |
| className | string | — | მიანიჭეთ სიმაღლე, მაგალითად h-80. |
| aria-label | string | — | საუბრის ხელმისაწვდომი სახელი. |