{"id":"building-ai-chat","name":"building-ai-chat","summary":"AIチャットインターフェースと会話型UIを構築し、ストリーミング応答、コンテキスト管理、マルチモーダルサポートを備えています。","body":"# AI Chat Interface Components\n\n## Purpose\n\nDefine the emerging standards for AI/human conversational interfaces in the 2024-2025 AI integration boom. This skill leverages meta-knowledge from building WITH Claude to establish definitive patterns for streaming UX, context management, and multi-modal interactions. As the industry lacks established patterns, this provides the reference implementation others will follow.\n\n## When to Use\n\nActivate this skill when:\n- Building ChatGPT-style conversational interfaces\n- Creating AI assistants, copilots, or chatbots\n- Implementing streaming text responses with markdown\n- Managing conversation context and token limits\n- Handling multi-modal inputs (text, images, files, voice)\n- Dealing with AI-specific errors (hallucinations, refusals, limits)\n- Adding feedback mechanisms (thumbs, regeneration, editing)\n- Implementing conversation branching or threading\n- Visualizing tool/function calling\n\n## Quick Start\n\nMinimal AI chat interface in under 50 lines:\n\n```tsx\nimport { useChat } from 'ai/react';\n\nexport function MinimalAIChat() {\n  const { messages, input, handleInputChange, handleSubmit, isLoading, stop } = useChat();\n\n  return (\n    <div className=\"chat-container\">\n      <div className=\"messages\">\n        {messages.map(m => (\n          <div key={m.id} className={`message ${m.role}`}>\n            <div className=\"content\">{m.content}</div>\n          </div>\n        ))}\n        {isLoading && <div className=\"thinking\">AI is thinking...</div>}\n      </div>\n\n      <form onSubmit={handleSubmit} className=\"input-form\">\n        <input\n          value={input}\n          onChange={handleInputChange}\n          placeholder=\"Ask anything...\"\n          disabled={isLoading}\n        />\n        {isLoading ? (\n          <button type=\"button\" onClick={stop}>Stop</button>\n        ) : (\n          <button type=\"submit\">Send</button>\n        )}\n      </form>\n    </div>\n  );\n}\n```\n\nFor complete implementation with streaming markdown, see `examples/basic-chat.tsx`.\n\n## Core Components\n\n### Message Display\n\nBuild user, AI, and system message bubbles with streaming support:\n\n```tsx\n// User message\n<div className=\"message user\">\n  <div className=\"content\">{message.content}</div>\n  <time className=\"timestamp\">{formatTime(message.timestamp)}</time>\n</div>\n\n// AI message with streaming\n<div className=\"message ai\">\n  <Streamdown className=\"content\">{message.content}</Streamdown>\n  {message.isStreaming && <span className=\"cursor\">▊</span>}\n</div>\n\n// System message\n<div className=\"message system\">\n  <Icon type=\"info\" />\n  <span>{message.content}</span>\n</div>\n```\n\nFor markdown rendering, code blocks, and formatting details, see `references/message-components.md`.\n\n### Input Components\n\nCreate rich input experiences with attachments and voice:\n\n```tsx\n<div className=\"input-container\">\n  <button onClick={attachFile} aria-label=\"Attach file\">\n    <PaperclipIcon />\n  </button>\n\n  <textarea\n    value={input}\n    onChange={handleChange}\n    onKeyDown={handleKeyDown}\n    placeholder=\"Type a message...\"\n    rows={1}\n    style={{ height: textareaHeight }}\n  />\n\n  <button onClick={toggleVoice} aria-label=\"Voice input\">\n    <MicIcon />\n  </button>\n\n  <button type=\"submit\" disabled={!input.trim() || isLoading}>\n    <SendIcon />\n  </button>\n</div>\n```\n\n### Response Controls\n\nEssential controls for AI responses:\n\n```tsx\n<div className=\"response-controls\">\n  {isStreaming && (\n    <button onClick={stop} className=\"stop-btn\">\n      Stop generating\n    </button>\n  )}\n\n  {!isStreaming && (\n    <>\n      <button onClick={regenerate} aria-label=\"Regenerate response\">\n        <RefreshIcon /> Regenerate\n      </button>\n      <button onClick={continueGeneration} aria-label=\"Continue\">\n        Continue\n      </button>\n      <button onClick={editMessage} aria-label=\"Edit message\">\n        <EditIcon /> Edit\n      </button>\n    </>\n  )}\n</div>\n```\n\n### Feedback Mechanisms\n\nCollect user feedback to improve AI responses:\n\n```tsx\n<div className=\"feedback-controls\">\n  <button\n    onClick={() => sendFeedback('positive')}\n    aria-label=\"Good response\"\n    className={feedback === 'positive' ? 'selected' : ''}\n  >\n    <ThumbsUpIcon />\n  </button>\n\n  <button\n    onClick={() => sendFeedback('negative')}\n    aria-label=\"Bad response\"\n    className={feedback === 'negative' ? 'selected' : ''}\n  >\n    <ThumbsDownIcon />\n  </button>\n\n  <button onClick={copyToClipboard} aria-label=\"Copy\">\n    <CopyIcon />\n  </button>\n\n  <button onClick={share} aria-label=\"Share\">\n    <ShareIcon />\n  </button>\n</div>\n```\n\n## Streaming & Real-Time UX\n\nProgressive rendering of AI responses requires special handling:\n\n```tsx\n// Use Streamdown for AI streaming (handles incomplete markdown)\nimport { Streamdown } from '@vercel/streamdown';\n\n// Auto-scroll management\nuseEffect(() => {\n  if (shouldAutoScroll()) {\n    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });\n  }\n}, [messages]);\n\n// Smart auto-scroll heuristic\nfunction shouldAutoScroll() {\n  const threshold = 100; // px from bottom\n  const isNearBottom =\n    container.scrollHeight - container.scrollTop - container.clientHeight < threshold;\n  const userNotReading = !hasUserScrolledUp && !isTextSelected;\n  return isNearBottom && userNotReading;\n}\n```\n\nFor complete streaming patterns, auto-scroll behavior, and stop generation, see `references/streaming-ux.md`.\n\n## Context Management\n\nCommunicate token limits clearly to users:\n\n```tsx\n// User-friendly token display\nfunction TokenIndicator({ used, total }) {\n  const percentage = (used / total) * 100;\n  const remaining = total - used;\n\n  return (\n    <div className=\"token-indicator\">\n      <div className=\"progress-bar\">\n        <div className=\"progress-fill\" style={{ width: `${percentage}%` }} />\n      </div>\n      <span className=\"token-text\">\n        {percentage > 80\n          ? `⚠️ About ${Math.floor(remaining / 250)} messages left`\n          : `${Math.floor(remaining / 250)} pages of conversation remaining`}\n      </span>\n    </div>\n  );\n}\n```\n\nFor summarization strategies, conversation branching, and organization, see `references/context-management.md`.\n\n## Multi-Modal Support\n\nHandle images, files, and voice inputs:\n\n```tsx\n// Image upload with preview\nfunction ImageUpload({ onUpload }) {\n  return (\n    <div\n      className=\"upload-zone\"\n      onDrop={handleDrop}\n      onDragOver={preventDefault}\n    >\n      <input\n        type=\"file\"\n        accept=\"image/*\"\n        onChange={handleFileSelect}\n        multiple\n        hidden\n        ref={fileInputRef}\n      />\n      {previews.map(preview => (\n        <img key={preview.id} src={preview.url} alt=\"Upload preview\" />\n      ))}\n    </div>\n  );\n}\n```\n\nFor complete multi-modal patterns including voice and screen sharing, see `references/multi-modal.md`.\n\n## Error Handling\n\nHandle AI-specific errors gracefully:\n\n```tsx\n// Refusal handling\nif (response.type === 'refusal') {\n  return (\n    <div className=\"error refusal\">\n      <Icon type=\"info\" />\n      <p>I cannot help with that request.</p>\n      <details>\n        <summary>Why?</summary>\n        <p>{response.reason}</p>\n      </details>\n      <p>Try asking: {response.suggestion}</p>\n    </div>\n  );\n}\n\n// Rate limit communication\nif (error.code === 'RATE_LIMIT') {\n  return (\n    <div className=\"error rate-limit\">\n      <p>Please wait {error.retryAfter} seconds</p>\n      <CountdownTimer seconds={error.retryAfter} onComplete={retry} />\n    </div>\n  );\n}\n```\n\nFor comprehensive error patterns, see `references/error-handling.md`.\n\n## Tool Usage Visualization\n\nShow when AI is using tools or functions:\n\n```tsx\nfunction ToolUsage({ tool }) {\n  return (\n    <div className=\"tool-usage\">\n      <div className=\"tool-header\">\n        <Icon type={tool.type} />\n        <span>{tool.name}</span>\n        {tool.status === 'running' && <Spinner />}\n      </div>\n      {tool.status === 'complete' && (\n        <details>\n          <summary>View details</summary>\n          <pre>{JSON.stringify(tool.result, null, 2)}</pre>\n        </details>\n      )}\n    </div>\n  );\n}\n```\n\nFor function calling, code execution, and web search patterns, see `references/tool-usage.md`.\n\n## Implementation Guide\n\n### Recommended Stack\n\nPrimary libraries (validated November 2025):\n\n```bash\n# Core AI chat functionality\nnpm install ai @ai-sdk/react @ai-sdk/openai\n\n# Streaming markdown rendering\nnpm install @vercel/streamdown\n\n# Syntax highlighting\nnpm install react-syntax-highlighter\n\n# Security for LLM outputs\nnpm install dompurify\n```\n\n### Performance Optimization\n\nCritical for smooth streaming:\n\n```tsx\n// Memoize message rendering\nconst MemoizedMessage = memo(Message, (prev, next) =>\n  prev.content === next.content && prev.isStreaming === next.isStreaming\n);\n\n// Debounce streaming updates\nconst debouncedUpdate = useMemo(\n  () => debounce(updateMessage, 50),\n  []\n);\n\n// Virtual scrolling for long conversations\nimport { VariableSizeList } from 'react-window';\n```\n\nFor detailed performance patterns, see `references/streaming-ux.md`.\n\n### Security Considerations\n\nAlways sanitize AI outputs:\n\n```tsx\nimport DOMPurify from 'dompurify';\n\nfunction SafeAIContent({ content }) {\n  const sanitized = DOMPurify.sanitize(content, {\n    ALLOWED_TAGS: ['p', 'br', 'strong', 'em', 'code', 'pre', 'blockquote', 'ul', 'ol', 'li'],\n    ALLOWED_ATTR: ['class']\n  });\n\n  return <Streamdown>{sanitized}</Streamdown>;\n}\n```\n\n### Accessibility\n\nEnsure AI chat is usable by everyone:\n\n```tsx\n// ARIA live regions for screen readers\n<div role=\"log\" aria-live=\"polite\" aria-relevant=\"additions\">\n  {messages.map(msg => (\n    <article key={msg.id} role=\"article\" aria-label={`${msg.role} message`}>\n      {msg.content}\n    </article>\n  ))}\n</div>\n\n// Loading announcements\n<div role=\"status\" aria-live=\"polite\" className=\"sr-only\">\n  {isLoading ? 'AI is responding' : ''}\n</div>\n```\n\nFor complete accessibility patterns, see `references/accessibility.md`.\n\n## Bundled Resources\n\n### Scripts (Token-Free Execution)\n\n- Run `scripts/parse_stream.js` to parse incomplete markdown during streaming\n- Run `scripts/calculate_tokens.py` to estimate token usage and context limits\n- Run `scripts/format_messages.js` to format message history for export\n\n### References (Progressive Disclosure)\n\n- `references/streaming-patterns.md` - Complete streaming UX patterns\n- `references/context-management.md` - Token limits and conversation strategies\n- `references/multimodal-input.md` - Image, file, and voice handling\n- `references/feedback-loops.md` - User feedback and RLHF patterns\n- `references/error-handling.md` - AI-specific error scenarios\n- `references/tool-usage.md` - Visualizing function calls and tool use\n- `references/accessibility-chat.md` - Screen reader and keyboard support\n- `references/library-guide.md` - Detailed library documentation\n- `references/performance-optimization.md` - Streaming performance patterns\n\n### Examples\n\n- `examples/basic-chat.tsx` - Minimal ChatGPT-style interface\n- `examples/streaming-chat.tsx` - Advanced streaming with memoization\n- `examples/multimodal-chat.tsx` - Images and file uploads\n- `examples/code-assistant.tsx` - IDE-style code copilot\n- `examples/tool-calling-chat.tsx` - Function calling visualization\n\n### Assets\n\n- `assets/system-prompts.json` - Curated prompts for different use cases\n- `assets/message-templates.json` - Pre-built message components\n- `assets/error-messages.json` - User-friendly error messages\n- `assets/themes.json` - Light, dark, and high-contrast themes\n\n## Design Token Integration\n\nAll visual styling uses the design-tokens system:\n\n```css\n/* Message bubbles use design tokens */\n.message.user {\n  background: var(--message-user-bg, var(--color-primary));\n  color: var(--message-user-text, var(--color-white));\n  padding: var(--message-padding, var(--spacing-md));\n  border-radius: var(--message-border-radius, var(--radius-lg));\n}\n\n.message.ai {\n  background: var(--message-ai-bg, var(--color-gray-100));\n  color: var(--message-ai-text, var(--color-text-primary));\n}\n```\n\nSee `skills/design-tokens/` for complete theming system.\n\n## Key Innovations\n\nThis skill provides industry-first solutions for:\n\n- **Memoized streaming rendering** - 10-50x performance improvement\n- **Intelligent auto-scroll** - User activity-aware scrolling\n- **Token metaphors** - User-friendly context communication\n- **Incomplete markdown handling** - Graceful partial rendering\n- **RLHF patterns** - Effective feedback collection\n- **Conversation branching** - Non-linear conversation trees\n- **Multi-modal integration** - Seamless file/image/voice handling\n- **Accessibility-first** - Built-in screen reader support\n\n## Strategic Importance\n\nThis is THE most critical skill because:\n\n1. **Perfect timing** - Every app adding AI (2024-2025 boom)\n2. **No standards exist** - Opportunity to define patterns\n3. **Meta-advantage** - Building WITH Claude = intimate UX knowledge\n4. **Unique challenges** - Streaming, context, hallucinations all new\n5. **Reference implementation** - Can become the standard others follow\n\nMaster this skill to lead the AI interface revolution.","author":"@ancoleman","ownerProfile":null,"authorContacts":null,"sourceUrl":"https://github.com/ancoleman/ai-design-components/tree/main/skills/building-ai-chat","license":"MIT","category":"coding","lang":"en","tokens":3053,"stars":0,"calls30d":1,"claimed":false,"visibility":"public","origin":"crawler","version":"0.1.0","createdAt":"2026-08-22","updatedAt":"2026-08-22","files":[{"path":"assets/error-messages.json","size":11904,"sha256":"4a48d5a97b5749b167d695bbbb469bc5b043abdf804bc0b0208a438a95924910"},{"path":"assets/message-templates.json","size":8398,"sha256":"a9104c1781f11e8ec74efe211d2f8df35c87c330f02c8b174effa4126fddafac"},{"path":"assets/system-prompts.json","size":5553,"sha256":"3272d6b06f576083065d5c9ee4535d147b0e27281d4e8cc28620ff7575fd6aad"},{"path":"assets/themes.json","size":10173,"sha256":"bf2ba6e7ef94975639b9a651232e3bb5d25f3a3761bb437516438c93ce391a8c"},{"path":"examples/basic-chat.tsx","size":10936,"sha256":"fe5f21c82fae3e496e9c60dc3543cebcdac42d754705df597c3baa8b73d3ceaa"},{"path":"examples/code-assistant.tsx","size":8510,"sha256":"0dd9013cfbda9f2e71aaaaf612b1679aa1ff153a2fd3efa61229651882c04658"},{"path":"examples/multimodal-chat.tsx","size":7910,"sha256":"c5985c2c331c7f8b78c944c8bc16bcbe4e4a9fd9c0b757e753ed640dc2db1af0"},{"path":"examples/streaming-chat.tsx","size":6559,"sha256":"445fad626793b1c99c09d669db39cbd8a338b9421c506132baa8d24015118350"},{"path":"examples/tool-calling-chat.tsx","size":9677,"sha256":"d587b1cf97f000b4ca625f6db37ff72f39629018b5e2602fa8130b881360db41"},{"path":"outputs.yaml","size":20201,"sha256":"3b4d7557565fa454e9332afeb33937fbcecec193d42a2725c25b6f10deea89af"},{"path":"references/accessibility-chat.md","size":23193,"sha256":"59eb9b434062d9484853aa74adb286547b84fbee0743291e4f8715c090bb8b5e"},{"path":"references/accessibility.md","size":7868,"sha256":"498da057db430dafb87921c680643f6acb94bc23401215cb60f629cb2b339ea9"},{"path":"references/context-management.md","size":31022,"sha256":"3683f59fcc7a9f6f8217a068ebf3fec328fe8d4f176f50390b417b89de7c8cfe"},{"path":"references/error-handling.md","size":27937,"sha256":"97d8ee19138c27418f75553cea03cfd2381f531bd683ee917f3283351dda29dc"},{"path":"references/feedback-loops.md","size":30485,"sha256":"c1b3b57d516e830bef84f49c15472bcbc1d6f4c10b89d79be4bd607f3f148de8"},{"path":"references/library-guide.md","size":19568,"sha256":"0d18bc690ece474c9933453976cb367c5d6803890078e9b0ca5cb8b3ef97ee10"},{"path":"references/message-components.md","size":10428,"sha256":"8dee9266acfb8976c245081486dcb6f76bdd908a0f8bb0458e78c7893ac6422a"},{"path":"references/multimodal-input.md","size":39373,"sha256":"88af3bdd76d76a85ce8022a89a9138c9150fcac2716d7dd20b3e8aa81b2cbc74"},{"path":"references/multi-modal.md","size":9862,"sha256":"890c9230201703e5ade6de90aba90a54c171168f35579d16e73b55f93ce2d177"},{"path":"references/performance-optimization.md","size":20908,"sha256":"efaea8747e7625d3b4d2f4c4cd47a300732e17c3299e4a03d0c16faa4ff6d1ef"},{"path":"references/streaming-patterns.md","size":17983,"sha256":"8b0f7048f68f649115757519aaa7cba377c98e9c8ed457fac27f34cdcaf02abe"},{"path":"references/streaming-ux.md","size":9779,"sha256":"e8d7993c80cc7e694ed1befa1860e433bae72da00c21fe26eb1bac4907de6670"},{"path":"references/tool-usage.md","size":29130,"sha256":"bc89ec1375fe8d26116f83079a9d8aa7e6fffbf8b6bfacb5792f81779ca180a7"},{"path":"scripts/calculate_tokens.py","size":12400,"sha256":"1fb59b89faf6d5b812254110c3d2885e6a63fc8138309db10d94cc729a53f383"}],"requires":{"mcp":[],"tools":[]},"safety":{"flags":[{"code":"code.eval","kind":"dangerous-code","where":"examples/code-assistant.tsx:103","excerpt":"exec(","message":"evaluates code at runtime","severity":"warn"},{"code":"injection.zero-width","kind":"injection","where":"examples/code-assistant.tsx:73","message":"contains zero-width or bidirectional control characters","severity":"warn"},{"code":"code.eval","kind":"dangerous-code","where":"examples/tool-calling-chat.tsx:267","excerpt":"eval(","message":"evaluates code at runtime","severity":"warn"},{"code":"code.eval","kind":"dangerous-code","where":"references/library-guide.md:153","excerpt":"eval(","message":"evaluates code at runtime","severity":"warn"},{"code":"code.eval","kind":"dangerous-code","where":"references/streaming-ux.md:94","excerpt":"exec(","message":"evaluates code at runtime","severity":"warn"},{"code":"net.endpoints","kind":"exfiltration","excerpt":"ai.google.dev, api.openai.com, platform.openai.com, react-spectrum.adobe.com, www.radix-ui.com, yourdomain.com","message":"bundled scripts reach 6 external host(s)","severity":"warn"}],"scannedAt":"2026-08-22","hasScripts":true,"networkEndpoints":["ai.google.dev","api.openai.com","platform.openai.com","react-spectrum.adobe.com","www.radix-ui.com","yourdomain.com"]}}