diff --git a/web/src/App.tsx b/web/src/App.tsx index 75ed1a64..35d564aa 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1,8 +1,7 @@ import { useState, useMemo, useEffect, useRef, useCallback } from 'react'; import { AppShell } from './components/layout/AppShell'; import { DocumentPane } from './components/doc/DocumentPane'; -import { MessageStream } from './components/chat/MessageStream'; -import { InputArea } from './components/chat/InputArea'; +import { ChatColumn } from './components/chat/ChatColumn'; import { fetchFreeformDoc } from './api/freeformDoc'; import type { DiffResult, OverlayMode } from './features/documentRevision/types'; import { fetchProjectLineage, getProject } from './api/projectWorkflow'; @@ -35,7 +34,6 @@ import { import { DocumentLineage } from './types/projectWorkflow'; import { StaleDocumentNotice } from './components/workflow/StaleDocumentNotice'; import { StageAdvanceBanner } from './components/workflow/StageAdvanceBanner'; -import { ProposalConfirmCard } from './components/doc/ProposalConfirmCard'; import { UserProjectRightsModal } from './components/user/UserProjectRightsModal'; import { UserSettingsModal } from './components/user/UserSettingsModal'; import { OnDemand } from './components/lazy/OnDemand'; @@ -114,13 +112,8 @@ import { RequestEpoch, shareInflight } from './data/initFanout'; import { resolveSessionProjectId, } from './lib/sessionAdapter'; -import { CategoryFilterBar } from './components/history/CategoryFilterBar'; -import { HistoryCalendar } from './components/history/HistoryCalendar'; -import { ChatSearchModal } from './components/history/ChatSearchModal'; -import { DigestTimelineModal } from './components/history/DigestTimelineModal'; -import { navigateToAnchor, parseAnchorHash, resolveTurnAnchorId } from './engine/anchorNavigator'; +import { navigateToAnchor, parseAnchorHash } from './engine/anchorNavigator'; import { useUnreadMarks } from './hooks/useUnreadMarks'; -import { CategoryDock } from './components/chat/CategoryDock'; import { fetchChatCategories, fetchChatDates } from './api/chatWindow'; import { ChatCategoryStat, ChatDateStat, QuotedMessage } from './types/workbench'; import { saveFile } from './data/fileStore'; @@ -134,9 +127,6 @@ export { const loadAdminConsoleView = () => import('./components/admin/console/AdminConsoleView').then((m) => ({ default: m.AdminConsoleView })); -const loadChatExportModal = () => - import('./components/history/ChatExportModal').then((m) => ({ default: m.ChatExportModal })); - export default function App() { // Wave 2:启动时幂等剔除 localStorage 中的 demo seed 残留(须先于 load*) purgeDemoSeedsFromLocalStorage(); @@ -1553,156 +1543,42 @@ export default function App() { }} workflowBanner={workflowBanner} chatContent={ -
- {/* P1, P2, P3: 顶部分类统计过滤栏、历史日历、三模搜索、智能摘要、导出与未读角标 */} - { - if (lastMessageId && activeSessionId) { - const anchorId = resolveTurnAnchorId(lastMessageId, chatWindow.allMessages); - navigateToAnchor( - { sessionId: activeSessionId, messageId: anchorId, highlightMs: 2000 }, - { sessionId: activeSessionId, loadDayForAnchor: chatWindow.loadDayForAnchor } - ); - } - }} - onToggleCalendar={() => setIsCalendarOpen((prev) => !prev)} - isCalendarOpen={isCalendarOpen} - onOpenSearch={() => setIsSearchOpen(true)} - onOpenDigest={() => setIsDigestOpen(true)} - onOpenExport={() => setIsExportOpen(true)} - unreadTotal={unreadTotal} - /> - {isCalendarOpen && ( - { - chatWindow.jumpToDate(date); - setIsCalendarOpen(false); - }} - onClose={() => setIsCalendarOpen(false)} - /> - )} - {isSearchOpen && ( - { - navigateToAnchor( - { sessionId: activeSessionId, messageId: hit.message_id }, - { sessionId: activeSessionId, loadDayForAnchor: chatWindow.loadDayForAnchor } - ); - setIsSearchOpen(false); - }} - onClose={() => setIsSearchOpen(false)} - /> - )} - {isDigestOpen && ( - { - navigateToAnchor( - { sessionId: activeSessionId, messageId }, - { sessionId: activeSessionId, loadDayForAnchor: chatWindow.loadDayForAnchor } - ); - setIsDigestOpen(false); - }} - onClose={() => setIsDigestOpen(false)} - /> - )} - {isExportOpen && ( - setIsExportOpen(false), - sessionId: activeSessionId, - dayStack: chatWindow.displayDayStack, - }} - /> - )} -
- { - navigateToAnchor( - { sessionId: activeSessionId, messageId: targetAnchor }, - { sessionId: activeSessionId, loadDayForAnchor: chatWindow.loadDayForAnchor } - ); - }} - selectedCategory={chatWindow.selectedCategory} - unreadMessageIds={unreadMessageIds} - onSaveToPrivate={handleSaveMessageToPrivate} - onExportDocx={handleExportReplyDocx} - onOpenDoc={handleSelectDoc} - /> - { - const targetId = await consumeNextUnread(); - if (targetId && activeSessionId) { - navigateToAnchor( - { sessionId: activeSessionId, messageId: targetId, highlightMs: 2000 }, - { sessionId: activeSessionId, loadDayForAnchor: chatWindow.loadDayForAnchor } - ); - } - }} - onNavigateToCategory={(_cat, lastMessageId) => { - if (lastMessageId && activeSessionId) { - const anchorId = resolveTurnAnchorId(lastMessageId, chatWindow.allMessages); - navigateToAnchor( - { sessionId: activeSessionId, messageId: anchorId, highlightMs: 2000 }, - { sessionId: activeSessionId, loadDayForAnchor: chatWindow.loadDayForAnchor } - ); - } - }} - onSelectCategory={chatWindow.setSelectedCategory} - /> -
- {pendingProposal && ( -
- {proposalError && ( -
- {proposalError} -
- )} - -
- )} - -
+ } docContent={ { + const app = readFileSync(new URL('../../App.tsx', import.meta.url), 'utf8'); + const column = readFileSync(new URL('./ChatColumn.tsx', import.meta.url), 'utf8'); + assert.equal((app.match(/ + import('../history/ChatExportModal').then((m) => ({ default: m.ChatExportModal })); + +export interface ChatColumnProps { + activeSessionId: string; + categoryStats: ChatCategoryStat[]; + chatWindow: UseChatWindowReturn; + isCalendarOpen: boolean; + setIsCalendarOpen: (next: boolean | ((prev: boolean) => boolean)) => void; + isSearchOpen: boolean; + setIsSearchOpen: (open: boolean) => void; + isDigestOpen: boolean; + setIsDigestOpen: (open: boolean) => void; + isExportOpen: boolean; + setIsExportOpen: (open: boolean) => void; + unreadTotal: number; + dateStats: ChatDateStat[]; + unreadMarks: UnreadMark[]; + unreadMessageIds: number[]; + isAllReadCompleted: boolean; + consumeNextUnread: () => Promise; + handleSelectDoc: (id: string) => void; + handleCardAction: (cardId: string, actionId: string, payload?: unknown) => void; + handleErrorAction: (messageId: string, action: 'retry' | 'continue' | 'restart') => void; + handleQuoteMessage: (message: ChatMessage) => void; + handleSaveMessageToPrivate: (message: ChatMessage) => void; + handleExportReplyDocx: (message: ChatMessage) => void | Promise; + pendingProposal: { changes: string; impact: string; risk: string } | null; + proposalError: string | null; + handleAcceptProposal: () => void; + handleRejectProposal: () => void; + handleSendMessage: React.ComponentProps['onSendMessage']; + isThinking: boolean; + isStreamingActive: boolean; + handleStopStreaming: () => void; + activeQuotes: QuotedMessage[]; + handleRemoveQuote: (index: number) => void; + isCurrentProjectExited: boolean; +} + +export function ChatColumn({ + activeSessionId, + categoryStats, + chatWindow, + isCalendarOpen, + setIsCalendarOpen, + isSearchOpen, + setIsSearchOpen, + isDigestOpen, + setIsDigestOpen, + isExportOpen, + setIsExportOpen, + unreadTotal, + dateStats, + unreadMarks, + unreadMessageIds, + isAllReadCompleted, + consumeNextUnread, + handleSelectDoc, + handleCardAction, + handleErrorAction, + handleQuoteMessage, + handleSaveMessageToPrivate, + handleExportReplyDocx, + pendingProposal, + proposalError, + handleAcceptProposal, + handleRejectProposal, + handleSendMessage, + isThinking, + isStreamingActive, + handleStopStreaming, + activeQuotes, + handleRemoveQuote, + isCurrentProjectExited, +}: ChatColumnProps) { + return ( +
+ { + if (lastMessageId && activeSessionId) { + const anchorId = resolveTurnAnchorId(lastMessageId, chatWindow.allMessages); + navigateToAnchor( + { sessionId: activeSessionId, messageId: anchorId, highlightMs: 2000 }, + { sessionId: activeSessionId, loadDayForAnchor: chatWindow.loadDayForAnchor } + ); + } + }} + onToggleCalendar={() => setIsCalendarOpen((prev) => !prev)} + isCalendarOpen={isCalendarOpen} + onOpenSearch={() => setIsSearchOpen(true)} + onOpenDigest={() => setIsDigestOpen(true)} + onOpenExport={() => setIsExportOpen(true)} + unreadTotal={unreadTotal} + /> + {isCalendarOpen && ( + { + chatWindow.jumpToDate(date); + setIsCalendarOpen(false); + }} + onClose={() => setIsCalendarOpen(false)} + /> + )} + {isSearchOpen && ( + { + navigateToAnchor( + { sessionId: activeSessionId, messageId: hit.message_id }, + { sessionId: activeSessionId, loadDayForAnchor: chatWindow.loadDayForAnchor } + ); + setIsSearchOpen(false); + }} + onClose={() => setIsSearchOpen(false)} + /> + )} + {isDigestOpen && ( + { + navigateToAnchor( + { sessionId: activeSessionId, messageId }, + { sessionId: activeSessionId, loadDayForAnchor: chatWindow.loadDayForAnchor } + ); + setIsDigestOpen(false); + }} + onClose={() => setIsDigestOpen(false)} + /> + )} + {isExportOpen && ( + setIsExportOpen(false), + sessionId: activeSessionId, + dayStack: chatWindow.displayDayStack, + }} + /> + )} +
+ { + navigateToAnchor( + { sessionId: activeSessionId, messageId: targetAnchor }, + { sessionId: activeSessionId, loadDayForAnchor: chatWindow.loadDayForAnchor } + ); + }} + selectedCategory={chatWindow.selectedCategory} + unreadMessageIds={unreadMessageIds} + onSaveToPrivate={handleSaveMessageToPrivate} + onExportDocx={handleExportReplyDocx} + onOpenDoc={handleSelectDoc} + /> + { + const targetId = await consumeNextUnread(); + if (targetId && activeSessionId) { + navigateToAnchor( + { sessionId: activeSessionId, messageId: targetId, highlightMs: 2000 }, + { sessionId: activeSessionId, loadDayForAnchor: chatWindow.loadDayForAnchor } + ); + } + }} + onNavigateToCategory={(_cat, lastMessageId) => { + if (lastMessageId && activeSessionId) { + const anchorId = resolveTurnAnchorId(lastMessageId, chatWindow.allMessages); + navigateToAnchor( + { sessionId: activeSessionId, messageId: anchorId, highlightMs: 2000 }, + { sessionId: activeSessionId, loadDayForAnchor: chatWindow.loadDayForAnchor } + ); + } + }} + onSelectCategory={chatWindow.setSelectedCategory} + /> +
+ {pendingProposal && ( +
+ {proposalError && ( +
+ {proposalError} +
+ )} + +
+ )} + +
+ ); +}