{
	"$schema": "https://shadcn-svelte.com/schema/registry-item.json",
	"name": "voice-chat-03",
	"title": "Voice Chat 03",
	"type": "registry:block",
	"description": "A tall voice-chat card with a conversation bar anchored at the bottom, an empty-state orb, and per-message copy buttons.",
	"devDependencies": [
		"@lucide/svelte@^1.7.0"
	],
	"registryDependencies": [
		"button",
		"card",
		"https://sv11.ui.twango.dev/r/conversation.json",
		"https://sv11.ui.twango.dev/r/conversation-bar.json",
		"https://sv11.ui.twango.dev/r/message.json",
		"https://sv11.ui.twango.dev/r/orb.json",
		"https://sv11.ui.twango.dev/r/response.json",
		"tooltip"
	],
	"files": [
		{
			"content": "<script lang=\"ts\">\n\timport CheckIcon from \"@lucide/svelte/icons/check\";\n\timport CopyIcon from \"@lucide/svelte/icons/copy\";\n\timport { Button } from \"$UI$/button/index.js\";\n\timport { Card, CardContent } from \"$UI$/card/index.js\";\n\timport {\n\t\tConversation,\n\t\tConversationContent,\n\t\tConversationEmptyState,\n\t\tConversationScrollButton,\n\t} from \"$UI$/conversation/index.js\";\n\timport { ConversationBar } from \"$UI$/conversation-bar/index.js\";\n\timport type {\n\t\tConversationAdapter,\n\t\tConversationConfig,\n\t\tConversationMessage,\n\t} from \"$UI$/conversation-bar/types.js\";\n\timport { Message, MessageContent } from \"$UI$/message/index.js\";\n\timport { Orb } from \"$UI$/orb/index.js\";\n\timport { Response } from \"$UI$/response/index.js\";\n\timport {\n\t\tTooltip,\n\t\tTooltipContent,\n\t\tTooltipProvider,\n\t\tTooltipTrigger,\n\t} from \"$UI$/tooltip/index.js\";\n\timport { cn } from \"$UTILS$.js\";\n\n\tlet { class: className }: { class?: string } = $props();\n\n\ttype ChatMessage = {\n\t\trole: \"user\" | \"assistant\";\n\t\tcontent: string;\n\t};\n\n\tconst GREETING = \"Hi, I'm your voice assistant. Tap the phone button or type a message to begin.\";\n\tconst CANNED_REPLIES = [\n\t\t\"Got it — tell me a bit more about what you're after.\",\n\t\t\"Sure, happy to help. Could you give me an example?\",\n\t\t\"Here's what I'd suggest as a starting point — let me know if you want to go deeper on any part.\",\n\t\t\"That's a good point. Want me to expand on it?\",\n\t];\n\n\tlet messages = $state<ChatMessage[]>([]);\n\tlet copiedIndex = $state<number | null>(null);\n\tlet timers: ReturnType<typeof setTimeout>[] = [];\n\tlet replyIndex = 0;\n\n\tfunction clearTimers() {\n\t\ttimers.forEach(clearTimeout);\n\t\ttimers = [];\n\t}\n\n\tfunction pushMessage(message: ChatMessage) {\n\t\tmessages = [...messages, message];\n\t}\n\n\tconst cannedAdapter: ConversationAdapter = {\n\t\tasync connect(config: ConversationConfig) {\n\t\t\tclearTimers();\n\t\t\tconfig.onStatusChange(\"connecting\");\n\t\t\ttimers.push(\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tconfig.onStatusChange(\"connected\");\n\t\t\t\t\tconfig.onConnect();\n\t\t\t\t\ttimers.push(\n\t\t\t\t\t\tsetTimeout(() => {\n\t\t\t\t\t\t\tconst greeting: ConversationMessage = { source: \"ai\", message: GREETING };\n\t\t\t\t\t\t\tconfig.onMessage(greeting);\n\t\t\t\t\t\t}, 600)\n\t\t\t\t\t);\n\t\t\t\t}, 900)\n\t\t\t);\n\t\t},\n\t\tdisconnect() {\n\t\t\tclearTimers();\n\t\t},\n\t\tsendMessage() {\n\t\t\tconst reply = CANNED_REPLIES[replyIndex % CANNED_REPLIES.length];\n\t\t\treplyIndex += 1;\n\t\t\ttimers.push(\n\t\t\t\tsetTimeout(() => {\n\t\t\t\t\tpushMessage({ role: \"assistant\", content: reply });\n\t\t\t\t}, 900)\n\t\t\t);\n\t\t},\n\t\tsendContextualUpdate() {\n\t\t\t// no-op in canned demo\n\t\t},\n\t\tsetMuted() {\n\t\t\t// no-op in canned demo\n\t\t},\n\t};\n\n\tfunction handleConnect() {\n\t\treplyIndex = 0;\n\t\tmessages = [];\n\t}\n\n\tfunction handleDisconnect() {\n\t\tclearTimers();\n\t\tmessages = [];\n\t}\n\n\tfunction handleMessage(message: ConversationMessage) {\n\t\tpushMessage({\n\t\t\trole: message.source === \"user\" ? \"user\" : \"assistant\",\n\t\t\tcontent: message.message,\n\t\t});\n\t}\n\n\tfunction handleSendMessage(text: string) {\n\t\tpushMessage({ role: \"user\", content: text });\n\t}\n\n\tasync function handleCopy(index: number, content: string) {\n\t\ttry {\n\t\t\tawait navigator.clipboard.writeText(content);\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\t\tcopiedIndex = index;\n\t\ttimers.push(\n\t\t\tsetTimeout(() => {\n\t\t\t\tif (copiedIndex === index) copiedIndex = null;\n\t\t\t}, 2000)\n\t\t);\n\t}\n\n\t$effect(() => {\n\t\treturn () => clearTimers();\n\t});\n</script>\n\n<div class={cn(\"relative mx-auto h-[600px] w-full\", className)}>\n\t<Card class=\"flex h-full w-full flex-col gap-0 overflow-hidden\">\n\t\t<CardContent class=\"relative flex-1 overflow-hidden p-0\">\n\t\t\t<Conversation class=\"absolute inset-0 pb-[88px]\">\n\t\t\t\t<ConversationContent class=\"flex min-w-0 flex-col gap-2 p-6 pb-6\">\n\t\t\t\t\t{#if messages.length === 0}\n\t\t\t\t\t\t<ConversationEmptyState\n\t\t\t\t\t\t\ttitle=\"Start a conversation\"\n\t\t\t\t\t\t\tdescription=\"Tap the phone button or type a message\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{#snippet icon()}\n\t\t\t\t\t\t\t\t<Orb class=\"size-12\" />\n\t\t\t\t\t\t\t{/snippet}\n\t\t\t\t\t\t</ConversationEmptyState>\n\t\t\t\t\t{:else}\n\t\t\t\t\t\t{#each messages as message, index (index)}\n\t\t\t\t\t\t\t<div class=\"flex w-full flex-col gap-1\">\n\t\t\t\t\t\t\t\t<Message from={message.role}>\n\t\t\t\t\t\t\t\t\t<MessageContent class=\"max-w-full min-w-0\">\n\t\t\t\t\t\t\t\t\t\t<Response\n\t\t\t\t\t\t\t\t\t\t\tcontent={message.content}\n\t\t\t\t\t\t\t\t\t\t\tclass=\"w-auto [overflow-wrap:anywhere] whitespace-pre-wrap\"\n\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t</MessageContent>\n\t\t\t\t\t\t\t\t\t{#if message.role === \"assistant\"}\n\t\t\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\t\t\tclass=\"ring-border size-6 flex-shrink-0 self-end overflow-hidden rounded-full ring-1\"\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t<Orb class=\"h-full w-full\" />\n\t\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t\t{/if}\n\t\t\t\t\t\t\t\t</Message>\n\t\t\t\t\t\t\t\t{#if message.role === \"assistant\"}\n\t\t\t\t\t\t\t\t\t<div class=\"flex items-center gap-1\">\n\t\t\t\t\t\t\t\t\t\t<TooltipProvider>\n\t\t\t\t\t\t\t\t\t\t\t<Tooltip>\n\t\t\t\t\t\t\t\t\t\t\t\t<TooltipTrigger>\n\t\t\t\t\t\t\t\t\t\t\t\t\t{#snippet child({ props })}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{...props}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tclass=\"text-muted-foreground hover:text-foreground relative size-9 p-1.5\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tonclick={() => handleCopy(index, message.content)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{#if copiedIndex === index}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<CheckIcon class=\"size-4\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{:else}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<CopyIcon class=\"size-4\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{/if}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"sr-only\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t>{copiedIndex === index ? \"Copied!\" : \"Copy\"}</span\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t\t\t\t\t\t\t{/snippet}\n\t\t\t\t\t\t\t\t\t\t\t\t</TooltipTrigger>\n\t\t\t\t\t\t\t\t\t\t\t\t<TooltipContent>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<p>{copiedIndex === index ? \"Copied!\" : \"Copy\"}</p>\n\t\t\t\t\t\t\t\t\t\t\t\t</TooltipContent>\n\t\t\t\t\t\t\t\t\t\t\t</Tooltip>\n\t\t\t\t\t\t\t\t\t\t</TooltipProvider>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t{/if}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t{/each}\n\t\t\t\t\t{/if}\n\t\t\t\t</ConversationContent>\n\t\t\t\t<ConversationScrollButton class=\"bottom-[100px]\" />\n\t\t\t</Conversation>\n\t\t\t<div class=\"absolute right-0 bottom-0 left-0 flex justify-center\">\n\t\t\t\t<ConversationBar\n\t\t\t\t\tclass=\"w-full max-w-2xl\"\n\t\t\t\t\tadapter={cannedAdapter}\n\t\t\t\t\tonConnect={handleConnect}\n\t\t\t\t\tonDisconnect={handleDisconnect}\n\t\t\t\t\tonMessage={handleMessage}\n\t\t\t\t\tonSendMessage={handleSendMessage}\n\t\t\t\t\tonError={(error) => console.error(\"Conversation error:\", error)}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</CardContent>\n\t</Card>\n</div>\n",
			"type": "registry:block",
			"target": "components/blocks/voice-chat-03/voice-chat-03.svelte"
		},
		{
			"content": "import VoiceChat03 from \"./voice-chat-03.svelte\";\n\nexport { VoiceChat03, VoiceChat03 as default };\n",
			"type": "registry:block",
			"target": "components/blocks/voice-chat-03/index.ts"
		}
	]
}