diff --git a/static/src/App.vue b/static/src/App.vue index c2b34ec..753acfa 100644 --- a/static/src/App.vue +++ b/static/src/App.vue @@ -111,8 +111,71 @@ :format-rate="formatRate" /> -
+
+ + {{ titleTypingText || currentConversationTitle }} + + +
+ + + +
+
o.value === this.resolvedRunMode); + return current ? current.label : '快速模式'; + }, + currentModelLabel() { + const modelStore = useModelStore(); + return modelStore.currentModel?.label || 'Kimi-k2.5'; + }, policyUiBlocks() { const store = usePolicyStore(); return store.uiBlocks || {}; @@ -502,13 +519,14 @@ const appOptions = { } }, - beforeUnmount() { - document.removeEventListener('click', this.handleClickOutsideQuickMenu); - document.removeEventListener('click', this.handleClickOutsidePanelMenu); - document.removeEventListener('click', this.handleClickOutsideMobileMenu); - window.removeEventListener('popstate', this.handlePopState); - window.removeEventListener('keydown', this.handleMobileOverlayEscape); - this.teardownMobileViewportWatcher(); + beforeUnmount() { + document.removeEventListener('click', this.handleClickOutsideQuickMenu); + document.removeEventListener('click', this.handleClickOutsidePanelMenu); + document.removeEventListener('click', this.handleClickOutsideHeaderMenu); + document.removeEventListener('click', this.handleClickOutsideMobileMenu); + window.removeEventListener('popstate', this.handlePopState); + window.removeEventListener('keydown', this.handleMobileOverlayEscape); + this.teardownMobileViewportWatcher(); this.subAgentStopPolling(); this.resourceStopContainerStatsPolling(); this.resourceStopProjectStoragePolling(); @@ -2780,6 +2798,16 @@ const appOptions = { } }, + toggleHeaderMenu() { + if (!this.isConnected) return; + this.headerMenuOpen = !this.headerMenuOpen; + if (this.headerMenuOpen) { + this.closeQuickMenu(); + this.inputCloseMenus(); + } + }, + + async handleModeSelect(mode) { if (!this.isConnected || this.streamingMessage) { return; @@ -2787,6 +2815,11 @@ const appOptions = { await this.setRunMode(mode); }, + async handleHeaderRunModeSelect(mode) { + await this.handleModeSelect(mode); + this.closeHeaderMenu(); + }, + async handleModelSelect(key) { if (!this.isConnected || this.streamingMessage) { return; @@ -2863,6 +2896,12 @@ const appOptions = { } }, + async handleHeaderModelSelect(key, disabled) { + if (disabled) return; + await this.handleModelSelect(key); + this.closeHeaderMenu(); + }, + async handleCycleRunMode() { const modes: Array<'fast' | 'thinking' | 'deep'> = ['fast', 'thinking', 'deep']; const currentMode = this.resolvedRunMode; @@ -3024,6 +3063,10 @@ const appOptions = { this.closeQuickMenu(); }, + closeHeaderMenu() { + this.headerMenuOpen = false; + }, + handleReviewSelect(id) { if (id === this.currentConversationId) { this.uiPushToast({ @@ -3207,6 +3250,16 @@ const appOptions = { } this.closeQuickMenu(); }, + + handleClickOutsideHeaderMenu(event) { + if (!this.headerMenuOpen) return; + const ribbon = this.$refs.titleRibbon as HTMLElement | undefined; + const menu = this.$refs.headerMenu as HTMLElement | undefined; + if ((ribbon && ribbon.contains(event.target)) || (menu && menu.contains(event.target))) { + return; + } + this.closeHeaderMenu(); + }, handleClickOutsidePanelMenu(event) { if (!this.panelMenuOpen) { diff --git a/static/src/components/input/QuickMenu.vue b/static/src/components/input/QuickMenu.vue index 83b0ea5..dd10ea0 100644 --- a/static/src/components/input/QuickMenu.vue +++ b/static/src/components/input/QuickMenu.vue @@ -26,24 +26,6 @@ > 发送图片 - - - -
- -
-
- - -
- -
-
diff --git a/static/src/styles/components/chat/_chat-area.scss b/static/src/styles/components/chat/_chat-area.scss index aabdd0c..b314e53 100644 --- a/static/src/styles/components/chat/_chat-area.scss +++ b/static/src/styles/components/chat/_chat-area.scss @@ -15,18 +15,19 @@ .conversation-ribbon { position: absolute; inset: 0 0 auto 0; - height: 38px; + height: 44px; padding: 8px 16px; display: flex; align-items: center; - justify-content: flex-end; + justify-content: space-between; color: #0f172a; background: var(--chat-surface-color); /* 与对话区域保持一致且不透明 */ box-shadow: none; - pointer-events: none; + pointer-events: auto; user-select: none; backdrop-filter: none; z-index: 40; + gap: 12px; } .conversation-ribbon::after { @@ -53,10 +54,12 @@ color: #0f172a; text-shadow: 0 1px 6px rgba(0, 0, 0, 0.12); white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .chat-container.has-title-ribbon .messages-area { - padding-top: 42px; + padding-top: 54px; } .chat-container--monitor .virtual-monitor-surface { @@ -129,6 +132,146 @@ padding-top: 68px; } +.conversation-ribbon__selector { + pointer-events: auto; + display: inline-flex; + align-items: center; + gap: 6px; + padding: 0; + border-radius: 0; + border: none; + background: transparent; + box-shadow: none; + color: #0f172a; + font-weight: 600; + font-size: 14px; + line-height: 1; + cursor: pointer; + transition: color 0.15s ease; +} + +.conversation-ribbon__selector:hover { + color: #111827; +} + +.conversation-ribbon__selector:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.conversation-ribbon__selector.open { + color: #111827; +} + +.selector-label { + display: inline-flex; + align-items: center; + gap: 6px; +} + +.selector-model { + font-weight: 700; +} + +.selector-sep { + opacity: 0.6; +} + +.selector-mode { + font-weight: 500; + color: rgba(15, 23, 42, 0.8); +} + +.selector-caret { + width: 14px; + height: 14px; + stroke: currentColor; +} + +.model-mode-dropdown { + position: absolute; + top: 48px; + left: 16px; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 12px; + padding: 12px; + border-radius: 14px; + background: rgba(255, 255, 255, 0.96); + box-shadow: 0 18px 48px rgba(15, 23, 42, 0.18); + backdrop-filter: blur(8px); + pointer-events: auto; + min-width: 480px; + z-index: 50; +} + +.dropdown-column { + display: flex; + flex-direction: column; + gap: 6px; +} + +.dropdown-title { + font-size: 12px; + font-weight: 700; + letter-spacing: 0.04em; + text-transform: uppercase; + color: rgba(15, 23, 42, 0.65); + padding: 2px 0; +} + +.dropdown-item { + text-align: left; + background: transparent; + border: 1px solid transparent; + border-radius: 12px; + padding: 10px 12px; + display: grid; + grid-template-columns: 1fr auto; + gap: 6px; + cursor: pointer; + transition: all 0.18s ease; + color: #0f172a; +} + +.dropdown-item:hover:not(.disabled) { + background: #f6f7fb; +} + +.dropdown-item.disabled { + opacity: 0.55; + cursor: not-allowed; +} + +.item-label { + font-weight: 700; + font-size: 14px; +} + +.item-desc { + grid-column: 1 / 2; + font-size: 12px; + color: rgba(15, 23, 42, 0.65); +} + +.item-check { + align-self: center; + font-weight: 800; + color: #0f172a; +} + +.header-menu-enter-active, +.header-menu-leave-active { + transition: all 0.16s ease, opacity 0.16s ease; + transform-origin: top left; +} + +.header-menu-enter-from, +.header-menu-leave-to { + opacity: 0; + transform: translateY(-6px) scale(0.98); +} + .scroll-lock-btn { width: 36px; height: 36px;