From a5f4de1db277832c9fa3321d51f9808c730b0a51 Mon Sep 17 00:00:00 2001 From: GCWing Date: Tue, 24 Mar 2026 17:23:27 +0800 Subject: [PATCH] feat(web-ui): boost menu opens @ mention; toolbar mode defaults expanded - Add Boost row to insert @ and open file/folder picker (RichTextInput.openMention) - Toolbar mode: enter expanded FlowChat size/position and expanded state by default - i18n: chatInput.boostAddContext (en-US, zh-CN) --- .../src/flow_chat/components/ChatInput.tsx | 27 +++++++++++++++++- .../flow_chat/components/RichTextInput.tsx | 28 ++++++++++++++++++- .../toolbar-mode/ToolbarModeContext.tsx | 10 +++---- src/web-ui/src/locales/en-US/flow-chat.json | 1 + src/web-ui/src/locales/zh-CN/flow-chat.json | 1 + 5 files changed, 60 insertions(+), 7 deletions(-) diff --git a/src/web-ui/src/flow_chat/components/ChatInput.tsx b/src/web-ui/src/flow_chat/components/ChatInput.tsx index fe972f98..c90471ea 100644 --- a/src/web-ui/src/flow_chat/components/ChatInput.tsx +++ b/src/web-ui/src/flow_chat/components/ChatInput.tsx @@ -5,7 +5,7 @@ import React, { useRef, useCallback, useEffect, useReducer, useState, useMemo } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { ArrowUp, Image, ChevronsUp, ChevronsDown, RotateCcw, Plus, X, Sparkles, Loader2, ChevronRight } from 'lucide-react'; +import { ArrowUp, Image, ChevronsUp, ChevronsDown, RotateCcw, Plus, X, Sparkles, Loader2, ChevronRight, Files } from 'lucide-react'; import { ContextDropZone, useContextStore } from '../../shared/context-system'; import { useActiveSessionState } from '../hooks/useActiveSessionState'; import { RichTextInput, type MentionState } from './RichTextInput'; @@ -1297,6 +1297,20 @@ export const ChatInput: React.FC = ({ [handleImageInput] ); + const handleBoostOpenAtContext = useCallback((e: React.SyntheticEvent) => { + e.stopPropagation(); + dispatchMode({ type: 'CLOSE_DROPDOWN' }); + dispatchInput({ type: 'ACTIVATE' }); + window.requestAnimationFrame(() => { + window.requestAnimationFrame(() => { + const el = richTextInputRef.current; + if (el && typeof (el as unknown as { openMention?: () => void }).openMention === 'function') { + (el as unknown as { openMention: () => void }).openMention(); + } + }); + }); + }, []); + const handleOpenSkillsLibrary = useCallback( (e: React.MouseEvent) => { e.stopPropagation(); @@ -1771,6 +1785,17 @@ export const ChatInput: React.FC = ({ )}
+
e.key === 'Enter' && handleBoostOpenAtContext(e)} + > + + {t('chatInput.boostAddContext')} +
+
{ + const editor = internalRef.current; + if (!editor) return; + + editor.focus(); + const sel = window.getSelection(); + let range: Range | null = null; + if (sel && sel.rangeCount > 0) { + range = sel.getRangeAt(0); + } + if (!range || !editor.contains(range.commonAncestorContainer)) { + range = document.createRange(); + range.selectNodeContents(editor); + range.collapse(false); + sel?.removeAllRanges(); + sel?.addRange(range); + } + + document.execCommand('insertText', false, '@'); + requestAnimationFrame(() => { + detectMention(); + }); + }, [detectMention, internalRef]); + // Expose methods to parent useEffect(() => { if (internalRef.current) { (internalRef.current as any).insertTag = insertTagAtCursor; (internalRef.current as any).insertTagReplacingMention = insertTagReplacingMention; + (internalRef.current as any).openMention = openMention; (internalRef.current as any).closeMention = () => { if (mentionStateRef.current.isActive) { mentionStateRef.current = { isActive: false, query: '', startOffset: 0 }; @@ -569,7 +595,7 @@ export const RichTextInput = React.forwardRef = ({ childr // Update state first so React renders the toolbar UI. setIsToolbarMode(true); - setIsExpanded(false); // Enter compact mode by default. + setIsExpanded(true); // Enter expanded FlowChat by default. if (isMaximized) { await win.unmaximize(); @@ -156,15 +156,15 @@ export const ToolbarModeProvider: React.FC = ({ childr const margin = Math.round(20 * scaleFactor); const taskbarHeight = Math.round(50 * scaleFactor); // Estimated taskbar height. - x = monitor.size.width - TOOLBAR_COMPACT_SIZE.width - margin; - y = monitor.size.height - TOOLBAR_COMPACT_SIZE.height - margin - taskbarHeight; + x = monitor.size.width - TOOLBAR_EXPANDED_SIZE.width - margin; + y = monitor.size.height - TOOLBAR_EXPANDED_SIZE.height - margin - taskbarHeight; } // Apply window props after toolbar UI renders. // macOS: avoid decorations toggles to preserve overlay title bar behavior. const toolbarWindowOps: Array> = [ win.setAlwaysOnTop(true), - win.setSize(new PhysicalSize(TOOLBAR_COMPACT_SIZE.width, TOOLBAR_COMPACT_SIZE.height)), + win.setSize(new PhysicalSize(TOOLBAR_EXPANDED_SIZE.width, TOOLBAR_EXPANDED_SIZE.height)), win.setPosition(new PhysicalPosition(x, y)), win.setResizable(true), win.setSkipTaskbar(true), @@ -179,7 +179,7 @@ export const ToolbarModeProvider: React.FC = ({ childr } await Promise.all(toolbarWindowOps); - await win.setMinSize(new PhysicalSize(TOOLBAR_COMPACT_MIN.width, TOOLBAR_COMPACT_MIN.height)); + await win.setMinSize(new PhysicalSize(TOOLBAR_EXPANDED_MIN.width, TOOLBAR_EXPANDED_MIN.height)); } catch (error) { log.error('Failed to enable toolbar mode', error); diff --git a/src/web-ui/src/locales/en-US/flow-chat.json b/src/web-ui/src/locales/en-US/flow-chat.json index 1f80ca67..87445c74 100644 --- a/src/web-ui/src/locales/en-US/flow-chat.json +++ b/src/web-ui/src/locales/en-US/flow-chat.json @@ -184,6 +184,7 @@ "addModeTooltip": "Add Plan or Debug", "boostSectionAgent": "Agent", "boostSectionContext": "Context", + "boostAddContext": "Reference file or folder", "boostSkills": "Skills", "boostSkillsLoading": "Loading skills…", "boostSkillsEmpty": "No enabled skills", diff --git a/src/web-ui/src/locales/zh-CN/flow-chat.json b/src/web-ui/src/locales/zh-CN/flow-chat.json index 0b3c576e..14d342e7 100644 --- a/src/web-ui/src/locales/zh-CN/flow-chat.json +++ b/src/web-ui/src/locales/zh-CN/flow-chat.json @@ -184,6 +184,7 @@ "addModeTooltip": "附加 Plan 或 Debug", "boostSectionAgent": "智能体", "boostSectionContext": "上下文", + "boostAddContext": "引用文件或文件夹", "boostSkills": "Skills", "boostSkillsLoading": "正在加载 Skills…", "boostSkillsEmpty": "暂无已启用的 Skill",