feat: 添加MinIO文件系统支持并优化外部连接器功能
- 添加MinIO用户文件系统配置选项(BEAVER_MINIO_ROOT_USER等) - 更新外部连接器配置结构,包括BASE_URL和认证令牌设置 - 改进connector provider支持更多类型(official, feishu_bot等) - 实现Mistral模型推理模式支持reasoning_effort参数 - 增强外部连接器策略配置和运行时配置管理 - 添加connector bridge事件验证和安全保护机制 - 优化任务路由逻辑,区分simple_chat和new_task场景 - 更新初始技能工具提示配置,分离authoring admin功能
This commit is contained in:
@ -2,15 +2,24 @@
|
||||
|
||||
import Link from 'next/link';
|
||||
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Brain, Plus, Send, Trash2, X } from 'lucide-react';
|
||||
import { Brain, Menu, Plus, Send, Trash2, X } from 'lucide-react';
|
||||
|
||||
import { ChatWorkbench } from '@/components/chat-workbench/ChatWorkbench';
|
||||
import { CurrentSessionProgressSidebar } from '@/components/chat-workbench/CurrentSessionProgressSidebar';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import {
|
||||
archiveSession,
|
||||
createSession,
|
||||
getActiveTask,
|
||||
getBackendTask,
|
||||
getSession,
|
||||
getSessionProcess,
|
||||
listSessions,
|
||||
@ -27,9 +36,9 @@ import {
|
||||
} from '@/lib/chat-messages';
|
||||
import { pickAppText } from '@/lib/i18n/core';
|
||||
import { useAppI18n } from '@/lib/i18n/provider';
|
||||
import { buildSessionProgressView } from '@/lib/session-progress';
|
||||
import { useChatStore } from '@/lib/store';
|
||||
import type { ActiveTask, ChatMessage, FileAttachment, SessionUpdatedEvent, WsEvent } from '@/types';
|
||||
import { buildTaskTimelineView } from '@/lib/task-timeline-view';
|
||||
import type { ActiveTask, BackendTask, ChatMessage, FileAttachment, SessionUpdatedEvent, WsEvent } from '@/types';
|
||||
|
||||
function isSessionUpdatedEvent(data: WsEvent | Record<string, unknown>): data is SessionUpdatedEvent {
|
||||
return data.type === 'session_updated' && typeof data.session_id === 'string';
|
||||
@ -86,13 +95,17 @@ export default function ChatPage() {
|
||||
const [thinkingModeEnabled, setThinkingModeEnabled] = useState(loadThinkingModePreference);
|
||||
const [pendingFiles, setPendingFiles] = useState<Array<{ file: File; id?: string; progress: number; error?: string }>>([]);
|
||||
const [activeTask, setActiveTask] = useState<ActiveTask | null>(null);
|
||||
const [activeTaskDetail, setActiveTaskDetail] = useState<BackendTask | null>(null);
|
||||
const [revisionTargetRunId, setRevisionTargetRunId] = useState<string | null>(null);
|
||||
const [documentHidden, setDocumentHidden] = useState(isDocumentHidden);
|
||||
const [sessionDrawerOpen, setSessionDrawerOpen] = useState(false);
|
||||
const [archiveTargetSessionId, setArchiveTargetSessionId] = useState<string | null>(null);
|
||||
const messagesEndRef = useRef<HTMLDivElement>(null);
|
||||
const messageViewportRef = useRef<HTMLDivElement>(null);
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const loadSessionReqSeq = useRef(0);
|
||||
const loadActiveTaskReqSeq = useRef(0);
|
||||
const loadedSessionIdRef = useRef<string | null>(null);
|
||||
const refreshSessionOnReconnectRef = useRef(false);
|
||||
const hasConnectedRef = useRef(false);
|
||||
@ -120,16 +133,15 @@ export default function ChatPage() {
|
||||
);
|
||||
|
||||
const selectedSessionRunId = selectedRunId && sessionRunIds.has(selectedRunId) ? selectedRunId : null;
|
||||
const sessionProgressView = useMemo(
|
||||
const activeTaskTimelineView = useMemo(
|
||||
() =>
|
||||
buildSessionProgressView({
|
||||
sessionId,
|
||||
processRuns,
|
||||
processEvents,
|
||||
processArtifacts,
|
||||
locale,
|
||||
buildTaskTimelineView({
|
||||
task: activeTaskDetail,
|
||||
liveRuns: processRuns,
|
||||
liveEvents: processEvents,
|
||||
liveArtifacts: processArtifacts,
|
||||
}),
|
||||
[locale, processArtifacts, processEvents, processRuns, sessionId]
|
||||
[activeTaskDetail, processArtifacts, processEvents, processRuns]
|
||||
);
|
||||
|
||||
const loadSessions = useCallback(async () => {
|
||||
@ -142,12 +154,34 @@ export default function ChatPage() {
|
||||
}, []);
|
||||
|
||||
const loadActiveTask = useCallback(async (key: string) => {
|
||||
const reqSeq = ++loadActiveTaskReqSeq.current;
|
||||
try {
|
||||
if (useChatStore.getState().sessionId !== key) return;
|
||||
setActiveTask(await getActiveTask(key));
|
||||
const nextActiveTask = await getActiveTask(key);
|
||||
if (reqSeq !== loadActiveTaskReqSeq.current || useChatStore.getState().sessionId !== key) return;
|
||||
setActiveTask(nextActiveTask);
|
||||
if (!nextActiveTask) {
|
||||
setActiveTaskDetail(null);
|
||||
return;
|
||||
}
|
||||
setActiveTaskDetail((current) => (current?.task_id === nextActiveTask.task_id ? current : null));
|
||||
try {
|
||||
const detail = await getBackendTask(nextActiveTask.task_id);
|
||||
if (reqSeq !== loadActiveTaskReqSeq.current || useChatStore.getState().sessionId !== key) return;
|
||||
if (detail.is_open === false) {
|
||||
setActiveTask(null);
|
||||
setActiveTaskDetail(null);
|
||||
return;
|
||||
}
|
||||
setActiveTaskDetail(detail);
|
||||
} catch {
|
||||
if (reqSeq === loadActiveTaskReqSeq.current && useChatStore.getState().sessionId === key) {
|
||||
setActiveTaskDetail(null);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
if (useChatStore.getState().sessionId === key) {
|
||||
if (reqSeq === loadActiveTaskReqSeq.current && useChatStore.getState().sessionId === key) {
|
||||
setActiveTask(null);
|
||||
setActiveTaskDetail(null);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
@ -194,6 +228,7 @@ export default function ChatPage() {
|
||||
setIsThinking(false);
|
||||
}
|
||||
setActiveTask(null);
|
||||
setActiveTaskDetail(null);
|
||||
setRevisionTargetRunId(null);
|
||||
setInput(useChatStore.getState().getInputDraft(sessionId));
|
||||
void loadSessionMessages(sessionId);
|
||||
@ -299,6 +334,7 @@ export default function ChatPage() {
|
||||
|
||||
useEffect(() => {
|
||||
shouldSnapToLatestRef.current = true;
|
||||
setSessionDrawerOpen(false);
|
||||
}, [sessionId]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
@ -474,6 +510,7 @@ export default function ChatPage() {
|
||||
setSessionId(id);
|
||||
setSelectedRunId(null);
|
||||
setActiveTask(null);
|
||||
setActiveTaskDetail(null);
|
||||
setRevisionTargetRunId(null);
|
||||
clearInputDraft(id);
|
||||
setInput('');
|
||||
@ -487,14 +524,15 @@ export default function ChatPage() {
|
||||
void loadSessions();
|
||||
};
|
||||
|
||||
const handleArchiveSession = async (key: string, e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
const handleArchiveSession = async (key: string) => {
|
||||
try {
|
||||
await archiveSession(key);
|
||||
setArchiveTargetSessionId(null);
|
||||
useChatStore.getState().setSessions(useChatStore.getState().sessions.filter((session) => session.key !== key));
|
||||
if (key === sessionId) {
|
||||
setSessionId('web:default');
|
||||
setActiveTask(null);
|
||||
setActiveTaskDetail(null);
|
||||
setRevisionTargetRunId(null);
|
||||
clearInputDraft(key);
|
||||
setInput(useChatStore.getState().getInputDraft('web:default'));
|
||||
@ -514,9 +552,11 @@ export default function ChatPage() {
|
||||
const handleSelectSession = (key: string) => {
|
||||
setSelectedRunId(null);
|
||||
setActiveTask(null);
|
||||
setActiveTaskDetail(null);
|
||||
setRevisionTargetRunId(null);
|
||||
setInput(useChatStore.getState().getInputDraft(key));
|
||||
setSessionId(key);
|
||||
setSessionDrawerOpen(false);
|
||||
};
|
||||
|
||||
const removePendingFile = useCallback((file: File) => {
|
||||
@ -551,53 +591,101 @@ export default function ChatPage() {
|
||||
return key;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-[calc(100vh-4rem)] bg-background">
|
||||
<aside className="flex w-[280px] shrink-0 flex-col border-r border-[#E6E1DE] bg-[#F7F6F5]">
|
||||
<div className="px-5 pb-5 pt-6">
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleNewSession}
|
||||
className="flex h-11 w-full items-center justify-center gap-2 rounded-full bg-primary px-4 text-sm font-medium text-primary-foreground transition-colors hover:bg-[#342E2B]"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
{pickAppText(locale, '新对话', 'New chat')}
|
||||
</button>
|
||||
</div>
|
||||
<ScrollArea className="flex-1">
|
||||
<div className="space-y-3 px-3 pb-6">
|
||||
<div className="px-3 pb-2 text-[14px] text-muted-foreground">{pickAppText(locale, '最近对话', 'Recent chats')}</div>
|
||||
{sessions.length === 0 && (
|
||||
<p className="px-3 py-4 text-sm text-muted-foreground">{pickAppText(locale, '暂无对话记录', 'No chat history yet')}</p>
|
||||
)}
|
||||
{sessions.map((session) => (
|
||||
const archiveTargetSessionName = archiveTargetSessionId ? formatSessionName(archiveTargetSessionId) : '';
|
||||
|
||||
const renderSessionSidebar = (variant: 'desktop' | 'drawer') => (
|
||||
<>
|
||||
<div className="px-5 pb-5 pt-6">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setSessionDrawerOpen(false);
|
||||
void handleNewSession();
|
||||
}}
|
||||
className="flex h-11 w-full items-center justify-center gap-2 rounded-full bg-primary px-4 text-sm font-medium text-primary-foreground transition-colors hover:bg-[#342E2B]"
|
||||
>
|
||||
<Plus className="h-4 w-4" />
|
||||
{pickAppText(locale, '新对话', 'New chat')}
|
||||
</button>
|
||||
</div>
|
||||
<ScrollArea className="flex-1">
|
||||
<div className="space-y-3 px-3 pb-6">
|
||||
<div className="px-3 pb-2 text-[14px] text-muted-foreground">{pickAppText(locale, '最近对话', 'Recent chats')}</div>
|
||||
{sessions.length === 0 && (
|
||||
<p className="px-3 py-4 text-sm text-muted-foreground">{pickAppText(locale, '暂无对话记录', 'No chat history yet')}</p>
|
||||
)}
|
||||
{sessions.map((session) => {
|
||||
const sessionName = formatSessionName(session.key);
|
||||
const isCurrent = session.key === sessionId;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={session.key}
|
||||
onClick={() => handleSelectSession(session.key)}
|
||||
className={`group flex cursor-pointer items-center justify-between rounded-xl px-4 py-3 text-[15px] transition-colors ${
|
||||
session.key === sessionId
|
||||
key={`${variant}:${session.key}`}
|
||||
className={`group flex items-center gap-1 rounded-xl px-2 py-1 text-[15px] transition-colors ${
|
||||
isCurrent
|
||||
? 'bg-[#EFEEED] text-foreground'
|
||||
: 'text-foreground hover:bg-[#EFEEED]/70'
|
||||
: 'text-foreground hover:bg-[#EFEEED]/70 focus-within:bg-[#EFEEED]/70'
|
||||
}`}
|
||||
>
|
||||
<div className="truncate">
|
||||
<span className="truncate">{formatSessionName(session.key)}</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={(event) => handleArchiveSession(session.key, event)}
|
||||
className="opacity-0 group-hover:opacity-100 p-0.5 hover:text-destructive transition-opacity"
|
||||
title={pickAppText(locale, '归档会话', 'Archive session')}
|
||||
aria-label={pickAppText(locale, '归档会话', 'Archive session')}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleSelectSession(session.key)}
|
||||
className="flex h-11 min-w-0 flex-1 items-center rounded-lg px-2 text-left outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
||||
aria-current={isCurrent ? 'true' : undefined}
|
||||
>
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
<span className="truncate">{sessionName}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setArchiveTargetSessionId(session.key)}
|
||||
className="flex h-11 w-11 shrink-0 items-center justify-center rounded-lg text-muted-foreground opacity-100 transition-colors hover:bg-white hover:text-destructive focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring md:opacity-0 md:group-hover:opacity-100 md:group-focus-within:opacity-100"
|
||||
title={pickAppText(locale, '归档会话', 'Archive session')}
|
||||
aria-label={pickAppText(locale, `归档会话 ${sessionName}`, `Archive session ${sessionName}`)}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="relative flex h-[calc(100dvh-4rem)] overflow-hidden bg-background">
|
||||
<aside className="hidden w-[280px] shrink-0 flex-col border-r border-[#E6E1DE] bg-[#F7F6F5] md:flex">
|
||||
{renderSessionSidebar('desktop')}
|
||||
</aside>
|
||||
|
||||
{sessionDrawerOpen && (
|
||||
<div className="fixed inset-x-0 bottom-0 top-16 z-40 md:hidden">
|
||||
<button
|
||||
type="button"
|
||||
className="absolute inset-0 bg-black/30"
|
||||
aria-label={pickAppText(locale, '关闭最近对话', 'Close recent chats')}
|
||||
onClick={() => setSessionDrawerOpen(false)}
|
||||
/>
|
||||
<aside className="absolute bottom-0 left-0 top-0 flex w-[min(86vw,320px)] flex-col border-r border-[#E6E1DE] bg-[#F7F6F5] shadow-2xl">
|
||||
{renderSessionSidebar('drawer')}
|
||||
</aside>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex-1 flex flex-col min-w-0">
|
||||
<div className="flex min-h-14 items-center gap-2 border-b border-[#E6E1DE] bg-[#F7F6F5] px-3 md:hidden">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSessionDrawerOpen(true)}
|
||||
className="flex h-11 w-11 items-center justify-center rounded-full border border-[#E6E1DE] bg-white text-[#1D1715]"
|
||||
aria-label={pickAppText(locale, '打开最近对话', 'Open recent chats')}
|
||||
>
|
||||
<Menu className="h-5 w-5" />
|
||||
</button>
|
||||
<div className="min-w-0 text-sm font-medium text-foreground">
|
||||
<span className="block truncate">{formatSessionName(sessionId)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 min-h-0">
|
||||
<ChatWorkbench
|
||||
messages={messages}
|
||||
@ -614,14 +702,14 @@ export default function ChatPage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="bg-background px-8 pb-8 pt-4">
|
||||
<div className="bg-background px-3 pb-4 pt-3 sm:px-5 sm:pb-6 md:px-8 md:pb-8 md:pt-4">
|
||||
<div className="mx-auto max-w-5xl">
|
||||
{(activeTask || revisionTargetRunId) && (
|
||||
<div className="mb-2 flex">
|
||||
{activeTask ? (
|
||||
<Link
|
||||
href={`/tasks/${encodeURIComponent(activeTask.task_id)}`}
|
||||
className="inline-flex max-w-full items-center gap-2 rounded-full border border-[#D8D2CE] bg-[#F7F6F5] px-3 py-1.5 text-xs text-foreground transition-colors hover:bg-[#EFEEED]"
|
||||
className="inline-flex h-11 max-w-full items-center gap-2 rounded-full border border-[#D8D2CE] bg-[#F7F6F5] px-3 text-xs text-foreground transition-colors hover:bg-[#EFEEED]"
|
||||
title={activeTask.description}
|
||||
>
|
||||
<span className="shrink-0 text-muted-foreground">
|
||||
@ -638,7 +726,7 @@ export default function ChatPage() {
|
||||
{pendingFiles.length > 0 && (
|
||||
<div className="mb-2 space-y-1">
|
||||
{pendingFiles.map((item, index) => (
|
||||
<div key={`${item.file.name}:${index}`} className="flex items-center gap-2 px-3 py-1.5 bg-muted rounded-md text-sm">
|
||||
<div key={`${item.file.name}:${index}`} className="flex min-h-11 items-center gap-2 rounded-md bg-muted px-3 py-1.5 text-sm">
|
||||
<span className="truncate flex-1">
|
||||
{item.file.name}{' '}
|
||||
<span className="text-muted-foreground">({(item.file.size / 1024).toFixed(0)}KB)</span>
|
||||
@ -652,8 +740,13 @@ export default function ChatPage() {
|
||||
) : (
|
||||
<span className="text-[#657162] text-xs">{pickAppText(locale, '就绪', 'Ready')}</span>
|
||||
)}
|
||||
<button onClick={() => removePendingFile(item.file)} className="text-muted-foreground hover:text-foreground">
|
||||
<X className="w-3.5 h-3.5" />
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => removePendingFile(item.file)}
|
||||
className="flex h-11 w-11 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-background hover:text-foreground"
|
||||
aria-label={pickAppText(locale, `移除附件 ${item.file.name}`, `Remove attachment ${item.file.name}`)}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
@ -662,8 +755,14 @@ export default function ChatPage() {
|
||||
|
||||
<div className="relative rounded-[28px] border border-[#E6E1DE] bg-white p-4 shadow-[0_8px_24px_rgba(0,0,0,0.08)]">
|
||||
<input ref={fileInputRef} type="file" multiple className="hidden" onChange={handleFileSelect} />
|
||||
<label htmlFor="chat-composer" className="sr-only">
|
||||
{revisionTargetRunId
|
||||
? pickAppText(locale, '修改要求', 'Revision request')
|
||||
: pickAppText(locale, '消息内容', 'Message content')}
|
||||
</label>
|
||||
|
||||
<textarea
|
||||
id="chat-composer"
|
||||
ref={textareaRef}
|
||||
value={input}
|
||||
onChange={(e) => {
|
||||
@ -677,7 +776,7 @@ export default function ChatPage() {
|
||||
: pickAppText(locale, '今天想聊什么?', 'What would you like to talk about today?')
|
||||
}
|
||||
rows={1}
|
||||
className="block w-full resize-none border-0 bg-transparent px-2 pb-8 pt-1 text-[17px] leading-7 placeholder:text-muted-foreground focus:outline-none disabled:cursor-not-allowed disabled:opacity-50"
|
||||
className="block w-full resize-none border-0 bg-transparent px-1 pb-8 pt-1 text-[16px] leading-7 placeholder:text-muted-foreground focus:outline-none disabled:cursor-not-allowed disabled:opacity-50 sm:px-2 sm:text-[17px]"
|
||||
style={{ minHeight: '72px', maxHeight: '200px' }}
|
||||
onInput={(e) => {
|
||||
const target = e.target as HTMLTextAreaElement;
|
||||
@ -687,18 +786,20 @@ export default function ChatPage() {
|
||||
/>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-5 text-[15px] text-muted-foreground">
|
||||
<div className="flex items-center gap-2 text-[15px] text-muted-foreground sm:gap-5">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
className="inline-flex items-center gap-2 text-foreground transition-colors hover:text-muted-foreground"
|
||||
className="inline-flex h-11 w-11 items-center justify-center rounded-full text-foreground transition-colors hover:bg-[#F7F5F4] hover:text-muted-foreground"
|
||||
title={pickAppText(locale, '添加附件', 'Add attachment')}
|
||||
aria-label={pickAppText(locale, '添加附件', 'Add attachment')}
|
||||
>
|
||||
<Plus className="h-5 w-5" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleThinkingMode}
|
||||
className={`inline-flex h-8 items-center gap-2 rounded-full border px-3 text-sm transition-colors ${
|
||||
className={`inline-flex h-11 items-center gap-2 rounded-full border px-3 text-sm transition-colors ${
|
||||
thinkingModeEnabled
|
||||
? 'border-primary/40 bg-[#F1EFEE] text-foreground'
|
||||
: 'border-[#E6E1DE] bg-white text-muted-foreground hover:text-foreground'
|
||||
@ -729,7 +830,43 @@ export default function ChatPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{sessionProgressView && <CurrentSessionProgressSidebar view={sessionProgressView} />}
|
||||
<Dialog open={Boolean(archiveTargetSessionId)} onOpenChange={(open) => !open && setArchiveTargetSessionId(null)}>
|
||||
<DialogContent className="max-w-[calc(100vw-2rem)] sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{pickAppText(locale, '归档此会话?', 'Archive this chat?')}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{pickAppText(
|
||||
locale,
|
||||
archiveTargetSessionName ? `会话「${archiveTargetSessionName}」会从最近对话中移除。` : '此会话会从最近对话中移除。',
|
||||
archiveTargetSessionName ? `Chat "${archiveTargetSessionName}" will be removed from recent chats.` : 'This chat will be removed from recent chats.'
|
||||
)}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter className="gap-2 sm:gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setArchiveTargetSessionId(null)}
|
||||
className="h-11 rounded-md border border-border px-4 text-sm text-muted-foreground hover:bg-accent"
|
||||
>
|
||||
{pickAppText(locale, '取消', 'Cancel')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => archiveTargetSessionId && void handleArchiveSession(archiveTargetSessionId)}
|
||||
className="h-11 rounded-md bg-destructive px-4 text-sm font-medium text-destructive-foreground hover:bg-destructive/90"
|
||||
>
|
||||
{pickAppText(locale, '确认归档', 'Confirm archive')}
|
||||
</button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{activeTaskDetail ? (
|
||||
<CurrentSessionProgressSidebar
|
||||
cards={activeTaskTimelineView?.cards ?? []}
|
||||
isLive={Boolean(activeTaskDetail.is_open && wsStatus === 'connected')}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user