feat(desktop): simple mode UI — ChatArea compact + SimpleSidebar + RightPanel dual-mode

Adapt ChatArea for compact/butler mode:
- Add onOpenDetail prop for expanding to full view
- Remove inline export dialog (moved to detail view)
- Replace SquarePen with ClipboardList icon

Add SimpleSidebar component for butler simple mode:
- Two tabs: 对话 / 行业资讯
- Quick suggestion buttons
- Minimal navigation

RightPanel refactoring for dual-mode support:
- Detect simple vs professional mode
- Conditional rendering based on butler mode state
This commit is contained in:
iven
2026-04-09 17:48:18 +08:00
parent 4b15ead8e7
commit 2f25316e83
3 changed files with 213 additions and 93 deletions

View File

@@ -10,7 +10,7 @@ import { useConfigStore } from '../store/configStore';
import { useSaaSStore } from '../store/saasStore';
import { type UnlistenFn } from '@tauri-apps/api/event';
import { safeListenEvent } from '../lib/safe-tauri';
import { Paperclip, SquarePen, ArrowUp, MessageSquare, Download, X, FileText, Image as ImageIcon, Search } from 'lucide-react';
import { Paperclip, ArrowUp, MessageSquare, Download, X, FileText, Image as ImageIcon, Search, ClipboardList } from 'lucide-react';
import { Button, EmptyState, MessageListSkeleton, LoadingDots } from './ui';
import { ResizableChatLayout } from './ai/ResizableChatLayout';
import { ArtifactPanel } from './ai/ArtifactPanel';
@@ -49,11 +49,11 @@ const DEFAULT_MESSAGE_HEIGHTS: Record<string, number> = {
// Threshold for enabling virtualization (messages count)
const VIRTUALIZATION_THRESHOLD = 100;
export function ChatArea({ compact }: { compact?: boolean }) {
export function ChatArea({ compact, onOpenDetail }: { compact?: boolean; onOpenDetail?: () => void }) {
const {
messages, isStreaming, isLoading,
sendMessage: sendToGateway, initStreamListener,
newConversation, chatMode, setChatMode, suggestions,
chatMode, setChatMode, suggestions,
totalInputTokens, totalOutputTokens,
} = useChatStore();
const currentAgent = useConversationStore((s) => s.currentAgent);
@@ -239,23 +239,6 @@ export function ChatArea({ compact }: { compact?: boolean }) {
const connected = connectionState === 'connected';
// Export current conversation as Markdown
const exportCurrentConversation = () => {
const title = currentAgent?.name || 'ZCLAW 对话';
const lines = [`# ${title}`, '', `导出时间: ${new Date().toLocaleString('zh-CN')}`, ''];
for (const msg of messages) {
const label = msg.role === 'user' ? '用户' : msg.role === 'assistant' ? '助手' : msg.role;
lines.push(`## ${label}`, '', msg.content, '');
}
const blob = new Blob([lines.join('\n')], { type: 'text/markdown;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${title.replace(/[/\\?%*:|"<>]/g, '_')}.md`;
a.click();
URL.revokeObjectURL(url);
};
// Build artifact panel content
const artifactRightPanel = (
<ArtifactPanel
@@ -364,28 +347,16 @@ export function ChatArea({ compact }: { compact?: boolean }) {
<Search className="w-3.5 h-3.5" />
</Button>
)}
{messages.length > 0 && (
{/* 详情按钮 (简洁模式) */}
{compact && onOpenDetail && (
<Button
variant="ghost"
size="sm"
onClick={exportCurrentConversation}
className="flex items-center gap-2 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 rounded-lg transition-colors"
title="导出对话"
onClick={onOpenDetail}
className="flex items-center gap-1 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 rounded-lg transition-colors"
title="详情"
>
<Download className="w-3.5 h-3.5" />
<span className="text-sm"></span>
</Button>
)}
{messages.length > 0 && (
<Button
variant="ghost"
size="sm"
onClick={newConversation}
className="flex items-center gap-2 text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 rounded-lg transition-colors"
title="新对话"
>
<SquarePen className="w-3.5 h-3.5" />
<ClipboardList className="w-3.5 h-3.5" />
</Button>
)}
</div>