import { useEffect, useState } from 'react'; import type { RawMessage } from '../../shared/chat-model'; import { extractText, isInternalMessage } from '../../shared/chat-model'; import { taskCenterList, type TaskCenterItem } from '../../constants/taskCenterList'; import { ChatComposer, ChatHistoryPanel, ChatMessageList, TaskBoard, type ChatHistoryBucket, type ChatMessageItem, type TaskItem, type TaskTabValue, } from '../../components/chat'; import { IPC_EVENTS } from '../../lib/constants'; import { invokeIpc } from '../../lib/host-api'; import { agentsStore, channelStore, chatStore, getCompletedTasks, getPendingTasks, taskStore, useAgentsStore, useChannelStore, useChatStore, useTaskStore, type StagedAttachment, } from '../../stores'; import { AddChannelDialog, TaskOperationDialog } from './components'; type SessionBucketKey = 'today' | 'yesterday' | 'withinWeek' | 'withinTwoWeeks' | 'withinMonth' | 'older'; const HISTORY_BUCKET_META: Array<{ key: SessionBucketKey; label: string }> = [ { key: 'today', label: '今天' }, { key: 'yesterday', label: '昨天' }, { key: 'withinWeek', label: '近7天' }, { key: 'withinTwoWeeks', label: '近14天' }, { key: 'withinMonth', label: '近30天' }, { key: 'older', label: '更早' }, ]; function getMessageTime(timestamp?: number): string { if (!timestamp) return '--'; const date = new Date(timestamp < 1e12 ? timestamp * 1000 : timestamp); return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', }); } function getHistoryBucket(activityMs: number, currentMs: number): SessionBucketKey { if (!activityMs || activityMs <= 0) return 'older'; const now = new Date(currentMs); const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime(); const startOfYesterday = startOfToday - 24 * 60 * 60 * 1000; if (activityMs >= startOfToday) return 'today'; if (activityMs >= startOfYesterday) return 'yesterday'; const daysAgo = (startOfToday - activityMs) / (24 * 60 * 60 * 1000); if (daysAgo <= 7) return 'withinWeek'; if (daysAgo <= 14) return 'withinTwoWeeks'; if (daysAgo <= 30) return 'withinMonth'; return 'older'; } function formatHistoryTime(activityMs: number, currentMs: number): string { if (!activityMs || activityMs <= 0) return '--'; const date = new Date(activityMs); const current = new Date(currentMs); const sameDay = date.toDateString() === current.toDateString(); if (sameDay) { return date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }); } const yesterday = new Date(current); yesterday.setDate(current.getDate() - 1); if (date.toDateString() === yesterday.toDateString()) { return `昨天 ${date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })}`; } return `${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; } function getTaskDateLabel(createdAt?: string): string { if (!createdAt) return '执行时段'; const current = new Date(createdAt); const today = new Date(); const y = today.getFullYear(); const m = today.getMonth(); const d = today.getDate(); const cy = current.getFullYear(); const cm = current.getMonth(); const cd = current.getDate(); if (cy === y && cm === m && cd === d) return '今天'; const yesterday = new Date(y, m, d - 1); if (cy === yesterday.getFullYear() && cm === yesterday.getMonth() && cd === yesterday.getDate()) { return '昨天'; } return `${String(cm + 1).padStart(2, '0')}/${String(cd).padStart(2, '0')}`; } function getTaskRoomTypeLabel(task: { roomType: string; roomList: Array<{ id?: string; pmsName?: string }> }): string { const matchedRoom = Array.isArray(task.roomList) ? task.roomList.find((item) => item.id === task.roomType) : null; return matchedRoom?.pmsName || task.roomType; } function mapMessages(messages: RawMessage[], streamingMessage: RawMessage | null): ChatMessageItem[] { const visibleMessages = messages .filter((message) => !isInternalMessage(message)) .filter((message) => { if (message.role === 'user' || message.role === 'assistant') return true; return Boolean(extractText(message).trim()); }) .map((message): ChatMessageItem => ({ id: message.id || `msg-${message.timestamp || Math.random()}`, role: message.role === 'user' ? 'user' : 'assistant', name: message.role === 'user' ? '你' : 'YINIAN', time: getMessageTime(message.timestamp), content: extractText(message), attachments: message._attachedFiles, isError: Boolean(message.isError), })); if (streamingMessage && extractText(streamingMessage).trim()) { visibleMessages.push({ id: streamingMessage.id || `stream-${Date.now()}`, role: 'assistant', name: 'YINIAN', time: getMessageTime(streamingMessage.timestamp), content: extractText(streamingMessage), attachments: streamingMessage._attachedFiles, isStreaming: true, }); } return visibleMessages; } export default function HomePage() { const agentsState = useAgentsStore(); const chat = useChatStore(); const taskState = useTaskStore(); const channelState = useChannelStore(); const [inputMessage, setInputMessage] = useState(''); const [attachments, setAttachments] = useState([]); const [activeTaskTab, setActiveTaskTab] = useState('pending'); const [taskCenterNotice, setTaskCenterNotice] = useState(null); const [taskDialogOpen, setTaskDialogOpen] = useState(false); const [addChannelDialogOpen, setAddChannelDialogOpen] = useState(false); useEffect(() => { void agentsStore.init(); void chatStore.init(); void taskStore.init(); void channelStore.init(); }, []); const currentMs = Date.now(); const historyBuckets: ChatHistoryBucket[] = HISTORY_BUCKET_META.map((bucket) => ({ ...bucket, sessions: [], })); const bucketMap = new Map(historyBuckets.map((bucket) => [bucket.key, bucket])); for (const session of [...chat.sessions].sort((left, right) => { const leftTime = chat.sessionLastActivity[left.key] || left.updatedAt || 0; const rightTime = chat.sessionLastActivity[right.key] || right.updatedAt || 0; return rightTime - leftTime; })) { const activityMs = chat.sessionLastActivity[session.key] || session.updatedAt || 0; const bucketKey = getHistoryBucket(activityMs, currentMs); const targetBucket = bucketMap.get(bucketKey); if (!targetBucket) continue; targetBucket.sessions.push({ conversationId: session.key, title: chat.sessionLabels[session.key] || session.displayName || session.key, updatedAt: formatHistoryTime(activityMs, currentMs), }); } const pendingTasks = getPendingTasks(taskState.tasks); const completedTasks = getCompletedTasks(taskState.tasks); const pendingTaskItems: TaskItem[] = pendingTasks.flatMap((task) => task.subTasks.map((subTask) => ({ id: subTask.id, removeTaskId: task.id, title: subTask.name, description: subTask.message || task.title, status: subTask.status, meta: task.title, }))); const completedTaskItems: TaskItem[] = completedTasks.map((task) => ({ id: task.id, title: task.title, description: `${task.operation === 'open' ? '开启' : '关闭'} ${getTaskRoomTypeLabel(task)}`, status: task.status, meta: `${task.dateRange[0]} ~ ${task.dateRange[1]}`, })); const currentTaskSource = activeTaskTab === 'pending' ? pendingTasks : completedTasks; const latestTask = currentTaskSource[0]; const visibleMessages = mapMessages(chat.messages, chat.streamingMessage); const selectedAgentId = agentsState.agents.some((agent) => agent.id === chat.currentAgentId) ? chat.currentAgentId : agentsState.defaultAgentId; const currentAgent = agentsState.agents.find((agent) => agent.id === selectedAgentId) || null; async function handleSendMessage(): Promise { const sent = await chatStore.sendMessage(inputMessage, attachments); if (sent) { setInputMessage(''); setAttachments([]); } } async function handleAttach(files: File[]): Promise { const stagedFiles = await chatStore.stageAttachmentFiles(files); setAttachments((currentAttachments) => [...currentAttachments, ...stagedFiles]); } async function handleTaskCenterItem(item: TaskCenterItem): Promise { setTaskCenterNotice(null); if (item.type === 'channel') { if (!channelState.selectedChannels.length) { setTaskCenterNotice('请先在当前项目里配置“已选渠道”,再执行一键打开。'); return; } try { await invokeIpc(IPC_EVENTS.OPEN_CHANNEL, channelState.selectedChannels); setTaskCenterNotice('已触发“打开渠道”操作。'); } catch (error) { setTaskCenterNotice(error instanceof Error ? error.message : String(error)); } return; } setTaskDialogOpen(true); } async function handleSaveChannels(items: typeof channelState.selectedChannels): Promise { await channelStore.saveSelectedChannels(items); setTaskCenterNotice('已更新“打开渠道”配置。'); setAddChannelDialogOpen(false); } async function handleCreateTask(options: Parameters[0]): Promise { const { task, result } = await taskStore.createAndExecuteTask(options); setTaskDialogOpen(false); if (result.success) { setTaskCenterNotice(`已创建任务“${task.title}”。`); return; } setTaskCenterNotice(result.error || `任务“${task.title}”创建后执行失败。`); } async function handleRetryTask(taskId: string): Promise { const result = await taskStore.retryFailedSubTasks(taskId); if (result.success) { setTaskCenterNotice('已重新触发失败子任务。'); return; } setTaskCenterNotice(result.error || '重试失败,请稍后再试。'); } return (
{ void chatStore.newSession(selectedAgentId || undefined); }} onSelectConversation={(conversationId) => { chatStore.switchSession(conversationId); }} onRenameConversation={(conversationId) => { const currentLabel = chat.sessionLabels[conversationId] || chat.sessions.find((session) => session.key === conversationId)?.displayName || ''; const nextLabel = window.prompt('重命名对话', currentLabel); if (nextLabel) { chatStore.renameSession(conversationId, nextLabel); } }} onDeleteConversation={(conversationId) => { const confirmed = window.confirm('确定删除该会话吗?删除后将无法恢复。'); if (confirmed) { void chatStore.deleteSession(conversationId); } }} />

智能对话

网关状态:{chat.gatewayStatus === 'connected' ? '已连接' : chat.gatewayStatus === 'reconnecting' ? '重连中' : '未连接'} {currentAgent ? ` · 当前代理:${currentAgent.name}` : ''}
chatStore.clearError()} onRemoveAttachment={(index) => { setAttachments((currentAttachments) => currentAttachments.filter((_, currentIndex) => currentIndex !== index)); }} onSend={() => { void handleSendMessage(); }} onStop={() => { void chatStore.abortRun(); }} />

任务中心

沿用当前视觉与入口结构
{taskCenterNotice ? (
{taskCenterNotice}
) : null}
{taskCenterList.map((item) => ( ))}
{ taskStore.removeTask(taskId); }} onRetryTask={(taskId) => { void handleRetryTask(taskId); }} onTabChange={setActiveTaskTab} />
setTaskDialogOpen(false)} onConfirm={handleCreateTask} /> setAddChannelDialogOpen(false)} onConfirm={handleSaveChannels} />
); }