refactor(chat): 全屏/半屏切换移入 ChatPanel 内部

- Header 恢复单个"AI 问答"按钮,点击默认打开半屏模式
- ChatPanel 头部新增全屏/半屏切换按钮(Maximize2/Minimize2 图标)
- 半屏:markdown 与问答并排各占一半
- 全屏:问答占满内容区,隐藏 markdown

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
huangjianwu
2026-03-23 15:23:55 +08:00
co-authored by Claude Opus 4.6
parent dea393e713
commit d8fbceaadf
3 changed files with 45 additions and 45 deletions
@@ -3,14 +3,18 @@ import { Bubble, Sender } from '@ant-design/x'
import type { BubbleProps } from '@ant-design/x' import type { BubbleProps } from '@ant-design/x'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge' import { Badge } from '@/components/ui/badge'
import { Loader2, Trash2, ChevronDown, ChevronUp, BookOpen, UserRound, Bot } from 'lucide-react' import { Loader2, Trash2, ChevronDown, ChevronUp, BookOpen, UserRound, Bot, Maximize2, Minimize2 } from 'lucide-react'
import { toast } from 'react-hot-toast' import { toast } from 'react-hot-toast'
import { useChatStore } from '@/store/chatStore' import { useChatStore } from '@/store/chatStore'
import { useTaskStore } from '@/store/taskStore' import { useTaskStore } from '@/store/taskStore'
import { askQuestion, getChatStatus, indexTask, type ChatSource, type IndexStatus } from '@/services/chat' import { askQuestion, getChatStatus, indexTask, type ChatSource, type IndexStatus } from '@/services/chat'
type ChatMode = 'half' | 'full'
interface ChatPanelProps { interface ChatPanelProps {
taskId: string taskId: string
mode: ChatMode
onModeChange: (mode: ChatMode) => void
} }
function SourceBadges({ sources }: { sources: ChatSource[] }) { function SourceBadges({ sources }: { sources: ChatSource[] }) {
@@ -43,7 +47,7 @@ function SourceBadges({ sources }: { sources: ChatSource[] }) {
) )
} }
export default function ChatPanel({ taskId }: ChatPanelProps) { export default function ChatPanel({ taskId, mode, onModeChange }: ChatPanelProps) {
const [input, setInput] = useState('') const [input, setInput] = useState('')
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [indexStatus, setIndexStatus] = useState<IndexStatus | null>(null) const [indexStatus, setIndexStatus] = useState<IndexStatus | null>(null)
@@ -218,16 +222,31 @@ export default function ChatPanel({ taskId }: ChatPanelProps) {
{/* 头部 */} {/* 头部 */}
<div className="flex items-center justify-between border-b px-3 py-2"> <div className="flex items-center justify-between border-b px-3 py-2">
<span className="text-sm font-medium">AI </span> <span className="text-sm font-medium">AI </span>
{messages.length > 0 && ( <div className="flex items-center gap-1">
<Button <Button
variant="ghost" variant="ghost"
size="sm" size="sm"
className="h-7 px-2 text-neutral-400 hover:text-red-500" className="h-7 px-2 text-neutral-400 hover:text-neutral-600"
onClick={() => clearChat(taskId)} onClick={() => onModeChange(mode === 'half' ? 'full' : 'half')}
title={mode === 'half' ? '全屏' : '半屏'}
> >
<Trash2 className="h-3.5 w-3.5" /> {mode === 'half' ? (
<Maximize2 className="h-3.5 w-3.5" />
) : (
<Minimize2 className="h-3.5 w-3.5" />
)}
</Button> </Button>
)} {messages.length > 0 && (
<Button
variant="ghost"
size="sm"
className="h-7 px-2 text-neutral-400 hover:text-red-500"
onClick={() => clearChat(taskId)}
>
<Trash2 className="h-3.5 w-3.5" />
</Button>
)}
</div>
</div> </div>
{/* 消息列表 */} {/* 消息列表 */}
@@ -1,7 +1,7 @@
'use client' 'use client'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Copy, Download, BrainCircuit, MessageSquare, PanelRight, Maximize2 } from 'lucide-react' import { Copy, Download, BrainCircuit, MessageSquare } from 'lucide-react'
import { Button } from '@/components/ui/button' import { Button } from '@/components/ui/button'
import { Select, SelectContent, SelectItem, SelectTrigger } from '@/components/ui/select' import { Select, SelectContent, SelectItem, SelectTrigger } from '@/components/ui/select'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
@@ -188,40 +188,22 @@ export function MarkdownHeader({
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
{setShowChat && ( {setShowChat && (
<div className="flex items-center gap-0.5 ml-1"> <TooltipProvider>
<TooltipProvider> <Tooltip>
<Tooltip> <TooltipTrigger asChild>
<TooltipTrigger asChild> <Button
<Button onClick={() => setShowChat(showChat ? false : 'half')}
onClick={() => setShowChat(showChat === 'half' ? false : 'half')} variant={showChat ? 'default' : 'ghost'}
variant={showChat === 'half' ? 'default' : 'ghost'} size="sm"
size="sm" className="h-8 px-2"
className="h-8 px-2" >
> <MessageSquare className="mr-1.5 h-4 w-4" />
<PanelRight className="mr-1.5 h-4 w-4" /> <span className="text-sm">AI </span>
<span className="text-sm"></span> </Button>
</Button> </TooltipTrigger>
</TooltipTrigger> <TooltipContent> AI </TooltipContent>
<TooltipContent></TooltipContent> </Tooltip>
</Tooltip> </TooltipProvider>
</TooltipProvider>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Button
onClick={() => setShowChat(showChat === 'full' ? false : 'full')}
variant={showChat === 'full' ? 'default' : 'ghost'}
size="sm"
className="h-8 px-2"
>
<Maximize2 className="mr-1.5 h-4 w-4" />
<span className="text-sm"></span>
</Button>
</TooltipTrigger>
<TooltipContent></TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
)} )}
</div> </div>
</div> </div>
@@ -221,10 +221,9 @@ const MarkdownViewer: FC<MarkdownViewerProps> = ({ status }) => {
<div className="flex flex-1 overflow-hidden bg-white py-2"> <div className="flex flex-1 overflow-hidden bg-white py-2">
{selectedContent && selectedContent !== 'loading' && selectedContent !== 'empty' ? ( {selectedContent && selectedContent !== 'loading' && selectedContent !== 'empty' ? (
<> <>
{/* 全屏问答模式:隐藏 markdownChatPanel 占满 */}
{showChat === 'full' && currentTask ? ( {showChat === 'full' && currentTask ? (
<div className="h-full w-full"> <div className="h-full w-full">
<ChatPanel taskId={currentTask.id} /> <ChatPanel taskId={currentTask.id} mode="full" onModeChange={setShowChat} />
</div> </div>
) : ( ) : (
<> <>
@@ -486,7 +485,7 @@ const MarkdownViewer: FC<MarkdownViewerProps> = ({ status }) => {
{/* 侧边问答模式:markdown + ChatPanel 各占一半 */} {/* 侧边问答模式:markdown + ChatPanel 各占一半 */}
{showChat === 'half' && currentTask && ( {showChat === 'half' && currentTask && (
<div className="ml-2 h-full w-1/2 shrink-0"> <div className="ml-2 h-full w-1/2 shrink-0">
<ChatPanel taskId={currentTask.id} /> <ChatPanel taskId={currentTask.id} mode="half" onModeChange={setShowChat} />
</div> </div>
)} )}
</> </>