diff --git a/src/components/features/ChatInput.tsx b/src/components/features/ChatInput.tsx
new file mode 100644
index 0000000..27352a6
--- /dev/null
+++ b/src/components/features/ChatInput.tsx
@@ -0,0 +1,126 @@
+'use client';
+
+import { useState } from 'react';
+import { Plus, Clock, ArrowUp } from 'lucide-react';
+import { ModelSelector } from './ModelSelector';
+import { ToolsDropdown } from './ToolsDropdown';
+import { cn } from '@/lib/utils';
+import type { Model, Tool } from '@/types';
+
+interface ChatInputProps {
+ models: Model[];
+ selectedModel: Model;
+ onModelSelect: (model: Model) => void;
+ tools: Tool[];
+ onToolToggle: (toolId: string) => void;
+ onEnableAllTools: (enabled: boolean) => void;
+ onSend: (message: string) => void;
+ placeholder?: string;
+ className?: string;
+}
+
+export function ChatInput({
+ models,
+ selectedModel,
+ onModelSelect,
+ tools,
+ onToolToggle,
+ onEnableAllTools,
+ onSend,
+ placeholder = 'How can I help you today?',
+ className,
+}: ChatInputProps) {
+ const [message, setMessage] = useState('');
+
+ const handleSend = () => {
+ if (message.trim()) {
+ onSend(message);
+ setMessage('');
+ }
+ };
+
+ const handleKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === 'Enter' && !e.shiftKey) {
+ e.preventDefault();
+ handleSend();
+ }
+ };
+
+ return (
+
+
+ {/* 第一行:输入区域 */}
+
+ setMessage(e.target.value)}
+ onKeyDown={handleKeyDown}
+ placeholder={placeholder}
+ className="w-full border-none outline-none text-base text-[var(--color-text-primary)] bg-transparent py-2 placeholder:text-[var(--color-text-placeholder)]"
+ />
+
+
+ {/* 第二行:功能按钮区域 */}
+
+ {/* 左侧按钮 */}
+
+ {/* 添加附件 */}
+
+
+ {/* 工具下拉 */}
+
+
+ {/* 历史记录 */}
+
+
+
+ {/* 右侧按钮 */}
+
+ {/* 模型选择器 */}
+
+
+ {/* 发送按钮 */}
+
+
+
+
+
+ );
+}
diff --git a/src/components/features/MessageBubble.tsx b/src/components/features/MessageBubble.tsx
new file mode 100644
index 0000000..395b8d4
--- /dev/null
+++ b/src/components/features/MessageBubble.tsx
@@ -0,0 +1,140 @@
+'use client';
+
+import { Copy, ThumbsUp, ThumbsDown, RefreshCw } from 'lucide-react';
+import { Avatar } from '@/components/ui/Avatar';
+import { AILogo } from '@/components/ui/AILogo';
+import { cn } from '@/lib/utils';
+import type { Message, User } from '@/types';
+
+interface MessageBubbleProps {
+ message: Message;
+ user?: User;
+}
+
+export function MessageBubble({ message, user }: MessageBubbleProps) {
+ const isUser = message.role === 'user';
+
+ // 简单的 Markdown 渲染
+ const renderContent = (content: string) => {
+ // 处理代码块
+ const parts = content.split(/(`[^`]+`)/g);
+ return parts.map((part, index) => {
+ if (part.startsWith('`') && part.endsWith('`')) {
+ return (
+
+ {part.slice(1, -1)}
+
+ );
+ }
+ // 处理粗体
+ const boldParts = part.split(/(\*\*[^*]+\*\*)/g);
+ return boldParts.map((boldPart, boldIndex) => {
+ if (boldPart.startsWith('**') && boldPart.endsWith('**')) {
+ return (
+
+ {boldPart.slice(2, -2)}
+
+ );
+ }
+ return {boldPart};
+ });
+ });
+ };
+
+ if (isUser) {
+ return (
+
+
+
+ {message.content}
+
+
+ {user &&
}
+
+ );
+ }
+
+ return (
+
+ {/* AI 图标 */}
+
+
+ {/* 消息内容 */}
+
+
+
+ {message.content.split('\n\n').map((paragraph, index) => {
+ // 处理列表
+ if (paragraph.startsWith('- ') || paragraph.startsWith('* ')) {
+ const items = paragraph.split('\n');
+ return (
+
+ {items.map((item, i) => (
+ -
+ {renderContent(item.replace(/^[-*]\s/, ''))}
+
+ ))}
+
+ );
+ }
+ // 处理有序列表
+ if (/^\d+\.\s/.test(paragraph)) {
+ const items = paragraph.split('\n');
+ return (
+
+ {items.map((item, i) => (
+ -
+ {renderContent(item.replace(/^\d+\.\s/, ''))}
+
+ ))}
+
+ );
+ }
+ return (
+
+ {renderContent(paragraph)}
+
+ );
+ })}
+
+
+ {/* 操作按钮 */}
+
+
+ {/* 免责声明 */}
+
+ cchcode can make mistakes
+
+
+
+
+ );
+}
+
+interface ActionButtonProps {
+ icon: React.ComponentType<{ size?: number }>;
+ title: string;
+ onClick?: () => void;
+}
+
+function ActionButton({ icon: Icon, title, onClick }: ActionButtonProps) {
+ return (
+
+ );
+}
diff --git a/src/components/features/ModelSelector.tsx b/src/components/features/ModelSelector.tsx
new file mode 100644
index 0000000..028c6d0
--- /dev/null
+++ b/src/components/features/ModelSelector.tsx
@@ -0,0 +1,80 @@
+'use client';
+
+import { useState, useRef, useEffect } from 'react';
+import { ChevronDown } from 'lucide-react';
+import { cn } from '@/lib/utils';
+import type { Model } from '@/types';
+
+interface ModelSelectorProps {
+ models: Model[];
+ selectedModel: Model;
+ onSelect: (model: Model) => void;
+}
+
+export function ModelSelector({ models, selectedModel, onSelect }: ModelSelectorProps) {
+ const [isOpen, setIsOpen] = useState(false);
+ const dropdownRef = useRef(null);
+
+ // 点击外部关闭
+ useEffect(() => {
+ function handleClickOutside(event: MouseEvent) {
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
+ setIsOpen(false);
+ }
+ }
+
+ document.addEventListener('mousedown', handleClickOutside);
+ return () => document.removeEventListener('mousedown', handleClickOutside);
+ }, []);
+
+ return (
+
+ {/* 触发按钮 */}
+
+
+ {/* 下拉菜单 */}
+
+ {models.map((model) => {
+ const isSelected = model.id === selectedModel.id;
+ return (
+
+ );
+ })}
+
+
+ );
+}
diff --git a/src/components/features/QuickActions.tsx b/src/components/features/QuickActions.tsx
new file mode 100644
index 0000000..4a8b63b
--- /dev/null
+++ b/src/components/features/QuickActions.tsx
@@ -0,0 +1,45 @@
+'use client';
+
+import { Code, PenTool, GraduationCap, Home, Sparkles } from 'lucide-react';
+import { cn } from '@/lib/utils';
+import type { QuickAction } from '@/types';
+
+const iconMap: Record> = {
+ Code,
+ PenTool,
+ GraduationCap,
+ Home,
+ Sparkles,
+};
+
+interface QuickActionsProps {
+ actions: QuickAction[];
+ onSelect: (action: QuickAction) => void;
+ className?: string;
+}
+
+export function QuickActions({ actions, onSelect, className }: QuickActionsProps) {
+ return (
+
+ {actions.map((action) => {
+ const Icon = iconMap[action.icon];
+ return (
+
+ );
+ })}
+
+ );
+}
diff --git a/src/components/features/ToolsDropdown.tsx b/src/components/features/ToolsDropdown.tsx
new file mode 100644
index 0000000..d9c0296
--- /dev/null
+++ b/src/components/features/ToolsDropdown.tsx
@@ -0,0 +1,105 @@
+'use client';
+
+import { useState, useRef, useEffect } from 'react';
+import { Wrench, Search, Terminal, Globe, Check } from 'lucide-react';
+import { Toggle } from '@/components/ui/Toggle';
+import { cn } from '@/lib/utils';
+import type { Tool } from '@/types';
+
+const iconMap: Record> = {
+ Search,
+ Terminal,
+ Globe,
+};
+
+interface ToolsDropdownProps {
+ tools: Tool[];
+ onToolToggle: (toolId: string) => void;
+ onEnableAllToggle: (enabled: boolean) => void;
+}
+
+export function ToolsDropdown({ tools, onToolToggle, onEnableAllToggle }: ToolsDropdownProps) {
+ const [isOpen, setIsOpen] = useState(false);
+ const dropdownRef = useRef(null);
+
+ const enabledCount = tools.filter((t) => t.enabled).length;
+ const allEnabled = enabledCount > 0;
+
+ // 点击外部关闭
+ useEffect(() => {
+ function handleClickOutside(event: MouseEvent) {
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
+ setIsOpen(false);
+ }
+ }
+
+ document.addEventListener('mousedown', handleClickOutside);
+ return () => document.removeEventListener('mousedown', handleClickOutside);
+ }, []);
+
+ return (
+
+ {/* 触发按钮 */}
+
+
+ {enabledCount > 0 && (
+
+ {enabledCount}
+
+ )}
+
+
+ {/* 下拉菜单 */}
+
+ {/* Header */}
+
+ Enable Tools
+
+
+
+ {/* 工具列表 */}
+ {tools.map((tool) => {
+ const Icon = iconMap[tool.icon];
+ return (
+
+ );
+ })}
+
+
+ );
+}
diff --git a/src/components/features/Welcome.tsx b/src/components/features/Welcome.tsx
new file mode 100644
index 0000000..e3b2b30
--- /dev/null
+++ b/src/components/features/Welcome.tsx
@@ -0,0 +1,25 @@
+'use client';
+
+import { AILogo } from '@/components/ui/AILogo';
+import { cn } from '@/lib/utils';
+
+interface WelcomeProps {
+ greeting: string;
+ className?: string;
+}
+
+export function Welcome({ greeting, className }: WelcomeProps) {
+ return (
+
+ {/* 装饰图标 */}
+
+
+ {/* 问候语 */}
+
+ {greeting}
+
+
+ );
+}