From bfa7b540cb405f7b4ff7653b21fe5548dc952433 Mon Sep 17 00:00:00 2001 From: JOJO <1498581755@qq.com> Date: Tue, 25 Nov 2025 21:13:09 +0800 Subject: [PATCH] fix: restore chat layout and file tree --- static/src/App.vue | 1093 ++++++ static/src/app.ts | 2139 ++++++++++ static/src/stores/file.ts | 203 + .../styles/components/chat/_chat-area.scss | 369 ++ static/src/styles/layout/_app-shell.scss | 15 + static/style.css | 3495 +---------------- 6 files changed, 3823 insertions(+), 3491 deletions(-) create mode 100644 static/src/App.vue create mode 100644 static/src/app.ts create mode 100644 static/src/stores/file.ts create mode 100644 static/src/styles/components/chat/_chat-area.scss create mode 100644 static/src/styles/layout/_app-shell.scss diff --git a/static/src/App.vue b/static/src/App.vue new file mode 100644 index 0000000..1a64c15 --- /dev/null +++ b/static/src/App.vue @@ -0,0 +1,1093 @@ + + + diff --git a/static/src/app.ts b/static/src/app.ts new file mode 100644 index 0000000..f5acb78 --- /dev/null +++ b/static/src/app.ts @@ -0,0 +1,2139 @@ +// @ts-nocheck +// static/app-enhanced.js - 修复版,正确实现Token实时更新 +import katex from 'katex'; +import { mapActions, mapState, mapWritableState } from 'pinia'; +import FileNode from './components/files/FileNode.vue'; +import ConversationSidebar from './components/sidebar/ConversationSidebar.vue'; +import LeftPanel from './components/panels/LeftPanel.vue'; +import FocusPanel from './components/panels/FocusPanel.vue'; +import TokenDrawer from './components/token/TokenDrawer.vue'; +import PersonalizationDrawer from './components/personalization/PersonalizationDrawer.vue'; +import QuickMenu from './components/input/QuickMenu.vue'; +import InputComposer from './components/input/InputComposer.vue'; +import AppShell from './components/shell/AppShell.vue'; +import { useUiStore } from './stores/ui'; +import { useConversationStore } from './stores/conversation'; +import { useChatStore } from './stores/chat'; +import { useInputStore } from './stores/input'; +import { useToolStore } from './stores/tool'; +import { useResourceStore } from './stores/resource'; +import { useUploadStore } from './stores/upload'; +import { useFileStore } from './stores/file'; +import { useSubAgentStore } from './stores/subAgent'; +import { useFocusStore } from './stores/focus'; +import { usePersonalizationStore } from './stores/personalization'; +import { useChatActionStore } from './stores/chatActions'; +import { ICONS, TOOL_CATEGORY_ICON_MAP } from './utils/icons'; +import { initializeLegacySocket } from './composables/useLegacySocket'; +import { useConnectionStore } from './stores/connection'; +import { useEasterEgg } from './composables/useEasterEgg'; +import { renderMarkdown as renderMarkdownHelper } from './composables/useMarkdownRenderer'; +import { + formatTokenCount, + formatBytes, + formatPercentage, + formatRate, + formatResetTime, + formatQuotaValue, + quotaTypeLabel, + buildQuotaResetSummary, + isQuotaExceeded as isQuotaExceededUtil, + buildQuotaToastMessage +} from './utils/formatters'; +import { + getToolIcon, + getToolAnimationClass, + getToolStatusText, + getToolDescription, + cloneToolArguments, + buildToolLabel, + formatSearchTopic, + formatSearchTime, + getLanguageClass +} from './utils/chatDisplay'; +import { + scrollToBottom as scrollToBottomHelper, + conditionalScrollToBottom as conditionalScrollToBottomHelper, + toggleScrollLock as toggleScrollLockHelper, + scrollThinkingToBottom as scrollThinkingToBottomHelper +} from './composables/useScrollControl'; +import { + startResize as startPanelResize, + handleResize as handlePanelResize, + stopResize as stopPanelResize +} from './composables/usePanelResize'; + + +window.katex = katex; + +function updateViewportHeightVar() { + const docEl = document.documentElement; + const visualViewport = window.visualViewport; + + if (visualViewport) { + const vh = visualViewport.height; + const bottomInset = Math.max( + 0, + (window.innerHeight || docEl.clientHeight || vh) - visualViewport.height - visualViewport.offsetTop + ); + docEl.style.setProperty('--app-viewport', `${vh}px`); + docEl.style.setProperty('--app-bottom-inset', `${bottomInset}px`); + } else { + const height = window.innerHeight || docEl.clientHeight; + if (height) { + docEl.style.setProperty('--app-viewport', `${height}px`); + } + docEl.style.setProperty('--app-bottom-inset', 'env(safe-area-inset-bottom, 0px)'); + } +} + +updateViewportHeightVar(); +window.addEventListener('resize', updateViewportHeightVar); +window.addEventListener('orientationchange', updateViewportHeightVar); +window.addEventListener('pageshow', updateViewportHeightVar); +if (window.visualViewport) { + window.visualViewport.addEventListener('resize', updateViewportHeightVar); + window.visualViewport.addEventListener('scroll', updateViewportHeightVar); +} + +const appOptions = { + data() { + return { + // 路由相关 + initialRouteResolved: false, + + // 工具状态跟踪 + preparingTools: new Map(), + activeTools: new Map(), + toolActionIndex: new Map(), + toolStacks: new Map(), + + // ========================================== + // 对话管理相关状态 + // ========================================== + + // 搜索功能 + // ========================================== + // Token统计相关状态(修复版) + // ========================================== + + // 对话压缩状态 + compressing: false, + skipConversationLoadedEvent: false, + skipConversationHistoryReload: false, + _scrollListenerReady: false, + + // 工具控制菜单 + icons: ICONS, + toolCategoryIcons: TOOL_CATEGORY_ICON_MAP + } + }, + + created() { + const actionStore = useChatActionStore(); + actionStore.registerDependencies({ + pushToast: (payload) => this.uiPushToast(payload), + autoResizeInput: () => this.autoResizeInput(), + focusComposer: () => { + const inputEl = this.getComposerElement('stadiumInput'); + if (inputEl && typeof inputEl.focus === 'function') { + inputEl.focus(); + } + }, + isConnected: () => this.isConnected, + getSocket: () => this.socket, + downloadResource: (url, filename) => this.downloadResource(url, filename) + }); + }, + + async mounted() { + console.log('Vue应用已挂载'); + if (window.ensureCsrfToken) { + window.ensureCsrfToken().catch((err) => { + console.warn('CSRF token 初始化失败:', err); + }); + } + await this.bootstrapRoute(); + await this.initSocket(); + this.$nextTick(() => { + this.ensureScrollListener(); + }); + + // 延迟加载初始数据 + setTimeout(() => { + this.loadInitialData(); + }, 500); + + document.addEventListener('click', this.handleClickOutsideQuickMenu); + document.addEventListener('click', this.handleClickOutsidePanelMenu); + window.addEventListener('popstate', this.handlePopState); + + this.subAgentFetch(); + this.subAgentStartPolling(); + + this.$nextTick(() => { + this.autoResizeInput(); + }); + this.resourceStartContainerStatsPolling(); + this.resourceStartProjectStoragePolling(); + this.resourceStartUsageQuotaPolling(); + }, + + computed: { + ...mapWritableState(useConnectionStore, [ + 'isConnected', + 'socket', + 'stopRequested', + 'projectPath', + 'agentVersion', + 'thinkingMode' + ]), + ...mapState(useFileStore, ['contextMenu', 'fileTree', 'expandedFolders', 'todoList']), + ...mapWritableState(useUiStore, [ + 'sidebarCollapsed', + 'panelMode', + 'panelMenuOpen', + 'leftWidth', + 'rightWidth', + 'rightCollapsed', + 'isResizing', + 'resizingPanel', + 'minPanelWidth', + 'maxPanelWidth', + 'quotaToast', + 'toastQueue', + 'confirmDialog', + 'easterEgg' + ]), + ...mapWritableState(useConversationStore, [ + 'conversations', + 'conversationsLoading', + 'hasMoreConversations', + 'loadingMoreConversations', + 'currentConversationId', + 'currentConversationTitle', + 'searchQuery', + 'searchTimer', + 'conversationsOffset', + 'conversationsLimit' + ]), + ...mapWritableState(useChatStore, [ + 'messages', + 'currentMessageIndex', + 'streamingMessage', + 'expandedBlocks', + 'autoScrollEnabled', + 'userScrolling', + 'thinkingScrollLocks' + ]), + ...mapWritableState(useInputStore, [ + 'inputMessage', + 'inputLineCount', + 'inputIsMultiline', + 'inputIsFocused', + 'quickMenuOpen', + 'toolMenuOpen', + 'settingsOpen' + ]), + ...mapWritableState(useToolStore, [ + 'preparingTools', + 'activeTools', + 'toolActionIndex', + 'toolStacks', + 'toolSettings', + 'toolSettingsLoading' + ]), + ...mapWritableState(useResourceStore, [ + 'tokenPanelCollapsed', + 'currentContextTokens', + 'currentConversationTokens', + 'projectStorage', + 'containerStatus', + 'containerNetRate', + 'usageQuota' + ]), + ...mapWritableState(useFocusStore, ['focusedFiles']), + ...mapWritableState(useUploadStore, ['uploading']) + }, + + beforeUnmount() { + document.removeEventListener('click', this.handleClickOutsideQuickMenu); + document.removeEventListener('click', this.handleClickOutsidePanelMenu); + window.removeEventListener('popstate', this.handlePopState); + this.subAgentStopPolling(); + this.resourceStopContainerStatsPolling(); + this.resourceStopProjectStoragePolling(); + this.resourceStopUsageQuotaPolling(); + const cleanup = this.destroyEasterEggEffect(true); + if (cleanup && typeof cleanup.catch === 'function') { + cleanup.catch(() => {}); + } + }, + + watch: { + inputMessage() { + this.autoResizeInput(); + }, + currentConversationId: { + immediate: false, + handler(newValue, oldValue) { + console.log('currentConversationId 变化', { oldValue, newValue, skipConversationHistoryReload: this.skipConversationHistoryReload }); + this.logMessageState('watch:currentConversationId', { oldValue, newValue, skipConversationHistoryReload: this.skipConversationHistoryReload }); + if (!newValue || typeof newValue !== 'string' || newValue.startsWith('temp_')) { + return; + } + if (this.skipConversationHistoryReload) { + this.skipConversationHistoryReload = false; + return; + } + if (oldValue && newValue === oldValue) { + return; + } + this.fetchAndDisplayHistory(); + this.fetchConversationTokenStatistics(); + this.updateCurrentContextTokens(); + } + } + }, + + methods: { + ensureScrollListener() { + if (this._scrollListenerReady) { + return; + } + const area = this.getMessagesAreaElement(); + if (!area) { + return; + } + this.initScrollListener(); + this._scrollListenerReady = true; + }, + ...mapActions(useUiStore, { + uiToggleSidebar: 'toggleSidebar', + uiSetPanelMode: 'setPanelMode', + uiSetPanelMenuOpen: 'setPanelMenuOpen', + uiTogglePanelMenu: 'togglePanelMenu', + uiPushToast: 'pushToast', + uiUpdateToast: 'updateToast', + uiDismissToast: 'dismissToast', + uiShowQuotaToastMessage: 'showQuotaToastMessage', + uiDismissQuotaToast: 'dismissQuotaToast', + uiRequestConfirm: 'requestConfirm', + uiResolveConfirm: 'resolveConfirm' + }), + ...mapActions(useChatStore, { + chatExpandBlock: 'expandBlock', + chatCollapseBlock: 'collapseBlock', + chatClearExpandedBlocks: 'clearExpandedBlocks', + chatSetThinkingLock: 'setThinkingLock', + chatClearThinkingLocks: 'clearThinkingLocks', + chatSetScrollState: 'setScrollState', + chatEnableAutoScroll: 'enableAutoScroll', + chatDisableAutoScroll: 'disableAutoScroll', + chatToggleScrollLockState: 'toggleScrollLockState', + chatAddUserMessage: 'addUserMessage', + chatStartAssistantMessage: 'startAssistantMessage', + chatStartThinkingAction: 'startThinkingAction', + chatAppendThinkingChunk: 'appendThinkingChunk', + chatCompleteThinkingAction: 'completeThinking', + chatStartTextAction: 'startTextAction', + chatAppendTextChunk: 'appendTextChunk', + chatCompleteTextAction: 'completeText', + chatAddSystemMessage: 'addSystemMessage', + chatAddAppendPayloadAction: 'addAppendPayloadAction', + chatAddModifyPayloadAction: 'addModifyPayloadAction', + chatEnsureAssistantMessage: 'ensureAssistantMessage' + }), + ...mapActions(useInputStore, { + inputSetFocused: 'setInputFocused', + inputToggleQuickMenu: 'toggleQuickMenu', + inputCloseMenus: 'closeMenus', + inputOpenQuickMenu: 'openQuickMenu', + inputSetQuickMenuOpen: 'setQuickMenuOpen', + inputToggleToolMenu: 'toggleToolMenu', + inputSetToolMenuOpen: 'setToolMenuOpen', + inputToggleSettingsMenu: 'toggleSettingsMenu', + inputSetSettingsOpen: 'setSettingsOpen', + inputSetMessage: 'setInputMessage', + inputClearMessage: 'clearInputMessage', + inputSetLineCount: 'setInputLineCount', + inputSetMultiline: 'setInputMultiline' + }), + ...mapActions(useToolStore, { + toolRegisterAction: 'registerToolAction', + toolUnregisterAction: 'unregisterToolAction', + toolFindAction: 'findToolAction', + toolTrackAction: 'trackToolAction', + toolReleaseAction: 'releaseToolAction', + toolGetLatestAction: 'getLatestActiveToolAction', + toolResetTracking: 'resetToolTracking', + toolSetSettings: 'setToolSettings', + toolSetSettingsLoading: 'setToolSettingsLoading' + }), + ...mapActions(useResourceStore, { + resourceUpdateCurrentContextTokens: 'updateCurrentContextTokens', + resourceFetchConversationTokenStatistics: 'fetchConversationTokenStatistics', + resourceToggleTokenPanel: 'toggleTokenPanel', + resourceApplyStatusSnapshot: 'applyStatusSnapshot', + resourceUpdateContainerStatus: 'updateContainerStatus', + resourceStartContainerStatsPolling: 'startContainerStatsPolling', + resourceStopContainerStatsPolling: 'stopContainerStatsPolling', + resourceStartProjectStoragePolling: 'startProjectStoragePolling', + resourceStopProjectStoragePolling: 'stopProjectStoragePolling', + resourceStartUsageQuotaPolling: 'startUsageQuotaPolling', + resourceStopUsageQuotaPolling: 'stopUsageQuotaPolling', + resourcePollContainerStats: 'pollContainerStats', + resourcePollProjectStorage: 'pollProjectStorage', + resourceFetchUsageQuota: 'fetchUsageQuota', + resourceResetTokenStatistics: 'resetTokenStatistics', + resourceSetUsageQuota: 'setUsageQuota' + }), + ...mapActions(useUploadStore, { + uploadHandleSelected: 'handleSelectedFiles' + }), + ...mapActions(useFileStore, { + fileFetchTree: 'fetchFileTree', + fileSetTreeFromResponse: 'setFileTreeFromResponse', + fileFetchTodoList: 'fetchTodoList', + fileSetTodoList: 'setTodoList', + fileHideContextMenu: 'hideContextMenu' + }), + ...mapActions(useSubAgentStore, { + subAgentFetch: 'fetchSubAgents', + subAgentStartPolling: 'startPolling', + subAgentStopPolling: 'stopPolling' + }), + ...mapActions(useFocusStore, { + focusFetchFiles: 'fetchFocusedFiles', + focusSetFiles: 'setFocusedFiles' + }), + ...mapActions(usePersonalizationStore, { + personalizationOpenDrawer: 'openDrawer' + }), + getInputComposerRef() { + return this.$refs.inputComposer || null; + }, + getComposerElement(field) { + const composer = this.getInputComposerRef(); + if (!composer || !composer[field]) { + return null; + } + const refValue = composer[field]; + if (refValue && typeof refValue === 'object' && 'value' in refValue) { + return refValue.value; + } + return refValue; + }, + getMessagesAreaElement() { + const ref = this.$refs.messagesArea; + if (!ref) { + return null; + } + if (ref instanceof HTMLElement) { + return ref; + } + if (ref.rootEl) { + return ref.rootEl.value || ref.rootEl; + } + if (ref.$el && ref.$el.querySelector) { + const el = ref.$el.querySelector('.messages-area'); + if (el) { + return el; + } + } + return null; + }, + getThinkingContentElement(blockId) { + const chatArea = this.$refs.messagesArea; + if (chatArea && typeof chatArea.getThinkingRef === 'function') { + const el = chatArea.getThinkingRef(blockId); + if (el) { + return el; + } + } + const refName = `thinkingContent-${blockId}`; + const elRef = this.$refs[refName]; + if (Array.isArray(elRef)) { + return elRef[0] || null; + } + return elRef || null; + }, + logMessageState(action, extra = {}) { + const count = Array.isArray(this.messages) ? this.messages.length : 'N/A'; + console.log('[Messages]', { + action, + count, + conversationId: this.currentConversationId, + streaming: this.streamingMessage, + ...extra + }); + }, + iconStyle(iconKey, size) { + const iconPath = this.icons ? this.icons[iconKey] : null; + if (!iconPath) { + return {}; + } + const style = { '--icon-src': `url(${iconPath})` }; + if (size) { + style['--icon-size'] = size; + } + return style; + }, + + toolCategoryIcon(categoryId) { + return this.toolCategoryIcons[categoryId] || 'settings'; + }, + + openGuiFileManager() { + window.open('/file-manager', '_blank'); + }, + + findMessageByAction(action) { + if (!action) { + return null; + } + for (const message of this.messages) { + if (!message.actions) { + continue; + } + if (message.actions.includes(action)) { + return message; + } + } + return null; + }, + + renderMarkdown(content, isStreaming = false) { + return renderMarkdownHelper(content, isStreaming); + }, + + hasContainerStats() { + return !!( + this.containerStatus && + this.containerStatus.mode === 'docker' && + this.containerStatus.stats + ); + }, + + containerStatusClass() { + if (!this.containerStatus) { + return 'status-pill--host'; + } + if (this.containerStatus.mode !== 'docker') { + return 'status-pill--host'; + } + const rawStatus = ( + this.containerStatus.state && + (this.containerStatus.state.status || this.containerStatus.state.Status) + ) || ''; + const status = String(rawStatus).toLowerCase(); + if (status.includes('running')) { + return 'status-pill--running'; + } + if (status.includes('paused')) { + return 'status-pill--stopped'; + } + if (status.includes('exited') || status.includes('dead')) { + return 'status-pill--stopped'; + } + return 'status-pill--running'; + }, + + containerStatusText() { + if (!this.containerStatus) { + return '未知'; + } + if (this.containerStatus.mode !== 'docker') { + return '宿主机模式'; + } + const rawStatus = ( + this.containerStatus.state && + (this.containerStatus.state.status || this.containerStatus.state.Status) + ) || ''; + const status = String(rawStatus).toLowerCase(); + if (status.includes('running')) { + return '运行中'; + } + if (status.includes('paused')) { + return '已暂停'; + } + if (status.includes('exited') || status.includes('dead')) { + return '已停止'; + } + return rawStatus || '容器模式'; + }, + + formatTime(value) { + if (!value) { + return '未知时间'; + } + let date; + if (typeof value === 'number') { + date = new Date(value); + } else if (typeof value === 'string') { + const parsed = Date.parse(value); + if (!Number.isNaN(parsed)) { + date = new Date(parsed); + } else { + const numeric = Number(value); + if (!Number.isNaN(numeric)) { + date = new Date(numeric); + } + } + } else if (value instanceof Date) { + date = value; + } + if (!date || Number.isNaN(date.getTime())) { + return String(value); + } + const now = Date.now(); + const diff = now - date.getTime(); + if (diff < 60000) { + return '刚刚'; + } + if (diff < 3600000) { + const mins = Math.floor(diff / 60000); + return `${mins} 分钟前`; + } + if (diff < 86400000) { + const hours = Math.floor(diff / 3600000); + return `${hours} 小时前`; + } + const formatter = new Intl.DateTimeFormat('zh-CN', { + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit' + }); + return formatter.format(date); + }, + + async bootstrapRoute() { + const path = window.location.pathname.replace(/^\/+/, ''); + if (!path || path === 'new') { + this.currentConversationId = null; + this.currentConversationTitle = '新对话'; + this.initialRouteResolved = true; + return; + } + + const convId = path.startsWith('conv_') ? path : `conv_${path}`; + try { + const resp = await fetch(`/api/conversations/${convId}/load`, { method: 'PUT' }); + const result = await resp.json(); + if (result.success) { + this.currentConversationId = convId; + this.currentConversationTitle = result.title || '对话'; + history.replaceState({ conversationId: convId }, '', `/${this.stripConversationPrefix(convId)}`); + } else { + history.replaceState({}, '', '/new'); + this.currentConversationId = null; + this.currentConversationTitle = '新对话'; + } + } catch (error) { + console.warn('初始化路由失败:', error); + history.replaceState({}, '', '/new'); + this.currentConversationId = null; + this.currentConversationTitle = '新对话'; + } finally { + this.initialRouteResolved = true; + } + }, + + handlePopState(event) { + const state = event.state || {}; + const convId = state.conversationId; + if (!convId) { + this.currentConversationId = null; + this.currentConversationTitle = '新对话'; + this.logMessageState('handlePopState:clear-messages-no-conversation'); + this.messages = []; + this.logMessageState('handlePopState:after-clear-no-conversation'); + this.resetAllStates('handlePopState:no-conversation'); + this.resetTokenStatistics(); + return; + } + this.loadConversation(convId); + }, + + stripConversationPrefix(conversationId) { + if (!conversationId) return ''; + return conversationId.startsWith('conv_') ? conversationId.slice(5) : conversationId; + }, + + async downloadFile(path) { + if (!path) { + this.fileHideContextMenu(); + return; + } + const url = `/api/download/file?path=${encodeURIComponent(path)}`; + const name = path.split('/').pop() || 'file'; + await this.downloadResource(url, name); + }, + + async downloadFolder(path) { + if (!path) { + this.fileHideContextMenu(); + return; + } + const url = `/api/download/folder?path=${encodeURIComponent(path)}`; + const segments = path.split('/').filter(Boolean); + const folderName = segments.length ? segments.pop() : 'folder'; + await this.downloadResource(url, `${folderName}.zip`); + }, + + async downloadResource(url, filename) { + try { + const response = await fetch(url); + if (!response.ok) { + let message = response.statusText; + try { + const errorData = await response.json(); + message = errorData.error || errorData.message || message; + } catch (err) { + message = await response.text(); + } + this.uiPushToast({ + title: '下载失败', + message: message || '无法完成下载', + type: 'error' + }); + return; + } + + const blob = await response.blob(); + const downloadName = filename || 'download'; + const link = document.createElement('a'); + const href = URL.createObjectURL(blob); + link.href = href; + link.download = downloadName; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(href); + } catch (error) { + console.error('下载失败:', error); + this.uiPushToast({ + title: '下载失败', + message: error.message || String(error), + type: 'error' + }); + } finally { + this.fileHideContextMenu(); + } + }, + + initScrollListener() { + const messagesArea = this.getMessagesAreaElement(); + if (!messagesArea) { + console.warn('消息区域未找到'); + return; + } + this._scrollListenerReady = true; + + let isProgrammaticScroll = false; + const bottomThreshold = 12; + + this._setScrollingFlag = (flag) => { + isProgrammaticScroll = !!flag; + }; + + messagesArea.addEventListener('scroll', () => { + if (isProgrammaticScroll) { + return; + } + + const scrollTop = messagesArea.scrollTop; + const scrollHeight = messagesArea.scrollHeight; + const clientHeight = messagesArea.clientHeight; + const isAtBottom = scrollHeight - scrollTop - clientHeight < bottomThreshold; + + if (isAtBottom) { + this.chatSetScrollState({ userScrolling: false, autoScrollEnabled: true }); + } else { + this.chatSetScrollState({ userScrolling: true, autoScrollEnabled: false }); + } + }); + }, + + async initSocket() { + await initializeLegacySocket(this); + }, + + cleanupStaleToolActions() { + this.messages.forEach(msg => { + if (!msg.actions) { + return; + } + msg.actions.forEach(action => { + if (action.type !== 'tool' || !action.tool) { + return; + } + if (['running', 'preparing'].includes(action.tool.status)) { + action.tool.status = 'stale'; + action.tool.message = action.tool.message || '已被新的响应中断'; + this.toolUnregisterAction(action); + } + }); + }); + this.preparingTools.clear(); + this.toolActionIndex.clear(); + }, + + // 完整重置所有状态 + resetAllStates(reason = 'unspecified') { + console.log('重置所有前端状态', { reason, conversationId: this.currentConversationId }); + this.logMessageState('resetAllStates:before-cleanup', { reason }); + this.fileHideContextMenu(); + + // 重置消息和流状态 + this.streamingMessage = false; + this.currentMessageIndex = -1; + this.stopRequested = false; + + // 清理工具状态 + this.toolResetTracking(); + + // 新增:将所有未完成的工具标记为已完成 + this.messages.forEach(msg => { + if (msg.role === 'assistant' && msg.actions) { + msg.actions.forEach(action => { + if (action.type === 'tool' && + (action.tool.status === 'preparing' || action.tool.status === 'running')) { + action.tool.status = 'completed'; + } + }); + } + }); + + // 重置滚动状态 + this.chatEnableAutoScroll(); + + // 清理Markdown缓存 + if (this.markdownCache) { + this.markdownCache.clear(); + } + this.chatClearThinkingLocks(); + + // 强制更新视图 + this.$forceUpdate(); + + this.inputSetSettingsOpen(false); + this.inputSetToolMenuOpen(false); + this.inputSetQuickMenuOpen(false); + this.inputSetLineCount(1); + this.inputSetMultiline(false); + this.inputClearMessage(); + this.toolSetSettingsLoading(false); + this.toolSetSettings([]); + + console.log('前端状态重置完成'); + this._scrollListenerReady = false; + this.$nextTick(() => { + this.ensureScrollListener(); + }); + + const activeConversationId = this.currentConversationId; + if (activeConversationId && !activeConversationId.startsWith('temp_')) { + setTimeout(() => { + if (this.currentConversationId === activeConversationId) { + this.fetchAndDisplayHistory(); + this.fetchConversationTokenStatistics(); + this.updateCurrentContextTokens(); + } + }, 250); + } + this.logMessageState('resetAllStates:after-cleanup', { reason }); + }, + + // 重置Token统计 + + openRealtimeTerminal() { + const { protocol, hostname, port } = window.location; + const target = `${protocol}//${hostname}${port ? ':' + port : ''}/terminal`; + window.open(target, '_blank'); + }, + + resetTokenStatistics() { + this.resourceResetTokenStatistics(); + }, + + async loadInitialData() { + try { + console.log('加载初始数据...'); + + await this.fileFetchTree(); + await this.focusFetchFiles(); + await this.fileFetchTodoList(); + + const statusResponse = await fetch('/api/status'); + const statusData = await statusResponse.json(); + this.projectPath = statusData.project_path || ''; + this.agentVersion = statusData.version || this.agentVersion; + this.thinkingMode = !!statusData.thinking_mode; + this.applyStatusSnapshot(statusData); + await this.fetchUsageQuota(); + + // 获取当前对话信息 + const statusConversationId = statusData.conversation && statusData.conversation.current_id; + if (statusConversationId) { + if (!this.currentConversationId) { + this.skipConversationHistoryReload = true; + this.currentConversationId = statusConversationId; + } + // 如果有当前对话,尝试获取标题和Token统计 + try { + const convResponse = await fetch(`/api/conversations/current`); + const convData = await convResponse.json(); + if (convData.success && convData.data) { + this.currentConversationTitle = convData.data.title; + } + await this.fetchAndDisplayHistory(); + // 获取当前对话的Token统计 + this.fetchConversationTokenStatistics(); + this.updateCurrentContextTokens(); + } catch (e) { + console.warn('获取当前对话标题失败:', e); + } + } + + await this.loadToolSettings(true); + + console.log('初始数据加载完成'); + } catch (error) { + console.error('加载初始数据失败:', error); + } + }, + + // ========================================== + // Token / 资源状态封装(Pinia store) + // ========================================== + + async updateCurrentContextTokens() { + await this.resourceUpdateCurrentContextTokens(this.currentConversationId); + }, + + async fetchConversationTokenStatistics() { + await this.resourceFetchConversationTokenStatistics(this.currentConversationId); + }, + + toggleTokenPanel() { + this.resourceToggleTokenPanel(); + }, + + applyStatusSnapshot(status) { + this.resourceApplyStatusSnapshot(status); + }, + + updateContainerStatus(status) { + this.resourceUpdateContainerStatus(status); + }, + + pollContainerStats() { + return this.resourcePollContainerStats(); + }, + + startContainerStatsPolling() { + this.resourceStartContainerStatsPolling(); + }, + + stopContainerStatsPolling() { + this.resourceStopContainerStatsPolling(); + }, + + pollProjectStorage() { + return this.resourcePollProjectStorage(); + }, + + startProjectStoragePolling() { + this.resourceStartProjectStoragePolling(); + }, + + stopProjectStoragePolling() { + this.resourceStopProjectStoragePolling(); + }, + + fetchUsageQuota() { + return this.resourceFetchUsageQuota(); + }, + + startUsageQuotaPolling() { + this.resourceStartUsageQuotaPolling(); + }, + + stopUsageQuotaPolling() { + this.resourceStopUsageQuotaPolling(); + }, + + // ========================================== + // 对话管理核心功能 + // ========================================== + + async loadConversationsList() { + this.conversationsLoading = true; + try { + const response = await fetch(`/api/conversations?limit=${this.conversationsLimit}&offset=${this.conversationsOffset}`); + const data = await response.json(); + + if (data.success) { + if (this.conversationsOffset === 0) { + this.conversations = data.data.conversations; + } else { + this.conversations.push(...data.data.conversations); + } + if (this.currentConversationId) { + this.promoteConversationToTop(this.currentConversationId); + } + this.hasMoreConversations = data.data.has_more; + console.log(`已加载 ${this.conversations.length} 个对话`); + + if (this.conversationsOffset === 0 && !this.currentConversationId && this.conversations.length > 0) { + const latestConversation = this.conversations[0]; + if (latestConversation && latestConversation.id) { + await this.loadConversation(latestConversation.id); + } + } + } else { + console.error('加载对话列表失败:', data.error); + } + } catch (error) { + console.error('加载对话列表异常:', error); + } finally { + this.conversationsLoading = false; + } + }, + + async loadMoreConversations() { + if (this.loadingMoreConversations || !this.hasMoreConversations) return; + + this.loadingMoreConversations = true; + this.conversationsOffset += this.conversationsLimit; + await this.loadConversationsList(); + this.loadingMoreConversations = false; + }, + + async loadConversation(conversationId) { + console.log('加载对话:', conversationId); + this.logMessageState('loadConversation:start', { conversationId }); + + if (conversationId === this.currentConversationId) { + console.log('已是当前对话,跳过加载'); + return; + } + + try { + // 1. 调用加载API + const response = await fetch(`/api/conversations/${conversationId}/load`, { + method: 'PUT' + }); + const result = await response.json(); + + if (result.success) { + console.log('对话加载API成功:', result); + + // 2. 更新当前对话信息 + this.skipConversationHistoryReload = true; + this.currentConversationId = conversationId; + this.currentConversationTitle = result.title; + this.promoteConversationToTop(conversationId); + history.pushState({ conversationId }, '', `/${this.stripConversationPrefix(conversationId)}`); + this.skipConversationLoadedEvent = true; + + // 3. 重置UI状态 + this.resetAllStates(`loadConversation:${conversationId}`); + this.subAgentFetch(); + this.fetchTodoList(); + + // 4. 延迟获取并显示历史对话内容(关键功能) + setTimeout(() => { + this.fetchAndDisplayHistory(); + }, 300); + + // 5. 获取Token统计(重点:加载历史累计统计+当前上下文) + setTimeout(() => { + this.fetchConversationTokenStatistics(); + this.updateCurrentContextTokens(); + }, 500); + + } else { + console.error('对话加载失败:', result.message); + this.uiPushToast({ + title: '加载对话失败', + message: result.message || '服务器未返回成功状态', + type: 'error' + }); + } + } catch (error) { + console.error('加载对话异常:', error); + this.uiPushToast({ + title: '加载对话异常', + message: error.message || String(error), + type: 'error' + }); + } + }, + + promoteConversationToTop(conversationId) { + if (!Array.isArray(this.conversations) || !conversationId) { + return; + } + const index = this.conversations.findIndex(conv => conv && conv.id === conversationId); + if (index > 0) { + const [selected] = this.conversations.splice(index, 1); + this.conversations.unshift(selected); + } + }, + + // ========================================== + // 关键功能:获取并显示历史对话内容 + // ========================================== + async fetchAndDisplayHistory() { + console.log('开始获取历史对话内容...'); + this.logMessageState('fetchAndDisplayHistory:start', { conversationId: this.currentConversationId }); + + if (!this.currentConversationId || this.currentConversationId.startsWith('temp_')) { + console.log('没有当前对话ID,跳过历史加载'); + return; + } + + try { + // 使用专门的API获取对话消息历史 + const messagesResponse = await fetch(`/api/conversations/${this.currentConversationId}/messages`); + + if (!messagesResponse.ok) { + console.warn('无法获取消息历史,尝试备用方法'); + // 备用方案:通过状态API获取 + const statusResponse = await fetch('/api/status'); + const status = await statusResponse.json(); + console.log('系统状态:', status); + this.applyStatusSnapshot(status); + + // 如果状态中有对话历史字段 + if (status.conversation_history && Array.isArray(status.conversation_history)) { + this.renderHistoryMessages(status.conversation_history); + return; + } + + console.log('备用方案也无法获取历史消息'); + return; + } + + const messagesData = await messagesResponse.json(); + console.log('获取到消息数据:', messagesData); + + if (messagesData.success && messagesData.data && messagesData.data.messages) { + const messages = messagesData.data.messages; + console.log(`发现 ${messages.length} 条历史消息`); + + if (messages.length > 0) { + // 清空当前显示的消息 + this.logMessageState('fetchAndDisplayHistory:before-clear-existing'); + this.messages = []; + this.logMessageState('fetchAndDisplayHistory:after-clear-existing'); + + // 渲染历史消息 - 这是关键功能 + this.renderHistoryMessages(messages); + + // 滚动到底部 + this.$nextTick(() => { + this.scrollToBottom(); + }); + + console.log('历史对话内容显示完成'); + } else { + console.log('对话存在但没有历史消息'); + this.logMessageState('fetchAndDisplayHistory:no-history-clear'); + this.messages = []; + this.logMessageState('fetchAndDisplayHistory:no-history-cleared'); + } + } else { + console.log('消息数据格式不正确:', messagesData); + this.logMessageState('fetchAndDisplayHistory:invalid-data-clear'); + this.messages = []; + this.logMessageState('fetchAndDisplayHistory:invalid-data-cleared'); + } + + } catch (error) { + console.error('获取历史对话失败:', error); + console.log('尝试不显示错误弹窗,仅在控制台记录'); + // 不显示alert,避免打断用户体验 + this.logMessageState('fetchAndDisplayHistory:error-clear', { error: error?.message || String(error) }); + this.messages = []; + this.logMessageState('fetchAndDisplayHistory:error-cleared'); + } + }, + + // ========================================== + // 关键功能:渲染历史消息 + // ========================================== + renderHistoryMessages(historyMessages) { + console.log('开始渲染历史消息...', historyMessages); + console.log('历史消息数量:', historyMessages.length); + this.logMessageState('renderHistoryMessages:start', { historyCount: historyMessages.length }); + + if (!Array.isArray(historyMessages)) { + console.error('历史消息不是数组格式'); + return; + } + + let currentAssistantMessage = null; + + historyMessages.forEach((message, index) => { + console.log(`处理消息 ${index + 1}/${historyMessages.length}:`, message.role, message); + + if (message.role === 'user') { + // 用户消息 - 先结束之前的assistant消息 + if (currentAssistantMessage && currentAssistantMessage.actions.length > 0) { + this.messages.push(currentAssistantMessage); + currentAssistantMessage = null; + } + + this.messages.push({ + role: 'user', + content: message.content || '' + }); + console.log('添加用户消息:', message.content?.substring(0, 50) + '...'); + + } else if (message.role === 'assistant') { + // AI消息 - 如果没有当前assistant消息,创建一个 + if (!currentAssistantMessage) { + currentAssistantMessage = { + role: 'assistant', + actions: [], + streamingThinking: '', + streamingText: '', + currentStreamingType: null, + activeThinkingId: null + }; + } + + const content = message.content || ''; + let reasoningText = (message.reasoning_content || '').trim(); + + if (!reasoningText) { + const thinkPatterns = [ + /([\s\S]*?)<\/think>/g, + /([\s\S]*?)<\/thinking>/g + ]; + + let extracted = ''; + for (const pattern of thinkPatterns) { + let match; + while ((match = pattern.exec(content)) !== null) { + extracted += (match[1] || '').trim() + '\n'; + } + } + reasoningText = extracted.trim(); + } + + if (reasoningText) { + const blockId = `history-thinking-${Date.now()}-${Math.random().toString(36).slice(2)}`; + currentAssistantMessage.actions.push({ + id: `history-think-${Date.now()}-${Math.random()}`, + type: 'thinking', + content: reasoningText, + streaming: false, + timestamp: Date.now(), + blockId + }); + console.log('添加思考内容:', reasoningText.substring(0, 50) + '...'); + } + + // 处理普通文本内容(移除思考标签后的内容) + const metadata = message.metadata || {}; + const appendPayloadMeta = metadata.append_payload; + const modifyPayloadMeta = metadata.modify_payload; + const isAppendMessage = message.name === 'append_to_file'; + const isModifyMessage = message.name === 'modify_file'; + const containsAppendMarkers = /<<<\s*(APPEND|MODIFY)/i.test(content || '') || /<<>>/i.test(content || ''); + + let textContent = content; + if (!message.reasoning_content) { + textContent = textContent + .replace(/[\s\S]*?<\/think>/g, '') + .replace(/[\s\S]*?<\/thinking>/g, '') + .trim(); + } else { + textContent = textContent.trim(); + } + + if (appendPayloadMeta) { + currentAssistantMessage.actions.push({ + id: `history-append-payload-${Date.now()}-${Math.random()}`, + type: 'append_payload', + append: { + path: appendPayloadMeta.path || '未知文件', + forced: !!appendPayloadMeta.forced, + success: appendPayloadMeta.success === undefined ? true : !!appendPayloadMeta.success, + lines: appendPayloadMeta.lines ?? null, + bytes: appendPayloadMeta.bytes ?? null + }, + timestamp: Date.now() + }); + console.log('添加append占位信息:', appendPayloadMeta.path); + } else if (modifyPayloadMeta) { + currentAssistantMessage.actions.push({ + id: `history-modify-payload-${Date.now()}-${Math.random()}`, + type: 'modify_payload', + modify: { + path: modifyPayloadMeta.path || '未知文件', + total: modifyPayloadMeta.total_blocks ?? null, + completed: modifyPayloadMeta.completed || [], + failed: modifyPayloadMeta.failed || [], + forced: !!modifyPayloadMeta.forced, + details: modifyPayloadMeta.details || [] + }, + timestamp: Date.now() + }); + console.log('添加modify占位信息:', modifyPayloadMeta.path); + } + + if (textContent && !appendPayloadMeta && !modifyPayloadMeta && !isAppendMessage && !isModifyMessage && !containsAppendMarkers) { + currentAssistantMessage.actions.push({ + id: `history-text-${Date.now()}-${Math.random()}`, + type: 'text', + content: textContent, + streaming: false, + timestamp: Date.now() + }); + console.log('添加文本内容:', textContent.substring(0, 50) + '...'); + } + + // 处理工具调用 + if (message.tool_calls && Array.isArray(message.tool_calls)) { + message.tool_calls.forEach((toolCall, tcIndex) => { + let arguments_obj = {}; + try { + arguments_obj = typeof toolCall.function.arguments === 'string' + ? JSON.parse(toolCall.function.arguments || '{}') + : (toolCall.function.arguments || {}); + } catch (e) { + console.warn('解析工具参数失败:', e); + arguments_obj = {}; + } + + currentAssistantMessage.actions.push({ + id: `history-tool-${toolCall.id || Date.now()}-${tcIndex}`, + type: 'tool', + tool: { + id: toolCall.id, + name: toolCall.function.name, + arguments: arguments_obj, + status: 'preparing', + result: null + }, + timestamp: Date.now() + }); + console.log('添加工具调用:', toolCall.function.name); + }); + } + + } else if (message.role === 'tool') { + // 工具结果 - 更新当前assistant消息中对应的工具 + if (currentAssistantMessage) { + // 查找对应的工具action - 使用更灵活的匹配 + let toolAction = null; + + // 优先按tool_call_id匹配 + if (message.tool_call_id) { + toolAction = currentAssistantMessage.actions.find(action => + action.type === 'tool' && + action.tool.id === message.tool_call_id + ); + } + + // 如果找不到,按name匹配最后一个同名工具 + if (!toolAction && message.name) { + const sameNameTools = currentAssistantMessage.actions.filter(action => + action.type === 'tool' && + action.tool.name === message.name + ); + toolAction = sameNameTools[sameNameTools.length - 1]; // 取最后一个 + } + + if (toolAction) { + // 解析工具结果 + let result; + try { + // 尝试解析为JSON + result = JSON.parse(message.content); + } catch (e) { + // 如果不是JSON,就作为纯文本 + result = { + output: message.content, + success: true + }; + } + + toolAction.tool.status = 'completed'; + toolAction.tool.result = result; + if (message.name === 'append_to_file' && result && result.message) { + toolAction.tool.message = result.message; + } + console.log(`更新工具结果: ${message.name} -> ${message.content?.substring(0, 50)}...`); + + // append_to_file 的摘要在 append_payload 占位中呈现,此处无需重复 + } else { + console.warn('找不到对应的工具调用:', message.name, message.tool_call_id); + } + } + + } else { + // 其他类型消息(如system)- 先结束当前assistant消息 + if (currentAssistantMessage && currentAssistantMessage.actions.length > 0) { + this.messages.push(currentAssistantMessage); + currentAssistantMessage = null; + } + + console.log('处理其他类型消息:', message.role); + this.messages.push({ + role: message.role, + content: message.content || '' + }); + } + }); + + // 处理最后一个assistant消息 + if (currentAssistantMessage && currentAssistantMessage.actions.length > 0) { + this.messages.push(currentAssistantMessage); + } + + console.log(`历史消息渲染完成,共 ${this.messages.length} 条消息`); + this.logMessageState('renderHistoryMessages:after-render'); + + // 强制更新视图 + this.$forceUpdate(); + + // 确保滚动到底部 + this.$nextTick(() => { + this.scrollToBottom(); + setTimeout(() => { + const blockCount = this.$el && this.$el.querySelectorAll + ? this.$el.querySelectorAll('.message-block').length + : 'N/A'; + console.log('[Messages] DOM 渲染统计', { + blocks: blockCount, + conversationId: this.currentConversationId + }); + }, 0); + }); + }, + + async createNewConversation() { + console.log('创建新对话...'); + this.logMessageState('createNewConversation:start'); + + try { + const response = await fetch('/api/conversations', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + thinking_mode: this.thinkingMode + }) + }); + + const result = await response.json(); + + if (result.success) { + console.log('新对话创建成功:', result.conversation_id); + + // 清空当前消息 + this.logMessageState('createNewConversation:before-clear'); + this.messages = []; + this.logMessageState('createNewConversation:after-clear'); + this.currentConversationId = result.conversation_id; + this.currentConversationTitle = '新对话'; + history.pushState({ conversationId: this.currentConversationId }, '', `/${this.stripConversationPrefix(this.currentConversationId)}`); + + // 重置Token统计 + this.resetTokenStatistics(); + + // 重置状态 + this.resetAllStates('createNewConversation'); + + // 刷新对话列表 + this.conversationsOffset = 0; + await this.loadConversationsList(); + + } else { + console.error('创建对话失败:', result.message); + this.uiPushToast({ + title: '创建对话失败', + message: result.message || '服务器未返回成功状态', + type: 'error' + }); + } + } catch (error) { + console.error('创建对话异常:', error); + this.uiPushToast({ + title: '创建对话异常', + message: error.message || String(error), + type: 'error' + }); + } + }, + + async deleteConversation(conversationId) { + const confirmed = await this.confirmAction({ + title: '删除对话', + message: '确定要删除这个对话吗?删除后无法恢复。', + confirmText: '删除', + cancelText: '取消' + }); + if (!confirmed) { + return; + } + + console.log('删除对话:', conversationId); + this.logMessageState('deleteConversation:start', { conversationId }); + + try { + const response = await fetch(`/api/conversations/${conversationId}`, { + method: 'DELETE' + }); + + const result = await response.json(); + + if (result.success) { + console.log('对话删除成功'); + + // 如果删除的是当前对话,清空界面 + if (conversationId === this.currentConversationId) { + this.logMessageState('deleteConversation:before-clear', { conversationId }); + this.messages = []; + this.logMessageState('deleteConversation:after-clear', { conversationId }); + this.currentConversationId = null; + this.currentConversationTitle = ''; + this.resetAllStates(`deleteConversation:${conversationId}`); + this.resetTokenStatistics(); + history.replaceState({}, '', '/new'); + } + + // 刷新对话列表 + this.conversationsOffset = 0; + await this.loadConversationsList(); + + } else { + console.error('删除对话失败:', result.message); + this.uiPushToast({ + title: '删除对话失败', + message: result.message || '服务器未返回成功状态', + type: 'error' + }); + } + } catch (error) { + console.error('删除对话异常:', error); + this.uiPushToast({ + title: '删除对话异常', + message: error.message || String(error), + type: 'error' + }); + } + }, + + async duplicateConversation(conversationId) { + console.log('复制对话:', conversationId); + try { + const response = await fetch(`/api/conversations/${conversationId}/duplicate`, { + method: 'POST' + }); + + const result = await response.json(); + + if (response.ok && result.success) { + const newId = result.duplicate_conversation_id; + if (newId) { + this.currentConversationId = newId; + } + + this.conversationsOffset = 0; + await this.loadConversationsList(); + } else { + const message = result.message || result.error || '复制失败'; + this.uiPushToast({ + title: '复制对话失败', + message, + type: 'error' + }); + } + } catch (error) { + console.error('复制对话异常:', error); + this.uiPushToast({ + title: '复制对话异常', + message: error.message || String(error), + type: 'error' + }); + } + }, + + searchConversations() { + // 简单的搜索功能,实际实现可以调用搜索API + if (this.searchTimer) { + clearTimeout(this.searchTimer); + } + + this.searchTimer = setTimeout(() => { + if (this.searchQuery.trim()) { + console.log('搜索对话:', this.searchQuery); + // TODO: 实现搜索API调用 + // this.searchConversationsAPI(this.searchQuery); + } else { + // 清空搜索,重新加载全部对话 + this.conversationsOffset = 0; + this.loadConversationsList(); + } + }, 300); + }, + + handleSidebarSearch(value) { + this.searchQuery = value; + this.searchConversations(); + }, + + toggleSidebar() { + this.uiToggleSidebar(); + }, + + togglePanelMenu() { + this.uiTogglePanelMenu(); + }, + + selectPanelMode(mode) { + this.uiSetPanelMode(mode); + this.uiSetPanelMenuOpen(false); + }, + + openPersonalPage() { + this.personalizationOpenDrawer(); + }, + + fetchTodoList() { + return this.fileFetchTodoList(); + }, + + fetchSubAgents() { + return this.subAgentFetch(); + }, + + async toggleThinkingMode() { + const nextMode = !this.thinkingMode; + try { + const response = await fetch('/api/thinking-mode', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ thinking_mode: nextMode }) + }); + const data = await response.json(); + if (response.ok && data.success) { + const actual = typeof data.data === 'boolean' ? data.data : nextMode; + this.thinkingMode = actual; + return; + } + throw new Error(data.message || data.error || '切换失败'); + } catch (error) { + console.error('切换思考模式失败:', error); + this.uiPushToast({ + title: '切换思考模式失败', + message: error.message || '请稍后重试', + type: 'error' + }); + } + }, + + triggerFileUpload() { + if (this.uploading) { + return; + } + const input = this.getComposerElement('fileUploadInput'); + if (input) { + input.click(); + } + }, + + handleFileSelected(files) { + this.uploadHandleSelected(files); + }, + + handleSendOrStop() { + if (this.streamingMessage) { + this.stopTask(); + } else { + this.sendMessage(); + } + }, + + sendMessage() { + if (this.streamingMessage || !this.isConnected) { + return; + } + + if (!this.inputMessage.trim()) { + return; + } + + const quotaType = this.thinkingMode ? 'thinking' : 'fast'; + if (this.isQuotaExceeded(quotaType)) { + this.showQuotaToast({ type: quotaType }); + return; + } + + const message = this.inputMessage; + + if (message.startsWith('/')) { + this.socket.emit('send_command', { command: message }); + this.inputClearMessage(); + this.autoResizeInput(); + return; + } + + this.chatAddUserMessage(message); + this.socket.emit('send_message', { message: message, conversation_id: this.currentConversationId }); + this.inputClearMessage(); + this.inputSetLineCount(1); + this.inputSetMultiline(false); + this.chatEnableAutoScroll(); + this.scrollToBottom(); + this.autoResizeInput(); + + // 发送消息后延迟更新当前上下文Token(关键修复:恢复原逻辑) + setTimeout(() => { + if (this.currentConversationId) { + this.updateCurrentContextTokens(); + } + }, 1000); + }, + + // 新增:停止任务方法 + stopTask() { + if (this.streamingMessage && !this.stopRequested) { + this.socket.emit('stop_task'); + this.stopRequested = true; + console.log('发送停止请求'); + } + }, + + async clearChat() { + const confirmed = await this.confirmAction({ + title: '清除对话', + message: '确定要清除所有对话记录吗?该操作不可撤销。', + confirmText: '清除', + cancelText: '取消' + }); + if (confirmed) { + this.socket.emit('send_command', { command: '/clear' }); + } + }, + + async compressConversation() { + if (!this.currentConversationId) { + this.uiPushToast({ + title: '无法压缩', + message: '当前没有可压缩的对话。', + type: 'info' + }); + return; + } + + if (this.compressing) { + return; + } + + const confirmed = await this.confirmAction({ + title: '压缩对话', + message: '确定要压缩当前对话记录吗?压缩后会生成新的对话副本。', + confirmText: '压缩', + cancelText: '取消' + }); + if (!confirmed) { + return; + } + + this.compressing = true; + + try { + const response = await fetch(`/api/conversations/${this.currentConversationId}/compress`, { + method: 'POST' + }); + + const result = await response.json(); + + if (response.ok && result.success) { + const newId = result.compressed_conversation_id; + if (newId) { + this.currentConversationId = newId; + } + console.log('对话压缩完成:', result); + } else { + const message = result.message || result.error || '压缩失败'; + this.uiPushToast({ + title: '压缩失败', + message, + type: 'error' + }); + } + } catch (error) { + console.error('压缩对话异常:', error); + this.uiPushToast({ + title: '压缩对话异常', + message: error.message || '请稍后重试', + type: 'error' + }); + } finally { + this.compressing = false; + } + }, + + toggleToolMenu() { + if (!this.isConnected) { + return; + } + const nextState = this.inputToggleToolMenu(); + if (nextState) { + this.inputSetSettingsOpen(false); + if (!this.quickMenuOpen) { + this.inputOpenQuickMenu(); + } + this.loadToolSettings(true); + } else { + this.inputSetToolMenuOpen(false); + } + }, + + toggleQuickMenu() { + if (!this.isConnected) { + return; + } + this.inputToggleQuickMenu(); + }, + + closeQuickMenu() { + this.inputCloseMenus(); + }, + + handleQuickUpload() { + if (this.uploading || !this.isConnected) { + return; + } + this.triggerFileUpload(); + }, + + handleQuickModeToggle() { + if (!this.isConnected || this.streamingMessage) { + return; + } + this.toggleThinkingMode(); + }, + + handleInputChange() { + this.autoResizeInput(); + }, + + handleInputFocus() { + this.inputSetFocused(true); + this.closeQuickMenu(); + }, + + handleInputBlur() { + this.inputSetFocused(false); + }, + + handleRealtimeTerminalClick() { + if (!this.isConnected) { + return; + } + this.openRealtimeTerminal(); + }, + + handleFocusPanelToggleClick() { + if (!this.isConnected) { + return; + } + this.toggleFocusPanel(); + }, + + handleTokenPanelToggleClick() { + if (!this.currentConversationId) { + return; + } + this.toggleTokenPanel(); + }, + + handleCompressConversationClick() { + if (this.compressing || this.streamingMessage || !this.isConnected) { + return; + } + this.compressConversation(); + }, + + autoResizeInput() { + this.$nextTick(() => { + const textarea = this.getComposerElement('stadiumInput'); + if (!textarea || !(textarea instanceof HTMLTextAreaElement)) { + return; + } + const previousHeight = textarea.offsetHeight; + textarea.style.height = 'auto'; + const computedStyle = window.getComputedStyle(textarea); + const lineHeight = parseFloat(computedStyle.lineHeight || '20') || 20; + const maxHeight = lineHeight * 6; + const targetHeight = Math.min(textarea.scrollHeight, maxHeight); + this.inputSetLineCount(Math.max(1, Math.round(targetHeight / lineHeight))); + this.inputSetMultiline(targetHeight > lineHeight * 1.4); + if (Math.abs(targetHeight - previousHeight) <= 0.5) { + textarea.style.height = `${targetHeight}px`; + return; + } + textarea.style.height = `${previousHeight}px`; + void textarea.offsetHeight; + requestAnimationFrame(() => { + textarea.style.height = `${targetHeight}px`; + }); + }); + }, + + handleClickOutsideQuickMenu(event) { + if (!this.quickMenuOpen) { + return; + } + const shell = this.getComposerElement('stadiumShellOuter') || this.getComposerElement('compactInputShell'); + if (shell && shell.contains(event.target)) { + return; + } + this.closeQuickMenu(); + }, + + handleClickOutsidePanelMenu(event) { + if (!this.panelMenuOpen) { + return; + } + const leftPanel = this.$refs.leftPanel; + const wrapper = leftPanel && leftPanel.panelMenuWrapper ? leftPanel.panelMenuWrapper : null; + if (wrapper && wrapper.contains(event.target)) { + return; + } + this.uiSetPanelMenuOpen(false); + }, + + applyToolSettingsSnapshot(categories) { + if (!Array.isArray(categories)) { + console.warn('[ToolSettings] Snapshot skipped: categories not array', categories); + return; + } + const normalized = categories.map((item) => ({ + id: item.id, + label: item.label || item.id, + enabled: !!item.enabled, + tools: Array.isArray(item.tools) ? item.tools : [] + })); + console.log('[ToolSettings] Snapshot applied', { + received: categories.length, + normalized, + anyEnabled: normalized.some(cat => cat.enabled), + toolExamples: normalized.slice(0, 3) + }); + this.toolSetSettings(normalized); + this.toolSetSettingsLoading(false); + }, + + async loadToolSettings(force = false) { + if (!this.isConnected && !force) { + console.log('[ToolSettings] Skip load: disconnected & not forced'); + return; + } + if (this.toolSettingsLoading) { + console.log('[ToolSettings] Skip load: already loading'); + return; + } + if (!force && this.toolSettings.length > 0) { + console.log('[ToolSettings] Skip load: already have settings'); + return; + } + console.log('[ToolSettings] Fetch start', { force, hasConnection: this.isConnected }); + this.toolSetSettingsLoading(true); + try { + const response = await fetch('/api/tool-settings'); + const data = await response.json(); + console.log('[ToolSettings] Fetch response', { status: response.status, data }); + if (response.ok && data.success && Array.isArray(data.categories)) { + this.applyToolSettingsSnapshot(data.categories); + } else { + console.warn('获取工具设置失败:', data); + this.toolSetSettingsLoading(false); + } + } catch (error) { + console.error('获取工具设置异常:', error); + this.toolSetSettingsLoading(false); + } + }, + + async updateToolCategory(categoryId, enabled) { + if (!this.isConnected) { + return; + } + if (this.toolSettingsLoading) { + return; + } + const previousSnapshot = this.toolSettings.map((item) => ({ ...item })); + const updatedSettings = this.toolSettings.map((item) => { + if (item.id === categoryId) { + return { ...item, enabled }; + } + return item; + }); + this.toolSetSettings(updatedSettings); + try { + const response = await fetch('/api/tool-settings', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + category: categoryId, + enabled + }) + }); + const data = await response.json(); + if (response.ok && data.success && Array.isArray(data.categories)) { + this.applyToolSettingsSnapshot(data.categories); + } else { + console.warn('更新工具设置失败:', data); + this.toolSetSettings(previousSnapshot); + } + } catch (error) { + console.error('更新工具设置异常:', error); + this.toolSetSettings(previousSnapshot); + } + this.toolSetSettingsLoading(false); + }, + + toggleSettings() { + if (!this.isConnected) { + return; + } + const nextState = this.inputToggleSettingsMenu(); + if (nextState) { + this.inputSetToolMenuOpen(false); + if (!this.quickMenuOpen) { + this.inputOpenQuickMenu(); + } + } + }, + + toggleFocusPanel() { + this.rightCollapsed = !this.rightCollapsed; + if (!this.rightCollapsed && this.rightWidth < this.minPanelWidth) { + this.rightWidth = this.minPanelWidth; + } + }, + + addSystemMessage(content) { + this.chatAddSystemMessage(content); + this.$forceUpdate(); + this.conditionalScrollToBottom(); + }, + getToolIcon, + getToolAnimationClass, + getToolStatusText, + getToolDescription, + cloneToolArguments, + buildToolLabel, + formatSearchTopic, + formatSearchTime, + getLanguageClass, + + scrollToBottom() { + scrollToBottomHelper(this); + }, + + conditionalScrollToBottom() { + conditionalScrollToBottomHelper(this); + }, + + toggleScrollLock() { + toggleScrollLockHelper(this); + }, + + scrollThinkingToBottom(blockId) { + scrollThinkingToBottomHelper(this, blockId); + }, + + // 面板调整方法 + startResize(panel, event) { + startPanelResize(this, panel, event); + }, + + handleResize(event) { + handlePanelResize(this, event); + }, + + stopResize() { + stopPanelResize(this); + }, + + async handleEasterEggPayload(payload) { + const controller = useEasterEgg(); + await controller.handlePayload(payload, this); + }, + + async startEasterEggEffect(effectName, payload = {}) { + const controller = useEasterEgg(); + await controller.startEffect(effectName, payload, this); + }, + + destroyEasterEggEffect(forceImmediate = false) { + const controller = useEasterEgg(); + return controller.destroyEffect(forceImmediate); + }, + + finishEasterEggCleanup() { + const controller = useEasterEgg(); + controller.finishCleanup(); + }, + formatTokenCount, + formatBytes, + formatPercentage, + formatRate, + quotaTypeLabel, + formatResetTime, + formatQuotaValue, + + quotaResetSummary() { + return buildQuotaResetSummary(this.usageQuota); + }, + + isQuotaExceeded(type) { + return isQuotaExceededUtil(this.usageQuota, type); + }, + + showQuotaToast(payload) { + if (!payload) { + return; + } + const type = payload.type || 'fast'; + const message = buildQuotaToastMessage(type, this.usageQuota, payload.reset_at); + this.uiShowQuotaToastMessage(message, type); + }, + + confirmAction(options = {}) { + return this.uiRequestConfirm(options); + } + } + }; + +(appOptions as any).components = { + FileNode, + ConversationSidebar, + LeftPanel, + FocusPanel, + TokenDrawer, + PersonalizationDrawer, + QuickMenu, + InputComposer, + AppShell +}; + +export default appOptions; diff --git a/static/src/stores/file.ts b/static/src/stores/file.ts new file mode 100644 index 0000000..1e084cb --- /dev/null +++ b/static/src/stores/file.ts @@ -0,0 +1,203 @@ +import { defineStore } from 'pinia'; + +interface FileNode { + type: 'folder' | 'file'; + name: string; + path: string; + annotation?: string; + children?: FileNode[]; +} + +interface TodoTask { + index: number; + title: string; + status: string; +} + +interface TodoList { + instruction?: string; + tasks?: TodoTask[]; +} + +interface ContextMenuState { + visible: boolean; + x: number; + y: number; + node: FileNode | null; +} + +interface FileState { + fileTree: FileNode[]; + expandedFolders: Record; + todoList: TodoList | null; + contextMenu: ContextMenuState; +} + +function buildNodes(treeMap: Record | undefined): FileNode[] { + if (!treeMap) { + return []; + } + const entries = Object.keys(treeMap).map(name => { + const node = treeMap[name] || {}; + if (node.type === 'folder') { + return { + type: 'folder' as const, + name, + path: node.path || name, + children: buildNodes(node.children) + }; + } + return { + type: 'file' as const, + name, + path: node.path || name, + annotation: node.annotation || '' + }; + }); + entries.sort((a, b) => { + if (a.type !== b.type) { + return a.type === 'folder' ? -1 : 1; + } + return a.name.localeCompare(b.name, 'zh-CN'); + }); + return entries; +} + +export const useFileStore = defineStore('file', { + state: (): FileState => ({ + fileTree: [], + expandedFolders: {}, + todoList: null, + contextMenu: { + visible: false, + x: 0, + y: 0, + node: null + } + }), + actions: { + async fetchFileTree() { + try { + const response = await fetch('/api/files'); + const data = await response.json(); + console.log('[FileTree] fetch result', data); + this.setFileTreeFromResponse(data); + } catch (error) { + console.error('获取文件树失败:', error); + } + }, + setFileTreeFromResponse(payload: any) { + if (!payload) { + console.warn('[FileTree] 空 payload'); + return; + } + const structure = payload.structure || payload; + if (!structure || !structure.tree) { + console.warn('[FileTree] 缺少 structure.tree', structure); + return; + } + const nodes = buildNodes(structure.tree); + const expanded = { ...this.expandedFolders }; + const validFolderPaths = new Set(); + + const ensureExpansion = (list: FileNode[]) => { + list.forEach(item => { + if (item.type === 'folder') { + validFolderPaths.add(item.path); + if (expanded[item.path] === undefined) { + expanded[item.path] = false; + } + ensureExpansion(item.children || []); + } + }); + }; + + ensureExpansion(nodes); + Object.keys(expanded).forEach(path => { + if (!validFolderPaths.has(path)) { + delete expanded[path]; + } + }); + + this.expandedFolders = expanded; + this.fileTree = nodes; + }, + toggleFolder(path: string) { + if (!path) { + return; + } + const current = !!this.expandedFolders[path]; + this.expandedFolders = { + ...this.expandedFolders, + [path]: !current + }; + }, + async fetchTodoList() { + try { + const response = await fetch('/api/todo-list'); + const data = await response.json(); + if (data && data.success) { + this.todoList = data.data || null; + } + } catch (error) { + console.error('获取待办列表失败:', error); + } + }, + setTodoList(payload: TodoList | null) { + this.todoList = payload; + }, + showContextMenu(payload: { node: FileNode; event: MouseEvent }) { + if (!payload || !payload.node) { + return; + } + const { node, event } = payload; + if (!node.path && node.path !== '') { + this.hideContextMenu(); + return; + } + if (node.type !== 'file' && node.type !== 'folder') { + this.hideContextMenu(); + return; + } + + if (event && typeof event.preventDefault === 'function') { + event.preventDefault(); + } + if (event && typeof event.stopPropagation === 'function') { + event.stopPropagation(); + } + + const menuWidth = 200; + const menuHeight = 50; + const viewportWidth = window.innerWidth; + const viewportHeight = window.innerHeight; + let x = (event && event.clientX) || 0; + let y = (event && event.clientY) || 0; + + if (x + menuWidth > viewportWidth) { + x = viewportWidth - menuWidth - 8; + } + if (y + menuHeight > viewportHeight) { + y = viewportHeight - menuHeight - 8; + } + + this.contextMenu = { + visible: true, + x: Math.max(8, x), + y: Math.max(8, y), + node + }; + }, + hideContextMenu() { + if (!this.contextMenu.visible) { + return; + } + this.contextMenu = { + visible: false, + x: 0, + y: 0, + node: null + }; + } + } +}); diff --git a/static/src/styles/components/chat/_chat-area.scss b/static/src/styles/components/chat/_chat-area.scss new file mode 100644 index 0000000..804cbb9 --- /dev/null +++ b/static/src/styles/components/chat/_chat-area.scss @@ -0,0 +1,369 @@ +/* 聊天容器整体布局,保证聊天区可见并支持上下滚动 */ +.chat-container { + flex: 1; + display: flex; + flex-direction: column; + background: rgba(255, 255, 255, 0.78); + min-width: 0; + min-height: 0; + position: relative; + backdrop-filter: blur(6px); +} + +/* 核心聊天区样式,确保对话内容在主面板中可见 */ +.messages-area { + flex: 1; + overflow-y: auto; + padding: 24px; + padding-bottom: calc(220px + var(--app-bottom-inset, 0px)); + min-height: 0; + position: relative; +} + +.messages-area::-webkit-scrollbar, +.sidebar::-webkit-scrollbar, +.conversation-list::-webkit-scrollbar { + width: 8px; +} + +.messages-area::-webkit-scrollbar-track, +.sidebar::-webkit-scrollbar-track, +.conversation-list::-webkit-scrollbar-track { + background: transparent; +} + +.messages-area::-webkit-scrollbar-thumb, +.sidebar::-webkit-scrollbar-thumb, +.conversation-list::-webkit-scrollbar-thumb { + background: rgba(121, 109, 94, 0.4); + border-radius: 8px; +} + +.scroll-lock-toggle { + position: absolute; + right: 28px; + bottom: 200px; + z-index: 25; + display: flex; + align-items: center; + justify-content: flex-end; +} + +.scroll-lock-btn { + width: 36px; + height: 36px; + border-radius: 50%; + border: 1px solid var(--claude-border); + background: rgba(255, 255, 255, 0.92); + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.2s ease; + box-shadow: 0 6px 16px rgba(61, 57, 41, 0.08); +} + +.scroll-lock-btn:hover { + transform: translateY(-2px); + box-shadow: 0 9px 20px rgba(61, 57, 41, 0.12); +} + +.scroll-lock-toggle.locked .scroll-lock-btn { + border-color: rgba(218, 119, 86, 0.32); + box-shadow: 0 0 10px rgba(218, 119, 86, 0.28); +} + +.scroll-lock-btn svg { + width: 18px; + height: 18px; + stroke: var(--claude-text); + stroke-width: 1.8; + fill: none; + transition: stroke 0.2s ease; +} + +.scroll-lock-toggle.locked .scroll-lock-btn svg { + stroke: var(--claude-accent); +} + +.message-block { + margin-bottom: 24px; +} + +.user-message .message-header, +.assistant-message .message-header { + font-size: 14px; + font-weight: 600; + color: var(--claude-text); + margin-bottom: 10px; + letter-spacing: 0.02em; +} + +.user-message .message-text, +.assistant-message .message-text { + padding: 16px 20px; + border-radius: 18px; + font-size: 15px; + line-height: 1.6; + box-shadow: 0 12px 28px rgba(61, 57, 41, 0.08); + color: var(--claude-text); + white-space: pre-wrap; +} + +.user-message .message-text { + background: rgba(255, 255, 255, 0.88); +} + +.assistant-message .message-text { + background: rgba(218, 119, 86, 0.12); + border-left: 4px solid var(--claude-accent); +} + +.thinking-content { + white-space: pre-wrap; + word-wrap: break-word; + font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; + font-size: 13px; + line-height: 1.6; + color: var(--claude-text-secondary); +} + +.collapsible-block { + background: rgba(255, 255, 255, 0.78); + border-radius: 12px; + margin-bottom: 12px; + overflow: hidden; + border: 1px solid var(--claude-border); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + box-shadow: 0 12px 28px rgba(61, 57, 41, 0.08); +} + +.collapsible-header { + padding: 14px 20px; + cursor: pointer; + display: flex; + align-items: center; + gap: 12px; + user-select: none; + background: rgba(255, 255, 255, 0.72); + transition: background-color 0.2s ease; + position: relative; +} + +.collapsible-header:hover { + background: rgba(218, 119, 86, 0.07); +} + +.arrow { + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); + color: var(--claude-text-secondary); +} + +.arrow::before { + content: '›'; + font-size: 18px; +} + +.collapsible-block.expanded .arrow { + transform: rotate(90deg); +} + +.captured-status { + display: flex; + align-items: center; + gap: 6px; + font-size: 14px; + color: var(--claude-text); +} + +.collapsible-content { + max-height: 0; + overflow: hidden; + opacity: 0; + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +.collapsible-block.expanded .collapsible-content { + max-height: 600px; + overflow-y: auto; + opacity: 1; +} + +.content-inner { + padding: 20px 20px 20px 56px; + font-size: 14px; + line-height: 1.6; + color: var(--claude-text-secondary); +} + +.action-item { + animation: slideInFade 0.6s cubic-bezier(0.4, 0, 0.2, 1) both; +} + +.action-item.streaming-content, +.action-item.immediate-show { + animation: quickFadeIn 0.2s ease-out both; +} + +.action-item.completed-tool { + animation: slideInFade 0.4s cubic-bezier(0.4, 0, 0.2, 1) both; + animation-delay: 100ms; +} + +@keyframes slideInFade { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes quickFadeIn { + from { + opacity: 0; + transform: translateY(5px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.text-output { + margin: 16px 0; + color: var(--claude-text); + font-size: 15px; + line-height: 1.7; +} + +.text-output .text-content { + padding: 0 20px 0 15px; +} + +.system-action { + margin: 12px 0; + padding: 10px 14px; + border-radius: 8px; + background: linear-gradient(135deg, rgba(99, 102, 241, 0.08), rgba(59, 130, 246, 0.08)); + border-left: 4px solid rgba(79, 70, 229, 0.6); + color: var(--claude-text); + font-size: 14px; + line-height: 1.5; +} + +.system-action-content { + display: flex; + align-items: flex-start; + gap: 8px; +} + +.append-block, +.append-placeholder { + margin: 12px 0; + padding: 12px 16px; + border-radius: 10px; + background: rgba(255, 255, 255, 0.82); + border-left: 4px solid rgba(218, 119, 86, 0.32); + box-shadow: inset 0 0 0 1px rgba(218, 119, 86, 0.05); + display: flex; + flex-direction: column; + gap: 8px; + color: var(--claude-text); +} + +.append-block.append-error, +.append-placeholder.append-error { + background: rgba(255, 244, 242, 0.85); + border-left-color: rgba(216, 90, 66, 0.38); +} + +.append-header { + display: flex; + align-items: center; + gap: 10px; + font-weight: 600; +} + +.code-block-wrapper { + border: 2px solid rgba(118, 103, 84, 0.25); + border-radius: 12px; + overflow: hidden; + margin: 16px 0; + background: rgba(255, 255, 255, 0.78); + min-height: 80px; +} + +.code-block-header { + background: rgba(218, 119, 86, 0.08); + padding: 10px 16px; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid rgba(118, 103, 84, 0.25); +} + +.code-language { + color: var(--claude-text-secondary); + font-size: 13px; + font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; + font-weight: 500; +} + +.copy-code-btn { + background: transparent; + color: var(--claude-text-secondary); + border: 1px solid rgba(121, 109, 94, 0.35); + padding: 6px 10px; + border-radius: 6px; + font-size: 16px; + cursor: pointer; + transition: all 0.2s ease; + line-height: 1; +} + +.copy-code-btn:hover { + background: rgba(218, 119, 86, 0.12); + color: var(--claude-accent-strong); +} + +.copy-code-btn.copied { + background: var(--claude-success); + border-color: var(--claude-success); + color: #f6fff8; +} + +.code-block-wrapper pre { + background: #ffffff !important; + padding: 16px !important; + margin: 0 !important; + border-radius: 0 !important; + border: none !important; +} + +.code-block-wrapper pre code { + background: transparent !important; + padding: 0 !important; + color: #000000; +} + +.streaming-text { + display: block; +} + +.cursor-blink { + animation: blink 1s steps(1) infinite; + color: var(--claude-accent); +} + +@keyframes blink { + 0%, 100% { opacity: 1; } + 50% { opacity: 0; } +} diff --git a/static/src/styles/layout/_app-shell.scss b/static/src/styles/layout/_app-shell.scss new file mode 100644 index 0000000..bf88060 --- /dev/null +++ b/static/src/styles/layout/_app-shell.scss @@ -0,0 +1,15 @@ +/* 主体容器,让三栏布局占满视口 */ +.main-container { + position: relative; + display: flex; + flex-direction: row; + min-height: var(--app-viewport, 100vh); + background: var(--claude-bg); + color: var(--claude-text); + overflow: hidden; +} + +#app { + min-height: var(--app-viewport, 100vh); + background: var(--claude-bg); +} diff --git a/static/style.css b/static/style.css index 43038fe..ffe1026 100644 --- a/static/style.css +++ b/static/style.css @@ -1,3491 +1,4 @@ -/* static/style-enhanced.css - 增强版,包含对话历史管理功能 */ - -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -:root { - --app-viewport: 100vh; - --app-bottom-inset: env(safe-area-inset-bottom, 0px); - --claude-bg: #eeece2; - --claude-panel: rgba(255, 255, 255, 0.82); - --claude-left-rail: #f7f3ea; - --claude-sidebar: rgba(255, 255, 255, 0.68); - --claude-border: rgba(118, 103, 84, 0.25); - --claude-text: #3d3929; - --claude-text-secondary: #7f7766; - --claude-muted: rgba(121, 109, 94, 0.4); - --claude-accent: #da7756; - --claude-accent-strong: #bd5d3a; - --claude-highlight: rgba(218, 119, 86, 0.14); - --claude-button-hover: #c76541; - --claude-button-active: #a95331; - --claude-shadow: 0 14px 36px rgba(61, 57, 41, 0.12); - --claude-success: #76b086; - --claude-warning: #d99845; -} - -/* 全局图标工具类 */ -.icon { - --icon-size: 1em; - width: var(--icon-size); - height: var(--icon-size); - display: inline-block; - vertical-align: middle; - background-color: currentColor; - mask-image: var(--icon-src); - mask-repeat: no-repeat; - mask-position: center; - mask-size: contain; - -webkit-mask-image: var(--icon-src); - -webkit-mask-repeat: no-repeat; - -webkit-mask-position: center; - -webkit-mask-size: contain; -} - -.icon-sm { - --icon-size: 16px; -} - -.icon-md { - --icon-size: 20px; -} - -.icon-lg { - --icon-size: 24px; -} - -.icon-xl { - --icon-size: 32px; -} - -.icon-label { - display: inline-flex; - align-items: center; - gap: 6px; -} - -html, body { - height: var(--app-viewport, 100vh); -} - -body { - font-family: 'Iowan Old Style', ui-serif, Georgia, Cambria, "Times New Roman", Times, serif; - background: var(--claude-bg); - color: var(--claude-text); - overflow: hidden; - -webkit-font-smoothing: antialiased; -} - -/* 顶部状态栏 */ -.header { - background: var(--claude-panel); - backdrop-filter: blur(24px); - border-bottom: 1px solid var(--claude-border); - padding: 12px 20px; - display: flex; - justify-content: space-between; - align-items: center; - height: 56px; - z-index: 100; - box-shadow: 0 10px 24px rgba(61, 57, 41, 0.05); -} - -.header-left { - display: flex; - align-items: center; - gap: 20px; -} - -.logo { - font-size: 18px; - font-weight: 600; - color: var(--claude-text); - letter-spacing: 0.02em; -} - -.agent-version { - color: var(--claude-text-secondary); - font-size: 16px; -} - -.header-right { - display: flex; - align-items: center; - gap: 20px; -} - -.thinking-mode { - background: var(--claude-accent); - color: #fff8f2; - padding: 5px 14px; - border-radius: 980px; - font-size: 13px; - font-weight: 500; - letter-spacing: 0.04em; -} - -.connection-status { - font-size: 13px; - color: var(--claude-text-secondary); - display: flex; - align-items: center; - gap: 6px; -} - -/* 主容器 */ -.main-container { - display: flex; - height: var(--app-viewport, 100vh); - background: var(--claude-bg); - position: relative; - align-items: stretch; -} - -/* ========================================= */ -/* 新增:对话历史侧边栏样式 */ -/* ========================================= */ - -.conversation-sidebar { - width: 280px; - background-color: var(--claude-left-rail); - border-right: none; - flex-shrink: 0; - display: flex; - flex-direction: column; - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - z-index: 50; - height: var(--app-viewport, 100vh) !important; - min-height: var(--app-viewport, 100vh) !important; - border-bottom: 1px solid var(--claude-border); -} - -.conversation-sidebar, -.conversation-sidebar .conversation-header, -.conversation-sidebar .conversation-search, -.conversation-sidebar .conversation-list, -.conversation-sidebar .conversation-personal-entry, -.conversation-sidebar .conversation-collapsed-spacer { - background-color: var(--claude-left-rail); -} - -.conversation-collapsed-spacer { - flex: 1 1 auto; - background-color: inherit; -} - -.conversation-sidebar.collapsed { - width: 50px; - overflow: hidden; - height: var(--app-viewport, 100vh) !important; - min-height: var(--app-viewport, 100vh) !important; - background-color: var(--claude-left-rail); -} - -.conversation-header { - padding: 18px 16px; - border-bottom: 1px solid rgba(118, 103, 84, 0.12); - display: flex; - justify-content: space-between; - align-items: center; - gap: 12px; - background-color: inherit; - color: var(--claude-text); - position: sticky; - top: 0; - z-index: 60; - min-height: 68px; -} - -.conversation-header.collapsed-layout { - flex-direction: column; - justify-content: flex-start; - align-items: center; - padding: 16px 6px 12px; - gap: 14px; - border-bottom: none; -} - -.collapsed-header-buttons { - display: flex; - flex-direction: column; - gap: 12px; - width: 100%; - align-items: center; -} - -.collapsed-control-btn { - width: 56px; - height: 56px; - border-radius: 28px; - border: none; - background: transparent; - color: var(--claude-text); - display: inline-flex; - align-items: center; - justify-content: center; - cursor: pointer; - padding: 0; - box-shadow: none; - transition: transform 0.2s ease, color 0.2s ease; -} - -.collapsed-control-btn:hover { - transform: translateY(-1px); - color: var(--claude-accent); -} - -.conversation-menu-btn { - color: #31271d; -} - -.conversation-menu-btn:hover { - color: var(--claude-accent); -} - -.chat-icon { - display: inline-flex; - align-items: center; - justify-content: center; -} - -.conversation-menu-btn svg { - width: 30px; - height: 30px; -} - -.quick-plus-btn { - width: 48px; - height: 48px; - border-radius: 24px; - border: none; - background: transparent; - color: #2f251b; - display: inline-flex; - align-items: center; - justify-content: center; - transition: color 0.25s ease, transform 0.2s ease; -} - -.quick-plus-btn:hover, -.quick-plus-btn:focus-visible { - color: var(--claude-accent); - transform: translateY(-1px); -} - -.pencil-icon svg { - width: 22px; - height: 22px; -} - -.pencil-icon path { - fill: currentColor; - fill-opacity: 0.92; -} - -.new-conversation-btn { - flex: 1; - background: linear-gradient(135deg, var(--claude-accent) 0%, var(--claude-accent-strong) 100%); - color: #fff8f2; - border: 1px solid rgba(189, 93, 58, 0.4); - padding: 8px 12px; - border-radius: 6px; - font-size: 13px; - font-weight: 500; - cursor: pointer; - transition: all 0.2s ease; - display: flex; - align-items: center; - gap: 6px; - box-shadow: 0 6px 14px rgba(189, 93, 58, 0.18); -} - -.new-conversation-btn:hover { - background: linear-gradient(135deg, var(--claude-button-hover) 0%, var(--claude-button-active) 100%); - transform: translateY(-1px); -} - -.btn-icon { - font-size: 16px; - font-weight: bold; -} - -.btn-text { - white-space: nowrap; -} - -.toggle-sidebar-btn { - background: rgba(255, 255, 255, 0.7); - color: var(--claude-text); - border: 1px solid rgba(118, 103, 84, 0.18); - padding: 6px 8px; - border-radius: 4px; - font-size: 14px; - cursor: pointer; - transition: all 0.2s ease; - min-width: 32px; - height: 32px; - display: flex; - align-items: center; - justify-content: center; -} - -.toggle-sidebar-btn:hover { - background: rgba(255, 255, 255, 0.9); -} - -.conversation-personal-entry { - margin-top: auto; - padding: 8px 16px 12px; - display: flex; - justify-content: center; - background-color: inherit; -} - -.conversation-personal-entry.collapsed { - padding: 8px 0 10px; -} - -.personal-page-btn { - border: none; - border-radius: 999px; - background: transparent; - color: var(--claude-text); - display: inline-flex; - align-items: center; - justify-content: center; - gap: 10px; - padding: 10px 18px; - font-size: 13px; - font-weight: 500; - cursor: pointer; - transition: color 0.2s ease, transform 0.2s ease; -} - -.conversation-personal-entry:not(.collapsed) .personal-page-btn { - width: 100%; - justify-content: flex-start; - padding: 8px 12px; - border-radius: 12px; - background: rgba(255, 255, 255, 0.4); -} - -.personal-page-btn svg { - width: 20px; - height: 20px; -} - -.personal-page-btn:hover, -.personal-page-btn:focus-visible { - color: var(--claude-accent); - transform: translateY(-1px); -} - -.personal-page-btn:focus-visible { - outline: none; - box-shadow: 0 0 0 2px rgba(218, 119, 86, 0.15); -} - -.personal-page-btn.icon-only { - width: 54px; - height: 54px; - padding: 0; - border-radius: 27px; - color: var(--claude-text); -} - -.personal-page-btn.icon-only svg { - width: 26px; - height: 26px; -} - -.personal-page-btn.icon-only:hover, -.personal-page-btn.icon-only:focus-visible { - color: var(--claude-accent); - transform: translateY(-1px); -} - -.conversation-personal-entry:not(.collapsed) .personal-page-btn:hover, -.conversation-personal-entry:not(.collapsed) .personal-page-btn:focus-visible { - background: rgba(218, 119, 86, 0.08); -} - -.personal-label { - letter-spacing: 0.02em; -} - -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - border: 0; -} - -.conversation-search { - padding: 12px; - background-color: inherit; - border-bottom: 1px solid var(--claude-border); -} - -.search-input { - width: 100%; - padding: 8px 12px; - border: 1px solid var(--claude-border); - border-radius: 6px; - font-size: 13px; - background: rgba(255, 255, 255, 0.55); - color: var(--claude-text); - transition: all 0.2s ease; -} - -.search-input:focus { - outline: none; - border-color: var(--claude-accent); - background: rgba(255, 255, 255, 0.85); - box-shadow: 0 0 0 3px rgba(218, 119, 86, 0.18); -} - -.conversation-list { - flex: 1; - overflow-y: auto; - padding: 8px 0; - background-color: inherit; -} - -.loading-conversations, -o-conversations { - text-align: center; - color: var(--claude-text-secondary); - padding: 30px 15px; - font-size: 13px; -} - -.conversation-item { - padding: 12px 16px; - margin: 2px 8px; - border-radius: 12px; - cursor: pointer; - transition: all 0.2s ease; - border: 1px solid transparent; - position: relative; - background: rgba(255, 255, 255, 0.7); - box-shadow: 0 6px 16px rgba(61, 57, 41, 0.04); -} - -.conversation-item:hover { - background: rgba(255, 255, 255, 0.85); - border-color: rgba(218, 119, 86, 0.35); - box-shadow: 0 10px 24px rgba(61, 57, 41, 0.08); -} - -.conversation-item.active { - background: rgba(218, 119, 86, 0.18); - border-color: var(--claude-accent); - box-shadow: 0 10px 28px rgba(189, 93, 58, 0.18); -} - -.conversation-title { - font-size: 14px; - font-weight: 500; - color: var(--claude-text); - margin-bottom: 6px; - word-wrap: break-word; - overflow: hidden; - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - line-height: 1.3; -} - -.conversation-meta { - font-size: 11px; - color: var(--claude-text-secondary); - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 2px; -} - -.conversation-time { - flex-shrink: 0; -} - -.conversation-counts { - text-align: right; - white-space: nowrap; - font-size: 10px; -} - -.conversation-actions { - position: absolute; - top: 50%; - right: 10px; - transform: translateY(-50%); - opacity: 0; - transition: opacity 0.2s ease; - display: flex; - flex-direction: column; - align-items: flex-end; - gap: 6px; -} - -.conversation-item:hover .conversation-actions { - opacity: 1; -} - -.conversation-action-btn { - border: none; - border-radius: 4px; - font-size: 13px; - width: 22px; - height: 22px; - display: flex; - align-items: center; - justify-content: center; - line-height: 1; - cursor: pointer; - transition: all 0.2s ease; - padding: 0; - color: white; -} - -.conversation-action-btn.copy-btn { - background: var(--claude-accent); -} - -.conversation-action-btn.copy-btn:hover { - background: var(--claude-button-hover); - transform: translateY(-1px); -} - -.conversation-action-btn.delete-btn { - background: #d85a42; -} - -.conversation-action-btn.delete-btn:hover { - background: #bf422b; - transform: translateY(-1px); -} - -.load-more { - padding: 12px 16px; - text-align: center; -} - -.load-more-btn { - background: rgba(218, 119, 86, 0.12); - color: var(--claude-accent); - border: 1px solid rgba(218, 119, 86, 0.35); - padding: 6px 14px; - border-radius: 6px; - font-size: 12px; - cursor: pointer; - transition: all 0.2s ease; -} - -.load-more-btn:hover:not(:disabled) { - background: var(--claude-accent); - color: #fffdf8; -} - -.load-more-btn:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -/* 拖拽手柄 */ -.resize-handle { - width: 4px; - background: var(--claude-left-rail); - cursor: col-resize; - position: relative; - transition: background 0.2s; - flex-shrink: 0; -} - -.resize-handle:hover { - background: rgba(218, 119, 86, 0.22); -} - -/* 侧边栏 */ -.sidebar { - background: var(--claude-left-rail); - overflow-y: auto; - flex-shrink: 0; - border-left: none; -} - -.sidebar.left-sidebar { - display: flex; - flex-direction: column; -} - -.sidebar-status { - padding: 18px 18px 8px; -} - -.compact-status-card { - background: var(--claude-panel); - border: 1px solid var(--claude-border); - border-radius: 18px; - padding: 14px 16px; - box-shadow: 0 12px 30px rgba(61, 57, 41, 0.12); - display: flex; - align-items: center; -} - -.status-line { - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - gap: 18px; -} - - .status-brand { - display: flex; - align-items: center; - gap: 12px; -} - -.status-logo { - color: var(--claude-accent); -} - -.brand-text { - display: flex; - align-items: baseline; - gap: 8px; - font-weight: 600; - color: var(--claude-text); -} - -.brand-name { - font-size: 16px; -} - -.status-indicators { - display: flex; - align-items: center; - gap: 10px; -} - -.mode-indicator { - width: 36px; - height: 36px; - border-radius: 18px; - display: inline-flex; - align-items: center; - justify-content: center; - background: var(--claude-accent); - color: #fffef8; - box-shadow: 0 8px 20px rgba(189, 93, 58, 0.25); - transition: background 0.25s ease, box-shadow 0.25s ease, transform 0.25s ease; -} - -.mode-indicator.fast { - background: #ffcc4d; - box-shadow: 0 8px 20px rgba(255, 204, 77, 0.35); -} - -.mode-indicator .icon { - --icon-size: 18px; - color: inherit; -} - -.connection-dot { - width: 12px; - height: 12px; - border-radius: 6px; - background: var(--claude-muted); - box-shadow: 0 0 0 4px rgba(121, 109, 94, 0.18); - transition: background 0.2s ease, box-shadow 0.2s ease; -} - -.connection-dot.active { - background: var(--claude-success); - box-shadow: 0 0 0 6px rgba(118, 176, 134, 0.25); -} - -.mode-icon-enter-active, -.mode-icon-leave-active { - transition: opacity 0.2s ease, transform 0.2s ease; -} - -.mode-icon-enter-from, -.mode-icon-leave-to { - opacity: 0; - transform: scale(0.5) rotate(8deg); -} - -.sidebar-header { - padding: 23px; - border-bottom: 1px solid var(--claude-border); - position: sticky; - top: 0; - background: rgba(255, 255, 255, 0.85); - z-index: 10; - backdrop-filter: blur(16px); - display: flex; - align-items: center; - gap: 10px; -} - -.sidebar-header h3 { - font-size: 15px; - font-weight: 600; - color: var(--claude-text); - margin: 0; -} - -.sidebar-manage-btn { - border: 1px solid rgba(118, 103, 84, 0.25); - background: rgba(255, 255, 255, 0.75); - color: var(--claude-text); - padding: 4px 10px; - border-radius: 6px; - font-size: 13px; - cursor: pointer; - transition: background 0.2s, border-color 0.2s; -} - -.sidebar-manage-btn:hover { - background: rgba(255, 255, 255, 0.95); - border-color: rgba(118, 103, 84, 0.45); -} - -.sidebar-view-toggle { - width: 32px; - height: 32px; - border-radius: 8px; - border: 1px solid rgba(118, 103, 84, 0.3); - background: rgba(255, 255, 255, 0.85); - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - font-size: 18px; - transition: all 0.2s ease; - color: var(--claude-text); -} - -.sidebar-view-toggle:hover { - background: rgba(255, 255, 255, 0.95); -} - -.sidebar-panel-card-wrapper { - padding: 0 18px 24px; - flex: 1 1 auto; - display: flex; - min-height: 0; -} - -.sidebar-panel-card { - background: var(--claude-panel); - border: 1px solid var(--claude-border); - border-radius: 18px; - box-shadow: 0 12px 30px rgba(61, 57, 41, 0.12); - width: 100%; - display: flex; - flex-direction: column; - overflow: hidden; - min-height: 0; -} - -.sidebar-panel-card .sidebar-header { - border-radius: 18px 18px 0 0; -} - -.sidebar-panel-content { - flex: 1 1 auto; - background: var(--claude-sidebar); - padding: 6px 12px 24px; - border-radius: 0 0 18px 18px; - border-top: 1px solid rgba(118, 103, 84, 0.12); -} - -.panel-menu-wrapper { - position: relative; - display: inline-flex; - align-items: center; -} - -.panel-menu { - position: absolute; - left: calc(100% + 8px); - top: 0; - display: flex; - gap: 6px; - background: rgba(255, 255, 255, 0.95); - border: 1px solid rgba(118, 103, 84, 0.2); - border-radius: 8px; - padding: 6px 8px; - box-shadow: 0 6px 18px rgba(61, 57, 41, 0.12); - z-index: 20; -} - -.panel-menu button { - border: none; - background: transparent; - font-size: 18px; - cursor: pointer; - padding: 4px 6px; - border-radius: 6px; -} - -.panel-menu button.active { - background: rgba(108, 92, 231, 0.1); -} - -.fade-enter-active, -.fade-leave-active { - transition: opacity 0.15s ease; -} - -.fade-enter-from, -.fade-leave-to { - opacity: 0; -} - -.sidebar.right-sidebar.collapsed { - width: 0 !important; - min-width: 0 !important; - border-left: none; - overflow: hidden; -} - -.sidebar.right-sidebar.collapsed .sidebar-header, -.sidebar.right-sidebar.collapsed .focused-files { - display: none; -} - -/* 文件树 */ -.file-tree { - padding: 12px 0 20px; - color: var(--claude-text); -} - -.todo-panel { - padding: 16px 20px 24px; - display: flex; - flex-direction: column; - gap: 12px; -} - -.sub-agent-panel { - padding: 16px 16px 24px; -} - -.sub-agent-cards { - display: flex; - flex-direction: column; - gap: 10px; -} - -.sub-agent-card { - border: 1px solid rgba(118, 103, 84, 0.2); - border-radius: 10px; - padding: 12px 14px; - background: rgba(255, 255, 255, 0.92); - cursor: pointer; - transition: border-color 0.2s ease, box-shadow 0.2s ease; -} - -.sub-agent-card:hover { - border-color: #6c5ce7; - box-shadow: 0 6px 16px rgba(108, 92, 231, 0.15); -} - -.sub-agent-header { - display: flex; - justify-content: space-between; - align-items: center; - font-weight: 600; - margin-bottom: 6px; -} - -.sub-agent-status { - text-transform: capitalize; - font-size: 12px; -} - -.sub-agent-status.running { - color: #0984e3; -} - -.sub-agent-status.completed { - color: #00b894; -} - -.sub-agent-status.failed, -.sub-agent-status.timeout { - color: #d63031; -} - -.sub-agent-summary { - font-size: 13px; - color: var(--claude-text); - margin-bottom: 4px; -} - -.sub-agent-tool { - font-size: 12px; - color: var(--claude-text-secondary); -} - -.todo-empty { - font-size: 14px; - color: var(--claude-text-secondary); - padding: 12px; - border-radius: 10px; - background: rgba(255, 255, 255, 0.8); - border: 1px dashed var(--claude-border); - text-align: center; -} - -.todo-task { - display: flex; - align-items: center; - justify-content: space-between; - padding: 10px 14px; - border: 1px solid rgba(118, 103, 84, 0.18); - border-radius: 10px; - font-size: 13px; - color: var(--claude-text); - background: rgba(255, 255, 255, 0.9); -} - -.todo-task.done { - background: rgba(92, 190, 125, 0.08); - border-color: rgba(92, 190, 125, 0.3); -} - -.todo-task-title { - flex: 1; -} - -.todo-task-status { - font-weight: 600; - font-size: 12px; - margin-left: 12px; -} - -.todo-instruction { - font-size: 12px; - color: var(--claude-text-secondary); - margin-top: 8px; -} - -.file-node-wrapper { - font-size: 14px; - color: var(--claude-text); - font-family: inherit; -} - -.folder-header { - width: 100%; - display: flex; - align-items: center; - gap: 8px; - padding: 6px 12px; - border: none; - background: transparent; - color: inherit; - cursor: pointer; - border-radius: 8px; - transition: background 0.2s ease, transform 0.2s ease; - text-align: left; - font-family: inherit; -} - -.folder-header:hover { - background: rgba(218, 119, 86, 0.12); - transform: translateX(2px); -} - -.folder-arrow { - width: 12px; - text-align: center; - color: var(--claude-text-secondary); - font-size: 12px; -} - -.folder-icon, -.file-icon { - width: 18px; - text-align: center; -} - -.folder-name, -.file-name { - flex: 1; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - font-family: inherit; -} -.folder-children { - margin-left: 14px; - padding-left: 6px; - border-left: 1px dashed rgba(0, 0, 0, 0.08); -} - -.folder-children .file-node-wrapper { - margin-left: 0; -} - -.file-node.file-leaf { - display: flex; - align-items: center; - gap: 8px; - padding: 6px 12px; - border-radius: 8px; - font-family: inherit; -} - -.file-node.file-leaf:hover { - background: rgba(218, 119, 86, 0.1); -} - -.file-node .annotation { - color: var(--claude-text-secondary); - font-size: 12px; - margin-left: 8px; -} - -.context-menu { - position: fixed; - background: #ffffff; - border: 1px solid rgba(15, 23, 42, 0.08); - border-radius: 8px; - box-shadow: 0 12px 28px rgba(15, 23, 42, 0.15); - z-index: 3000; - min-width: 180px; - padding: 6px 0; - backdrop-filter: blur(8px); -} - -.context-menu button { - width: 100%; - padding: 8px 18px; - background: transparent; - border: none; - text-align: left; - font-size: 13px; - color: #1f2933; - cursor: pointer; - transition: background 0.15s ease; -} - -.context-menu button:hover { - background: rgba(59, 130, 246, 0.12); -} - -.context-menu button:disabled { - color: #9ca3af; - cursor: not-allowed; - background: transparent; -} - -/* 聊天容器 */ -.chat-container { - flex: 1; - display: flex; - flex-direction: column; - background: rgba(255, 255, 255, 0.78); - min-width: 0; - min-height: 0; - position: relative; - backdrop-filter: blur(6px); -} - -.chat-container button { - outline: none; -} - -.chat-container button:focus, -.chat-container button:focus-visible { - outline: none; - box-shadow: none; -} - -/* 消息区域 */ -.messages-area { - flex: 1; - overflow-y: auto; - padding: 24px; - padding-top: 30px; - padding-bottom: calc(80px + var(--app-bottom-inset, 0px)); - min-height: 0; -} - -.scroll-lock-toggle { - position: absolute; - right: 28px; - bottom: 110px; - z-index: 25; - display: flex; - align-items: center; - justify-content: flex-end; -} - -.scroll-lock-btn { - width: 36px; - height: 36px; - border-radius: 50%; - border: 1px solid var(--claude-border); - background: rgba(255, 255, 255, 0.92); - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - transition: all 0.2s ease; - box-shadow: 0 6px 16px rgba(61, 57, 41, 0.08); -} - -.scroll-lock-btn:hover { - transform: translateY(-2px); - box-shadow: 0 9px 20px rgba(61, 57, 41, 0.12); -} - -.scroll-lock-toggle.locked .scroll-lock-btn { - border-color: rgba(218, 119, 86, 0.32); - box-shadow: 0 0 10px rgba(218, 119, 86, 0.28); -} - -.scroll-lock-btn svg { - width: 18px; - height: 18px; - stroke: var(--claude-text); - stroke-width: 1.8; - fill: none; - transition: stroke 0.2s ease; -} - -.scroll-lock-toggle.locked .scroll-lock-btn svg { - stroke: var(--claude-accent); -} - -/* 滚动条 */ -.messages-area::-webkit-scrollbar, -.sidebar::-webkit-scrollbar, -.conversation-list::-webkit-scrollbar { - width: 8px; -} - -.messages-area::-webkit-scrollbar-track, -.sidebar::-webkit-scrollbar-track, -.conversation-list::-webkit-scrollbar-track { - background: transparent; -} - -.messages-area::-webkit-scrollbar-thumb, -.sidebar::-webkit-scrollbar-thumb, -.conversation-list::-webkit-scrollbar-thumb { - background: rgba(121, 109, 94, 0.4); - border-radius: 8px; -} - -/* 消息块 */ -.message-block { - margin-bottom: 24px; -} - -/* 用户消息 */ -.user-message .message-header { - font-size: 14px; - font-weight: 600; - color: var(--claude-text); - margin-bottom: 8px; - letter-spacing: 0.02em; -} - -.user-message .message-text { - background: rgba(255, 255, 255, 0.85); - padding: 16px 20px; - border-radius: 18px; - color: var(--claude-text); - font-size: 15px; - line-height: 1.6; - box-shadow: 0 12px 28px rgba(61, 57, 41, 0.08); - white-space: pre-wrap; -} - -/* AI消息 */ -.assistant-message .message-header { - font-size: 14px; - font-weight: 600; - color: var(--claude-text); - margin-bottom: 12px; - letter-spacing: 0.02em; -} - -.assistant-message .message-text { - background: rgba(218, 119, 86, 0.12); - padding: 16px 20px; - border-radius: 18px; - color: var(--claude-text); - font-size: 15px; - line-height: 1.6; - box-shadow: 0 12px 28px rgba(61, 57, 41, 0.08); - border-left: 4px solid var(--claude-accent); - white-space: pre-wrap; -} - -/* Action项入场动画 */ -@keyframes slideInFade { - from { - opacity: 0; - transform: translateY(20px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -@keyframes quickFadeIn { - from { - opacity: 0; - transform: translateY(5px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.action-item { - animation: slideInFade 0.6s cubic-bezier(0.4, 0, 0.2, 1) both; - animation-delay: 0ms; -} - -/* 流式内容:立即显示 */ -.action-item.streaming-content, -.action-item.immediate-show { - animation: quickFadeIn 0.2s ease-out both; - animation-delay: 0ms !important; -} - -/* 已完成的工具:统一延迟看动画 */ -.action-item.completed-tool { - animation: slideInFade 0.4s cubic-bezier(0.4, 0, 0.2, 1) both; - animation-delay: 100ms; -} - -/* 文本输出 - 无缩进,与思考块对齐 */ -.text-output { - margin: 16px 0; - color: var(--claude-text); - font-size: 15px; - line-height: 1.7; -} - -.text-output .text-content { - padding: 0 20px 0 15px; /* 左边56px与思考块对齐 */ -} - -.text-output .text-content p { - margin-bottom: 12px; -} - -.text-output .text-content p:last-child { - margin-bottom: 0; -} - -.system-action { - margin: 12px 0; - padding: 10px 14px; - border-radius: 8px; - background: linear-gradient(135deg, rgba(99, 102, 241, 0.08), rgba(59, 130, 246, 0.08)); - border-left: 4px solid rgba(79, 70, 229, 0.6); - color: var(--claude-text); - font-size: 14px; - line-height: 1.5; -} - -.system-action-content { - display: flex; - align-items: flex-start; - gap: 8px; -} - -.append-block { - margin: 12px 0; - padding: 12px 16px; - border-radius: 10px; - background: rgba(255, 255, 255, 0.82); - border-left: 4px solid rgba(218, 119, 86, 0.32); - box-shadow: inset 0 0 0 1px rgba(218, 119, 86, 0.05); - display: flex; - flex-direction: column; - gap: 8px; -} - -.append-block.append-error { - background: rgba(255, 244, 242, 0.85); - border-left-color: rgba(216, 90, 66, 0.38); - box-shadow: inset 0 0 0 1px rgba(216, 90, 66, 0.08); -} - -.append-header { - display: flex; - align-items: center; - gap: 10px; - font-weight: 600; - color: var(--claude-text); - font-size: 15px; -} - -.append-block.append-error .append-header { - color: #b0432a; -} - -.append-icon { - font-size: 18px; -} - -.append-summary { - flex: 1; -} - -.append-meta { - display: flex; - flex-wrap: wrap; - gap: 12px; - color: var(--claude-text-secondary); - font-size: 13px; -} - -.append-meta-item { - display: inline-flex; - align-items: center; - gap: 4px; -} - -.append-status { - font-weight: 500; - color: var(--claude-text); -} - -.append-error-text { - color: #b0432a; -} - -.append-warning { - color: var(--claude-warning); - font-weight: 500; -} - -.modify-placeholder { - margin: 12px 0; -} - -.modify-placeholder-content { - background: rgba(255, 255, 255, 0.82); - border-left: 4px solid rgba(218, 119, 86, 0.32); - border-radius: 10px; - padding: 12px 16px; - display: flex; - flex-direction: column; - gap: 6px; - color: var(--claude-text); - font-size: 14px; - line-height: 1.6; -} - -.modify-placeholder-content .modify-warning { - color: var(--claude-warning); - font-weight: 500; -} - -.modify-placeholder-content .modify-meta { - display: flex; - flex-wrap: wrap; - gap: 12px; - color: var(--claude-text-secondary); - font-size: 13px; -} - -.append-placeholder { - margin: 12px 0; -} -.append-placeholder-content { - background: rgba(255, 255, 255, 0.82); - border-left: 4px solid rgba(218, 119, 86, 0.32); - border-radius: 10px; - padding: 12px 16px; - display: flex; - flex-direction: column; - gap: 6px; - color: var(--claude-text); - font-size: 14px; - line-height: 1.6; -} - -.append-placeholder-content .append-warning { - margin-top: 4px; -} - -.append-placeholder.append-error .append-placeholder-content { - background: rgba(255, 244, 242, 0.85); - border-left-color: rgba(216, 90, 66, 0.38); -} -/* Markdown表格样式 */ -.text-content table { - border-collapse: collapse; - width: 100%; - margin: 16px 0; - border: 1px solid #e0e0e0; - border-radius: 8px; - overflow: hidden; -} - -.text-content table th, -.text-content table td { - border: 1px solid #e0e0e0; - padding: 10px 14px; - text-align: left; -} - -.text-content table th { - background: rgba(218, 119, 86, 0.08); - font-weight: 600; - color: var(--claude-text); -} - -.text-content table tr:hover { - background: #fafafa; -} - -/* 代码块包装器 - 防止跳动 */ -.code-block-wrapper { - border: 2px solid rgba(118, 103, 84, 0.25); - border-radius: 12px; - overflow: hidden; - margin: 16px 0; - background: rgba(255, 255, 255, 0.78); - min-height: 80px; /* 添加最小高度防止跳动 */ - contain: layout; /* CSS containment 优化渲染 */ -} - -/* 代码块头部 */ -.code-block-header { - background: rgba(218, 119, 86, 0.08); - padding: 10px 16px; - display: flex; - justify-content: space-between; - align-items: center; - border-bottom: 1px solid rgba(118, 103, 84, 0.25); -} - -.code-language { - color: var(--claude-text-secondary); - font-size: 13px; - font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; - font-weight: 500; -} - -.copy-code-btn { - background: transparent; - color: var(--claude-text-secondary); - border: 1px solid rgba(121, 109, 94, 0.35); - padding: 0; - border-radius: 6px; - width: 36px; - height: 32px; - cursor: pointer; - transition: all 0.2s ease; - display: inline-flex; - align-items: center; - justify-content: center; -} - -.copy-code-btn::before { - content: ''; - width: 18px; - height: 18px; - background-color: currentColor; - mask-image: url('/static/icons/clipboard.svg'); - mask-repeat: no-repeat; - mask-position: center; - mask-size: contain; - -webkit-mask-image: url('/static/icons/clipboard.svg'); - -webkit-mask-repeat: no-repeat; - -webkit-mask-position: center; - -webkit-mask-size: contain; -} - -.copy-code-btn:hover { - background: rgba(218, 119, 86, 0.12); - color: var(--claude-accent-strong); -} - -.copy-code-btn.copied { - background: var(--claude-success); - border-color: var(--claude-success); - color: #f6fff8; -} - -.copy-code-btn.copied::before { - mask-image: url('/static/icons/check.svg'); - -webkit-mask-image: url('/static/icons/check.svg'); -} - -/* 代码块内容区 */ -.code-block-wrapper pre { - background: #ffffff !important; - padding: 16px !important; - margin: 0 !important; - border-radius: 0 !important; - border: none !important; -} - -.code-block-wrapper pre code { - background: transparent !important; - padding: 0 !important; - color: #000000; -} -/* 流式文本 */ -.streaming-text { - display: block; -} - -.cursor-blink { - animation: blink 1s steps(1) infinite; - color: var(--claude-accent); - font-weight: normal; -} - -/* 思考内容样式 - 保留换行 */ -.thinking-content { - white-space: pre-wrap; /* 保留换行和空格 */ - word-wrap: break-word; - font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; - font-size: 13px; - line-height: 1.6; - color: var(--claude-text-secondary); -} - -/* 可折叠块 */ -.collapsible-block { - background: rgba(255, 255, 255, 0.78); - border-radius: 12px; - margin-bottom: 12px; - overflow: hidden; - border: 1px solid var(--claude-border); - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); - box-shadow: 0 12px 28px rgba(61, 57, 41, 0.08); -} - -.collapsible-block:hover { - box-shadow: 0 14px 28px rgba(61, 57, 41, 0.1); -} - -.collapsible-header { - padding: 14px 20px; - cursor: pointer; - display: flex; - align-items: center; - gap: 12px; - user-select: none; - background: rgba(255, 255, 255, 0.72); - transition: background-color 0.2s ease; - position: relative; -} - -.collapsible-header:hover { - background: rgba(218, 119, 86, 0.07); -} - -/* 箭头 */ -.arrow { - width: 20px; - height: 20px; - display: flex; - align-items: center; - justify-content: center; - transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); - color: var(--claude-text-secondary); -} - -.arrow::before { - content: '›'; - font-size: 18px; -} - -.collapsible-block.expanded .arrow { - transform: rotate(90deg); -} - -/* 状态图标 */ -.status-icon { - width: 24px; - height: 24px; - display: flex; - align-items: center; - justify-content: center; - font-size: 18px; - color: var(--claude-text); -} - -/* 内容区域 */ -.collapsible-content { - max-height: 0; - overflow: hidden; - opacity: 0; - transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); -} - -.collapsible-block.expanded .collapsible-content { - max-height: 600px; - overflow-y: auto; - opacity: 1; -} - -.content-inner { - padding: 20px 20px 20px 56px; - color: var(--claude-text-secondary); - font-size: 14px; - line-height: 1.6; -} - -/* 工具动画 */ -@keyframes brain-pulse { - 0%, 100% { transform: scale(1); } - 50% { transform: scale(1.15); } -} - -.thinking-icon { - animation: brain-pulse 1.5s ease-in-out infinite; -} - -@keyframes file-bounce { - 0%, 100% { transform: translateY(0); } - 50% { transform: translateY(-3px); } -} - -.file-animation { - animation: file-bounce 1.5s ease-in-out infinite; -} - -@keyframes scan-effect { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.5; } -} - -.read-animation { - animation: scan-effect 1.5s ease-in-out infinite; -} - -@keyframes search-rotate { - 0% { transform: rotate(-10deg); } - 50% { transform: rotate(10deg); } - 100% { transform: rotate(-10deg); } -} - -.search-animation { - animation: search-rotate 1s ease-in-out infinite; -} - -@keyframes code-breathe { - 0%, 100% { opacity: 0.4; } - 50% { opacity: 1; } -} - -.code-animation { - animation: code-breathe 2s ease-in-out infinite; -} - -.terminal-animation::after { - content: '_'; - animation: blink 1s steps(1) infinite; - margin-left: 2px; -} - -@keyframes memory-fade { - 0%, 100% { opacity: 1; transform: scale(1); } - 50% { opacity: 0.3; transform: scale(0.95); } -} - -.memory-animation { - animation: memory-fade 2s ease-in-out infinite; -} - -@keyframes focus-glow { - 0%, 100% { filter: brightness(1); } - 50% { filter: brightness(1.3); } -} - -.focus-animation { - animation: focus-glow 1.5s ease-in-out infinite; -} - -/* 进度条 */ -.progress-indicator { - position: absolute; - bottom: 0; - left: 0; - height: 2px; - background: var(--claude-accent); - animation: progress 2s ease-in-out infinite; - opacity: 0; - transition: opacity 0.3s ease; -} - -.collapsible-block.processing .progress-indicator { - opacity: 1; -} - -@keyframes progress { - 0% { width: 0%; left: 0%; } - 50% { width: 40%; left: 30%; } - 100% { width: 0%; left: 100%; } -} - -/* 状态文字 */ -.status-text { - font-size: 14px; - color: var(--claude-text); - font-weight: 500; -} - -.processing .status-text { - color: var(--claude-text-secondary); -} - -/* 完成勾号 */ -.checkmark { - color: var(--claude-success); - font-weight: 600; - font-size: 16px; -} - -/* 工具描述 */ -.tool-desc { - color: var(--claude-text-secondary); - font-size: 12px; - margin-left: 8px; -} - -/* 输入区域 */ -.input-area { - position: absolute; - left: 0; - right: 0; - bottom: 32px; - background: transparent; - padding: 0 24px; - flex-shrink: 0; - pointer-events: none; - z-index: 30; -} - -.compact-input-area { - display: flex; - justify-content: center; - pointer-events: none; -} - -.stadium-input-wrapper { - position: relative; - width: min(900px, 94%); - pointer-events: auto; -} - -.stadium-shell { - --stadium-radius: 24px; - position: relative; - width: 100%; - min-height: calc(var(--stadium-radius) * 2.1); - padding: 12px 18px; - border-radius: var(--stadium-radius); - border: 1px solid rgba(15, 23, 42, 0.12); - background: #ffffff; - box-shadow: 0 18px 46px rgba(15, 23, 42, 0.16); - display: flex; - align-items: center; - gap: 12px; - transition: - padding 0.2s ease, - min-height 0.2s ease, - box-shadow 0.45s cubic-bezier(0.4, 0, 0.2, 1), - border-color 0.45s cubic-bezier(0.4, 0, 0.2, 1); -} - -.stadium-shell.is-multiline { - padding-top: 16px; - padding-bottom: 16px; - min-height: calc(var(--stadium-radius) * 2.7); - border-color: rgba(15, 23, 42, 0.2); - box-shadow: 0 26px 70px rgba(15, 23, 42, 0.22); -} - -.stadium-shell.is-focused, -.stadium-shell.has-text { - border-color: rgba(218, 119, 86, 0.32); - box-shadow: - 0 2px 22px rgba(218, 119, 86, 0.18), - 0 0 30px rgba(218, 119, 86, 0.16), - 0 22px 60px rgba(15, 23, 42, 0.22); -} - -.stadium-shell.is-multiline.is-focused, -.stadium-shell.is-multiline.has-text { - box-shadow: - 0 2px 28px rgba(218, 119, 86, 0.2), - 0 0 36px rgba(218, 119, 86, 0.2), - 0 32px 86px rgba(15, 23, 42, 0.28); -} - -.stadium-input { - flex: 1 1 auto; - width: 100%; - border: none; - resize: none; - background: transparent; - font-size: 14px; - line-height: 1.4; - font-family: inherit; - color: var(--claude-text); - padding: 0; - min-height: 20px; - outline: none; - overflow-y: auto; - scrollbar-width: none; - transition: height 0.28s cubic-bezier(0.4, 0, 0.2, 1); - will-change: height; -} - -.stadium-input:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.stadium-input::-webkit-scrollbar { - width: 0; - height: 0; -} - -.stadium-btn { - flex: 0 0 36px; - width: 36px; - height: 36px; - border: none; - border-radius: 50%; - background: transparent; - color: var(--claude-text); - font-size: 18px; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: background 0.2s ease, transform 0.2s ease, margin-top 0.2s ease; -} - -.stadium-btn:disabled { - opacity: 0.4; - cursor: not-allowed; -} - -.stadium-btn:hover:not(:disabled) { - background: rgba(0, 0, 0, 0.05); -} - -.add-btn { - font-size: 22px; -} - -.stadium-btn.send-btn { - background: var(--claude-accent); - color: #fffaf0; - box-shadow: 0 10px 20px rgba(189, 93, 58, 0.28); -} - -.stadium-btn.send-btn:hover:not(:disabled) { - transform: translateY(-1px); - background: var(--claude-button-hover); -} - -.stadium-btn.send-btn:disabled { - opacity: 0.4; - box-shadow: none; -} - -.stadium-btn.send-btn span { - position: relative; - top: 0px; -} - -.stadium-btn.send-btn .send-icon { - width: 0; - height: 0; - border-top: 6px solid transparent; - border-bottom: 6px solid transparent; - border-left: 10px solid #fffaf0; - margin-left: 5px; -} - -.stadium-btn.send-btn .stop-icon { - width: 12px; - height: 12px; - border-radius: 2px; - background-color: #fffaf0; - display: block; -} - -.stadium-btn.send-btn:disabled .send-icon { - border-left-color: rgba(255, 255, 255, 0.4); -} - -.stadium-btn.send-btn:disabled .stop-icon { - background-color: rgba(255, 255, 255, 0.4); -} - -.stadium-shell.is-multiline .stadium-btn { - align-self: flex-end; - margin-top: 0; -} - -.file-input-hidden { - display: none; -} - -.quick-menu { - position: absolute; - left: 0; - bottom: calc(100% + 14px); - display: flex; - flex-direction: column; - gap: 6px; - width: 230px; - padding: 12px; - background: rgba(255, 255, 255, 0.98); - border: 1px solid var(--claude-border); - border-radius: 18px; - box-shadow: var(--claude-shadow); - z-index: 30; - pointer-events: auto; -} - -.menu-entry { - border: none; - background: transparent; - padding: 10px 12px; - border-radius: 12px; - font-size: 14px; - text-align: left; - color: var(--claude-text); - display: flex; - align-items: center; - justify-content: space-between; - cursor: pointer; - transition: background 0.15s ease; - min-height: 44px; -} - -.menu-entry:hover:not(:disabled) { - background: rgba(0, 0, 0, 0.05); -} - -.menu-entry:disabled { - opacity: 0.45; - cursor: not-allowed; -} - -.menu-entry.has-submenu .entry-arrow { - margin-left: 10px; - color: var(--claude-text-secondary); -} - -.quick-submenu { - position: absolute; - top: 0; - left: calc(100% + 12px); - width: 230px; - min-width: 0; - padding: 12px; - border-radius: 18px; - border: 1px solid var(--claude-border); - background: rgba(255, 255, 255, 0.98); - box-shadow: var(--claude-shadow); - z-index: 31; -} - -.submenu-status, -.submenu-empty { - font-size: 13px; - color: var(--claude-text-secondary); -} - -.submenu-list { - display: flex; - flex-direction: column; - gap: 6px; -} - -.quick-submenu.tool-submenu { - top: auto; - bottom: 0; -} - -.menu-entry.submenu-entry { - width: 100%; - justify-content: space-between; -} - -.menu-entry.submenu-entry .entry-arrow { - color: var(--claude-text-secondary); -} - -.menu-entry.disabled { - opacity: 0.5; -} - -.submenu-label { - display: inline-flex; - align-items: center; - gap: 8px; -} - -.quick-menu-enter-active, -.quick-menu-leave-active { - transition: opacity 0.2s ease, transform 0.2s ease; -} - -.quick-menu-enter-from, -.quick-menu-leave-to { - opacity: 0; - transform: translateY(8px); -} - -.submenu-slide-enter-active, -.submenu-slide-leave-active { - transition: opacity 0.2s ease, transform 0.2s ease; -} - -.submenu-slide-enter-from, -.submenu-slide-leave-to { - opacity: 0; - transform: translateX(10px); -} - -/* 适应折叠屏/矮屏幕,保证输入区完整可见 */ -@media (max-height: 900px) { - .input-area { - bottom: 12px; - padding: 0 12px; - } - .stadium-shell { - width: min(900px, 98%); - } -} - -.settings-menu { - position: absolute; - right: 0; - bottom: calc(100% + 12px); - background: rgba(255, 255, 255, 0.96); - border: 1px solid var(--claude-border); - border-radius: 12px; - box-shadow: var(--claude-shadow); - padding: 12px; - display: flex; - flex-direction: column; - gap: 8px; - min-width: 150px; - z-index: 40; -} - -.settings-menu::before { - content: ''; - position: absolute; - bottom: -10px; - right: 20px; - border-width: 10px 10px 0 10px; - border-style: solid; - border-color: rgba(255, 255, 255, 0.96) transparent transparent transparent; - filter: drop-shadow(0 3px 4px rgba(61, 57, 41, 0.12)); -} - -.settings-menu.tool-menu { - right: auto; - left: 0; - min-width: 520px; - max-width: 580px; - padding: 16px 20px; -} - -.settings-menu.tool-menu::before { - left: 32px; - right: auto; -} - -.tool-menu .tool-menu-status, -.tool-menu .tool-menu-empty { - font-size: 13px; - color: rgba(61, 57, 41, 0.78); - text-align: left; -} - -.tool-menu .tool-menu-list { - display: grid; - grid-template-columns: repeat(4, minmax(110px, 1fr)); - gap: 12px; -} - -.tool-menu .tool-category-item { - display: flex; - flex-direction: column; - align-items: center; - gap: 10px; - padding: 14px 10px; - border: 1px solid rgba(118, 103, 84, 0.14); - border-radius: 10px; - background: rgba(255, 255, 255, 0.88); - font-size: 13px; - aspect-ratio: 1 / 1; - min-height: 0; - justify-content: space-between; -} - -.tool-menu .tool-category-item.disabled { - opacity: 0.55; -} - -.tool-menu .tool-category-label { - flex: 1; - font-size: 13px; - font-weight: 500; - color: var(--claude-text); - display: inline-flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 6px; - text-align: center; - white-space: nowrap; - line-height: 1.4; -} - -.tool-category-icon { - font-size: 20px; -} - -.tool-menu .tool-category-toggle { - width: 100% !important; - display: inline-flex; - align-items: center; - justify-content: center; - padding: 6px 12px; - text-align: center; - white-space: nowrap; - margin-top: auto; -} - -.menu-btn { - width: 100%; - padding: 8px 14px; - border: 1px solid rgba(118, 103, 84, 0.14); - border-radius: 8px; - font-size: 13px; - font-weight: 500; - text-align: left; - background: rgba(255, 255, 255, 0.78); - color: var(--claude-text); - cursor: pointer; - transition: all 0.2s ease; -} - -.menu-btn:not(:disabled):hover { - background: rgba(255, 255, 255, 0.95); - transform: translateY(-1px); -} - -.menu-btn:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.menu-btn.compress-entry { - background: rgba(255, 255, 255, 0.78); - color: var(--claude-success); -} - -.menu-btn.compress-entry:not(:disabled):hover { - background: rgba(255, 255, 255, 0.95); -} - -.menu-btn.clear-entry { - background: rgba(255, 255, 255, 0.78); - color: #bf422b; -} - -.menu-btn.clear-entry:not(:disabled):hover { - background: rgba(255, 255, 255, 0.95); -} - -.settings-menu-enter-active, -.settings-menu-leave-active { - transition: opacity 0.18s ease, transform 0.18s ease; -} - -.settings-menu-enter-from, -.settings-menu-leave-to { - opacity: 0; - transform: translateY(6px); -} - -/* 聚焦文件 */ -.focused-files { - padding: 16px; -} - -o-files { - text-align: center; - color: var(--claude-text-secondary); - padding: 60px 20px; - font-size: 14px; -} - -.file-tabs { - display: flex; - flex-direction: column; - gap: 12px; -} - -.file-tab { - border: 1px solid var(--claude-border); - border-radius: 12px; - overflow: hidden; - background: rgba(255, 255, 255, 0.75); - box-shadow: 0 12px 28px rgba(61, 57, 41, 0.08); -} - -.tab-header { - background: rgba(218, 119, 86, 0.08); - padding: 10px 16px; - display: flex; - justify-content: space-between; - align-items: center; - font-size: 14px; - border-bottom: 1px solid var(--claude-border); -} - -.file-name { - font-weight: 500; - color: var(--claude-text); -} - -.file-size { - color: var(--claude-text-secondary); - font-size: 12px; -} - -.file-content { - max-height: 320px; - overflow-y: auto; - background: #1e1e1e; -} - -.file-content pre { - margin: 0; - padding: 16px; -} - -.file-content code { - font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; - font-size: 13px; - line-height: 1.5; - color: #aed581; -} - -/* 系统消息 */ -.system-message .collapsible-block { - margin: 0; -} - -/* Markdown样式 */ -.text-content h1, -.text-content h2, -.text-content h3 { - margin-top: 20px; - margin-bottom: 12px; - font-weight: 600; - color: var(--claude-text); -} - -.text-content p { - margin-bottom: 12px; -} - -.text-content pre { - background: rgba(255, 255, 255, 0.78); - padding: 16px; - border-radius: 12px; - overflow-x: auto; - margin: 16px 0; - border: 1px solid var(--claude-border); - box-shadow: 0 10px 24px rgba(61, 57, 41, 0.08); -} - -.text-content code { - background: rgba(218, 119, 86, 0.1); - padding: 2px 6px; - border-radius: 4px; - font-size: 14px; - font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; - color: var(--claude-accent-strong); -} - -@keyframes blink { - 0%, 50% { opacity: 1; } - 51%, 100% { opacity: 0; } -} -/* ========================================= */ -/* 响应式设计 */ -/* ========================================= */ - -@media (max-width: 1200px) { - .conversation-sidebar { - width: 260px; - } - - .left-sidebar, - .right-sidebar { - width: 300px !important; - } -} - -@media (max-width: 768px) { - .conversation-sidebar { - position: absolute; - left: 0; - top: 0; - height: 100%; - z-index: 1000; - box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1); - transform: translateX(-100%); - } - - .conversation-sidebar:not(.collapsed) { - transform: translateX(0); - } - - .left-sidebar, - .right-sidebar { - display: none; - } - - .resize-handle { - display: none; - } -} -.token-count { - color: var(--claude-accent); - font-weight: 500; -} -/* 顶部 Token 抽屉 */ -.token-drawer { - position: absolute; - top: 20px; - left: 0; - right: 0; - width: 100%; - display: flex; - justify-content: center; - align-items: flex-start; - z-index: 20; - pointer-events: none; -} - -.token-display-panel { - width: min(860px, 92%); - background: var(--claude-panel); - border: 1px solid var(--claude-border); - border-radius: 26px; - box-shadow: 0 24px 42px rgba(61, 57, 41, 0.18); - transition: transform 0.35s ease, opacity 0.35s ease; - pointer-events: auto; -} - -.token-drawer.collapsed .token-display-panel { - transform: translateY(-120%); - opacity: 0; - pointer-events: none; -} - -.token-panel-content { - padding: 16px 36px; -} - -.token-panel-grid { - display: grid; - gap: 24px; -} - -.usage-dashboard { - position: relative; - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - grid-auto-rows: auto; - column-gap: 48px; - row-gap: 32px; - padding: 8px 0; -} - -.usage-dashboard::before { - content: ''; - position: absolute; - top: 0; - bottom: 0; - left: 50%; - width: 1px; - background: var(--claude-border); - transform: translateX(-0.5px); - pointer-events: none; - z-index: 0; -} - -.usage-cell { - display: flex; - flex-direction: column; - gap: 16px; - position: relative; - z-index: 1; -} - -.usage-dashboard .panel-card { - border-left: none; - padding: 0; -} - -.usage-cell--left { - padding-right: 24px; -} - -.usage-cell--right { - padding-left: 24px; -} - -.usage-title { - display: flex; - justify-content: space-between; - align-items: center; - font-size: 14px; - font-weight: 600; - color: var(--claude-text); -} - -.stat-grid { - display: grid; - gap: 12px; -} - -.stat-grid--triple { - grid-template-columns: repeat(3, minmax(0, 1fr)); -} - -.stat-grid--double { - grid-template-columns: repeat(2, minmax(0, 1fr)); -} - -.stat-block { - display: flex; - flex-direction: column; - gap: 6px; -} - -.stat-label { - color: var(--claude-text-secondary); - font-size: 12px; - letter-spacing: 0.04em; -} - -.stat-value { - font-weight: 600; - font-size: 18px; - font-variant-numeric: tabular-nums; - color: var(--claude-text); -} - -.stat-value--accent { - color: var(--claude-accent); - font-size: 20px; -} - -.stat-value--success { - color: var(--claude-success); -} - -.stat-value--warning { - color: var(--claude-warning); -} - -.stat-value--mono { - font-family: 'SF Mono', 'Monaco', 'Consolas', monospace; - font-size: 16px; -} - -.stat-meta, -.stat-foot { - font-size: 12px; - color: var(--claude-text-secondary); -} - -.stat-meta { - text-transform: none; -} - -.stat-foot { - margin-top: 2px; -} - -.usage-placeholder { - font-size: 13px; - color: var(--claude-text-secondary); -} - -.panel-row { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(360px, 1fr)); - gap: 24px; - align-items: stretch; -} - -.token-card { - flex: 1 1 320px; -} - -.token-card.compact, -.container-stats-card { - display: flex; - flex-direction: column; - height: 100%; - justify-content: space-between; -} - -.token-stats { - display: flex; - justify-content: space-between; - gap: 32px; - flex-wrap: wrap; -} - -.token-stats.inline-row { - flex-wrap: nowrap; - gap: 24px; - align-items: flex-start; -} - -.token-stats.inline-row > .token-item { - flex: 1 1 0; - min-width: 0; -} - -.quota-inline { - display: flex; - gap: 24px; - flex-wrap: nowrap; - justify-content: space-between; - align-items: flex-end; -} - -.quota-inline-item { - flex: 1 1 0; - min-width: 0; -} - -.usage-quota-card { - margin-top: 16px; -} - -.quota-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); - gap: 12px; -} - -.quota-label { - font-size: 12px; - color: var(--claude-text-secondary); - font-weight: 500; -} - -.quota-value { - font-weight: 600; - font-size: 16px; - color: var(--claude-text); -} - -.quota-reset { - font-size: 12px; - color: var(--claude-text-secondary); -} - -.token-item { - display: flex; - flex-direction: column; - gap: 4px; - min-width: 120px; -} - -.token-label { - color: var(--claude-text-secondary); - font-size: 11px; - font-weight: 500; - text-transform: uppercase; - letter-spacing: 0.04em; -} - -.token-value { - color: var(--claude-text); - font-weight: 600; - font-size: 18px; - font-variant-numeric: tabular-nums; -} - -.token-value.current { - color: var(--claude-accent); - font-size: 20px; -} - -.token-value.input { color: var(--claude-success); } -.token-value.output { color: var(--claude-warning); } - -.panel-row-title { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 12px; - font-size: 12px; - color: var(--claude-text-secondary); - text-transform: uppercase; - letter-spacing: 0.04em; -} - -.double-metric { - display: flex; - gap: 24px; -} - -.status-pill { - padding: 2px 10px; - border-radius: 999px; - font-size: 11px; - font-weight: 600; - background: rgba(125, 109, 94, 0.15); - color: var(--claude-text-secondary); -} - -.quota-toast { - position: fixed; - top: 24px; - right: 32px; - padding: 12px 18px; - border-radius: 12px; - background: var(--claude-bg); - border: 1px solid var(--claude-border-strong); - box-shadow: 0 12px 30px rgba(0, 0, 0, 0.18); - z-index: 2000; - max-width: 320px; - color: var(--claude-text); - font-weight: 500; - display: flex; - align-items: center; - gap: 12px; -} - -.quota-toast-label { - display: block; - flex: 1; -} - -.quota-toast-fade-enter-active, -.quota-toast-fade-leave-active { - transition: opacity 0.2s ease, transform 0.2s ease; -} - -.quota-toast-fade-enter-from, -.quota-toast-fade-leave-to { - opacity: 0; - transform: translateX(20px); -} - -.toast-stack { - position: fixed; - top: 96px; - right: 32px; - width: min(320px, calc(100vw - 32px)); - display: flex; - flex-direction: column; - gap: 12px; - z-index: 2050; -} - -.toast-stack.toast-stack--empty { - pointer-events: none; -} - -.app-toast { - display: flex; - align-items: flex-start; - gap: 12px; - padding: 12px 14px; - border-radius: 14px; - background: var(--claude-bg); - border: 1px solid var(--claude-border-strong, rgba(118,103,84,0.35)); - box-shadow: 0 12px 30px rgba(0, 0, 0, 0.18); - color: var(--claude-text); -} - -.app-toast::before { - content: ""; - width: 4px; - border-radius: 999px; - background: var(--claude-text-secondary); - align-self: stretch; -} - -.app-toast--success::before { - background: var(--claude-success); -} - -.app-toast--warning::before { - background: var(--claude-warning); -} - -.app-toast--error::before { - background: #d45f5f; -} - -.app-toast-body { - flex: 1; - display: flex; - flex-direction: column; - gap: 4px; -} - -.app-toast-title { - font-weight: 600; -} - -.app-toast-message { - font-size: 13px; - line-height: 1.4; -} - -.toast-close { - border: none; - background: transparent; - color: var(--claude-text-secondary); - font-size: 16px; - cursor: pointer; - padding: 2px; -} - -.toast-close:hover { - color: var(--claude-text); -} - -.confirm-overlay { - position: fixed; - inset: 0; - background: rgba(8, 8, 8, 0.45); - z-index: 2200; - display: flex; - align-items: center; - justify-content: center; - padding: 20px; -} - -.confirm-modal { - width: min(360px, 100%); - background: var(--claude-bg); - border: 1px solid var(--claude-border-strong, rgba(118,103,84,0.35)); - border-radius: 18px; - padding: 24px; - box-shadow: 0 18px 40px rgba(61, 57, 41, 0.22); -} - -.confirm-title { - font-size: 16px; - font-weight: 600; - margin-bottom: 12px; -} - -.confirm-message { - font-size: 14px; - color: var(--claude-text-secondary); -} - -.confirm-actions { - display: flex; - justify-content: flex-end; - gap: 12px; - margin-top: 24px; -} - -.confirm-button { - border: 1px solid var(--claude-border-strong, rgba(118,103,84,0.35)); - background: transparent; - color: var(--claude-text); - border-radius: 10px; - padding: 8px 20px; - font-weight: 500; - cursor: pointer; -} - -.confirm-button--primary { - background: var(--claude-accent); - border-color: var(--claude-accent-strong); - color: #fff; -} - -.status-pill--running { - background: rgba(118, 176, 134, 0.18); - color: var(--claude-success); -} - -.status-pill--stopped { - background: rgba(217, 152, 69, 0.2); - color: var(--claude-warning); -} - -.status-pill--host { - background: rgba(125, 109, 94, 0.12); - color: var(--claude-text-secondary); -} - -.container-metric-grid { - display: grid; - grid-template-columns: repeat(2, minmax(150px, 1fr)); - gap: 16px 24px; -} - -.container-metric { - display: flex; - flex-direction: column; - gap: 4px; -} - -.metric-label { - color: var(--claude-text-secondary); - font-size: 11px; - letter-spacing: 0.04em; - text-transform: uppercase; -} - -.metric-value { - font-weight: 600; - color: var(--claude-text); - font-size: 16px; - font-variant-numeric: tabular-nums; -} - -.metric-subtext { - color: var(--claude-text-secondary); - font-size: 12px; -} - -.container-empty { - color: var(--claude-text-secondary); - font-size: 13px; - padding: 12px 0; -} - -@media (max-width: 900px) { - .token-panel-layout { - flex-direction: column; - gap: 20px; - } - - .container-stats-card { - border-left: none; - border-top: 1px solid var(--claude-border); - padding-left: 0; - padding-top: 16px; - } - - .token-stats { - flex-direction: column; - gap: 16px; - } - - .usage-dashboard { - grid-template-columns: 1fr; - row-gap: 20px; - padding: 0; - } - - .usage-dashboard::before { - display: none; - } - - .usage-cell--left, - .usage-cell--right { - padding: 0; - } - - .stat-grid--triple { - grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); - } - - .token-item { - min-width: auto; - } - - .token-panel-content { - padding: 18px 24px; - } -} - -/* Markdown列表样式 - 修复偏左问题 */ -.text-content ul, -.text-content ol { - margin-left: 24px; /* 增加左边距 */ - padding-left: 0; - margin-bottom: 12px; -} - -.text-content ul { - list-style-type: disc; /* 实心圆点 */ -} - -.text-content ul ul { - list-style-type: circle; /* 空心圆点 */ - margin-top: 6px; -} - -.text-content ol { - list-style-type: decimal; /* 数字列表 */ -} - -.text-content li { - margin-bottom: 6px; - line-height: 1.6; -} - -/* 搜索结果展示 */ -.search-meta { - font-size: 14px; - color: var(--claude-text-secondary); - line-height: 1.6; - margin-bottom: 12px; - word-break: break-word; -} - -.search-result-list { - display: flex; - flex-direction: column; - gap: 10px; -} - -.search-result-item { - padding: 10px 12px; - border: 1px solid rgba(118, 103, 84, 0.16); - border-radius: 8px; - background: rgba(255, 255, 255, 0.65); - word-break: break-word; -} - -.search-result-title { - font-weight: 600; - margin-bottom: 6px; - color: var(--claude-text); -} - -.search-result-url a { - color: var(--claude-accent-strong); - text-decoration: none; - word-break: break-all; -} - -.search-result-url a:hover { - text-decoration: underline; -} - -.search-empty { - font-size: 13px; - color: var(--claude-text-secondary); - font-style: italic; -} - -.personal-page-overlay { - position: fixed; - inset: 0; - background: rgba(33, 24, 14, 0.55); - backdrop-filter: blur(12px); - display: flex; - align-items: center; - justify-content: center; - z-index: 400; - padding: 20px; - transition: opacity 0.25s ease, backdrop-filter 0.25s ease; - will-change: opacity, backdrop-filter; -} - -.personal-page-card { - width: min(95vw, 860px); - background: #fffaf4; - border-radius: 24px; - border: 1px solid rgba(118, 103, 84, 0.25); - box-shadow: 0 28px 60px rgba(38, 28, 18, 0.25); - padding: 40px; - text-align: left; - color: var(--claude-text); - max-height: calc(100vh - 40px); - overflow: hidden; -} - -.personal-page-header { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 16px; - margin-bottom: 24px; -} - -.personal-page-actions { - display: flex; - gap: 10px; - align-items: flex-start; -} - -.personal-page-header h2 { - font-size: 24px; - margin: 0; -} - -.personal-page-header p { - margin: 6px 0 0; - color: var(--claude-text-secondary); - font-size: 14px; -} - -.personal-page-close { - align-self: flex-start; - padding: 8px 18px; - border-radius: 999px; - border: none; - background: linear-gradient(135deg, var(--claude-accent) 0%, var(--claude-accent-strong) 100%); - color: #fffdf8; - font-size: 13px; - font-weight: 600; - cursor: pointer; - box-shadow: 0 14px 28px rgba(189, 93, 58, 0.2); - transition: transform 0.2s ease, box-shadow 0.2s ease; -} - -.personal-page-close:hover { - transform: translateY(-1px); - box-shadow: 0 18px 34px rgba(189, 93, 58, 0.3); -} - -.personal-page-logout { - align-self: flex-start; - padding: 8px 16px; - border-radius: 999px; - border: 1px solid rgba(118, 103, 84, 0.35); - background: rgba(255, 255, 255, 0.92); - color: var(--claude-text); - font-size: 13px; - font-weight: 600; - cursor: pointer; - transition: background 0.2s ease, box-shadow 0.2s ease; -} - -.personal-page-logout:hover { - background: #fff; - box-shadow: 0 12px 24px rgba(38, 28, 18, 0.12); -} - -.personalization-body { - display: flex; - flex-direction: column; - gap: 20px; - overflow-y: auto; - max-height: calc(100vh - 180px); - padding-right: 6px; - scrollbar-width: none; - -ms-overflow-style: none; -} - -.personalization-body::-webkit-scrollbar { - display: none; -} - -.personal-toggle { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - padding: 16px 18px; - border-radius: 16px; - background: rgba(255, 255, 255, 0.85); - border: 1px solid rgba(118, 103, 84, 0.2); -} - -.toggle-text { - display: flex; - flex-direction: column; - gap: 6px; -} - -.toggle-title { - font-weight: 600; -} - -.toggle-desc { - color: var(--claude-text-secondary); - font-size: 13px; -} - -.toggle-switch { - position: relative; - width: 46px; - height: 26px; -} - -.toggle-switch input { - opacity: 0; - width: 0; - height: 0; -} - -.switch-slider { - position: absolute; - inset: 0; - background-color: #d7d1c5; - border-radius: 30px; - transition: background-color 0.2s ease; -} - -.switch-slider::before { - content: ""; - position: absolute; - left: 4px; - top: 4px; - width: 18px; - height: 18px; - border-radius: 50%; - background: #fff; - transition: transform 0.2s ease; -} - -.toggle-switch input:checked + .switch-slider { - background: var(--claude-accent); -} - -.toggle-switch input:checked + .switch-slider::before { - transform: translateX(20px); -} - -.personal-form { - display: flex; - flex-direction: column; - gap: 18px; - flex: 1; -} - -.personalization-sections { - display: flex; - gap: 18px; - align-items: flex-start; - flex-wrap: nowrap; -} - -.personal-section { - flex: 1 1 0; - min-width: 240px; - background: rgba(255, 255, 255, 0.9); - border: 1px solid rgba(118, 103, 84, 0.25); - border-radius: 18px; - padding: 16px 18px; - display: flex; - flex-direction: column; - gap: 16px; -} - -.personal-right-column { - display: flex; - flex-direction: column; - gap: 14px; - flex: 1 1 0; - max-width: none; - align-self: stretch; -} - -.personal-right-column .personal-section { - flex: 1 1 auto; -} - -.personal-section.personal-considerations .personal-field { - flex: 1; - display: flex; - flex-direction: column; -} - -.personal-section.personal-considerations .consideration-list { - max-height: 260px; - min-height: 220px; - flex: 1; - overflow-y: auto; - scrollbar-width: none; - -ms-overflow-style: none; -} - -.personal-section.personal-considerations .consideration-list::-webkit-scrollbar { - display: none; -} - -.personal-section.personal-considerations .consideration-item { - background: rgba(255, 255, 255, 0.95); -} - -@media (max-width: 1024px) { - .personalization-sections { - flex-direction: column; - flex-wrap: wrap; - } - - .personal-right-column { - width: 100%; - max-width: none; - } -} - -.personal-field { - display: flex; - flex-direction: column; - gap: 10px; - font-size: 14px; -} - -.personal-field input { - width: 100%; - padding: 10px 14px; - border-radius: 12px; - border: 1px solid rgba(118, 103, 84, 0.4); - background: rgba(255, 255, 255, 0.9); - font-size: 14px; -} - -.personal-field input:focus { - outline: 2px solid rgba(189, 93, 58, 0.35); - border-color: rgba(189, 93, 58, 0.6); -} - -.tone-preset-row { - display: flex; - align-items: center; - flex-wrap: wrap; - gap: 10px; - font-size: 13px; - color: var(--claude-text-secondary); -} - -.tone-preset-buttons { - display: flex; - flex-wrap: wrap; - gap: 6px; -} - -.tone-preset-buttons button { - padding: 4px 10px; - border-radius: 999px; - border: 1px solid rgba(118, 103, 84, 0.4); - background: #fff; - font-size: 13px; - cursor: pointer; -} - -.tone-preset-buttons button:hover { - border-color: var(--claude-accent); - color: var(--claude-accent); -} - -.consideration-input { - display: flex; - gap: 8px; - align-items: center; -} - -.consideration-input input { - flex: 1; -} - -.consideration-add { - width: 38px; - height: 38px; - border-radius: 12px; - border: none; - background: var(--claude-accent); - color: #fffdf8; - font-size: 20px; - font-weight: 600; - cursor: pointer; -} - -.consideration-add:disabled { - opacity: 0.4; - cursor: not-allowed; -} - -.consideration-list { - list-style: none; - margin: 6px 0 0; - padding: 0; - display: flex; - flex-direction: column; - gap: 8px; -} - -.consideration-item { - display: flex; - align-items: center; - gap: 10px; - padding: 10px 12px; - border-radius: 12px; - border: 1px dashed rgba(118, 103, 84, 0.5); - background: rgba(255, 255, 255, 0.9); - cursor: grab; -} - -.drag-handle { - font-size: 16px; - color: var(--claude-text-secondary); -} - -.consideration-text { - flex: 1; - font-size: 14px; - color: var(--claude-text); -} - -.consideration-remove { - border: none; - background: none; - font-size: 18px; - line-height: 1; - cursor: pointer; - color: #d64545; - padding: 0 4px; -} - -.consideration-hint, -.consideration-limit { - font-size: 13px; - color: var(--claude-text-secondary); -} - -.personal-form-actions { - display: flex; - align-items: center; - gap: 8px; - margin-top: auto; - padding: 12px 0 0; - justify-content: flex-end; -} - -.personal-status-group { - flex: 0 0 auto; - display: flex; - align-items: center; - gap: 4px; - min-height: 22px; -} - -.personal-form-actions .primary { - padding: 10px 20px; - border: none; - border-radius: 999px; - background: linear-gradient(135deg, var(--claude-accent) 0%, var(--claude-accent-strong) 100%); - color: #fff; - font-weight: 600; - cursor: pointer; -} - -.personal-form-actions .primary:disabled { - opacity: 0.6; - cursor: not-allowed; -} - -.personal-form-actions .status { - font-size: 13px; -} - -.personal-form-actions .status.success { - color: #0f9d58; -} - -.personal-form-actions .status.error { - color: #d64545; -} - -.personal-status-fade-enter-active, -.personal-status-fade-leave-active { - transition: opacity 0.25s ease; -} - -.personal-status-fade-enter-from, -.personal-status-fade-leave-to { - opacity: 0; -} - -.personalization-loading { - padding: 32px 0; - text-align: center; - color: var(--claude-text-secondary); - font-size: 14px; -} -} - -.personal-page-fade-enter-active, -.personal-page-fade-leave-active { - transition: opacity 0.25s ease, backdrop-filter 0.25s ease; -} - -.personal-page-fade-enter-from, -.personal-page-fade-leave-to { - opacity: 0; - backdrop-filter: blur(0); -} - -.personal-page-overlay.personal-page-fade-enter-active .personal-page-card, -.personal-page-overlay.personal-page-fade-leave-active .personal-page-card { - transition: transform 0.25s ease, opacity 0.25s ease; -} - -.personal-page-overlay.personal-page-fade-enter-from .personal-page-card, -.personal-page-overlay.personal-page-fade-leave-to .personal-page-card { - transform: translateY(18px) scale(0.985); - opacity: 0; -} - -/* 彩蛋灌水特效 */ -.easter-egg-overlay { - position: fixed; - inset: 0; - pointer-events: none; - display: flex; - align-items: flex-end; - justify-content: center; - opacity: 0; - transition: opacity 0.6s ease; - z-index: 80; -} - -.easter-egg-overlay.active { - opacity: 1; -} -.panel-card { - border-left: 1px solid var(--claude-border); - padding: 0 0 0 0; -} +/* 新版 Vite 样式 */ +@import url('/static/dist/assets/main.css?v=20251125'); +/* 回落:加载旧版 UI 布局,确保输入区、文件树等模块正常显示 */ +@import url('/static/old_version_backup/前端备份/style.css');