diff --git a/src/renderer/src/components/QuickPanel/view.tsx b/src/renderer/src/components/QuickPanel/view.tsx index 36957aaf63..c955453903 100644 --- a/src/renderer/src/components/QuickPanel/view.tsx +++ b/src/renderer/src/components/QuickPanel/view.tsx @@ -66,6 +66,9 @@ export const QuickPanelView: React.FC = ({ setInputText }) => { const prevSearchTextRef = useRef('') const prevSymbolRef = useRef('') + // 无匹配项自动关闭的定时器 + const noMatchTimeoutRef = useRef(null) + // 处理搜索,过滤列表 const list = useMemo(() => { if (!ctx.isVisible && !ctx.symbol) return [] @@ -128,12 +131,44 @@ export const QuickPanelView: React.FC = ({ setInputText }) => { prevSymbolRef.current = ctx.symbol return newList - }, [ctx.isVisible, ctx.list, ctx.symbol, searchText]) + }, [ctx.isVisible, ctx.symbol, ctx.list, searchText]) const canForwardAndBackward = useMemo(() => { return list.some((item) => item.isMenu) || historyPanel.length > 0 }, [list, historyPanel]) + // 智能关闭逻辑:当有搜索文本但无匹配项时,延迟关闭面板 + useEffect(() => { + const _searchText = searchText.replace(/^[/@]/, '') + + // 清除之前的定时器(无论面板是否可见都要清理) + if (noMatchTimeoutRef.current) { + clearTimeout(noMatchTimeoutRef.current) + noMatchTimeoutRef.current = null + } + + // 面板不可见时不设置新定时器 + if (!ctx.isVisible) { + return + } + + // 只有在有搜索文本但无匹配项时才设置延迟关闭 + if (_searchText && _searchText.length > 0 && list.length === 0) { + noMatchTimeoutRef.current = setTimeout(() => { + ctx.close('no-matches') + }, 300) + } + + // 清理函数 + return () => { + if (noMatchTimeoutRef.current) { + clearTimeout(noMatchTimeoutRef.current) + noMatchTimeoutRef.current = null + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps -- ctx对象引用不稳定,使用具体属性避免过度重渲染 + }, [ctx.isVisible, searchText, list.length, ctx.close]) + const clearSearchText = useCallback( (includeSymbol = false) => { const textArea = document.querySelector('.inputbar textarea') as HTMLTextAreaElement @@ -275,7 +310,7 @@ export const QuickPanelView: React.FC = ({ setInputText }) => { const newSearchText = textBeforeCursor.slice(lastSymbolIndex) setSearchText(newSearchText) } else { - handleClose('delete-symbol') + ctx.close('delete-symbol') } } diff --git a/src/renderer/src/pages/home/Inputbar/InputbarTools.tsx b/src/renderer/src/pages/home/Inputbar/InputbarTools.tsx index 9053a4e02e..4f6f264ace 100644 --- a/src/renderer/src/pages/home/Inputbar/InputbarTools.tsx +++ b/src/renderer/src/pages/home/Inputbar/InputbarTools.tsx @@ -397,6 +397,7 @@ const InputbarTools = ({ ToolbarButton={ToolbarButton} couldMentionNotVisionModel={couldMentionNotVisionModel} files={files} + setText={setText} /> ) }, diff --git a/src/renderer/src/pages/home/Inputbar/MentionModelsButton.tsx b/src/renderer/src/pages/home/Inputbar/MentionModelsButton.tsx index e76b85877b..2aff40c0de 100644 --- a/src/renderer/src/pages/home/Inputbar/MentionModelsButton.tsx +++ b/src/renderer/src/pages/home/Inputbar/MentionModelsButton.tsx @@ -27,6 +27,7 @@ interface Props { couldMentionNotVisionModel: boolean files: FileType[] ToolbarButton: any + setText: React.Dispatch> } const MentionModelsButton: FC = ({ @@ -35,13 +36,17 @@ const MentionModelsButton: FC = ({ onMentionModel, couldMentionNotVisionModel, files, - ToolbarButton + ToolbarButton, + setText }) => { const { providers } = useProviders() const { t } = useTranslation() const navigate = useNavigate() const quickPanel = useQuickPanel() + // 记录是否有模型被选择的动作发生 + const hasModelActionRef = useRef(false) + const pinnedModels = useLiveQuery( async () => { const setting = await db.settings.get('pinned:models') @@ -74,7 +79,10 @@ const MentionModelsButton: FC = ({ ), filterText: getFancyProviderName(p) + m.name, - action: () => onMentionModel(m), + action: () => { + hasModelActionRef.current = true // 标记有模型动作发生 + onMentionModel(m) + }, isSelected: mentionedModels.some((selected) => getModelUniqId(selected) === getModelUniqId(m)) })) ) @@ -107,7 +115,10 @@ const MentionModelsButton: FC = ({ ), filterText: getFancyProviderName(p) + m.name, - action: () => onMentionModel(m), + action: () => { + hasModelActionRef.current = true // 标记有模型动作发生 + onMentionModel(m) + }, isSelected: mentionedModels.some((selected) => getModelUniqId(selected) === getModelUniqId(m)) })) @@ -127,6 +138,9 @@ const MentionModelsButton: FC = ({ }, [pinnedModels, providers, t, couldMentionNotVisionModel, mentionedModels, onMentionModel, navigate]) const openQuickPanel = useCallback(() => { + // 重置模型动作标记 + hasModelActionRef.current = false + quickPanel.open({ title: t('agents.edit.model.select.title'), list: modelItems, @@ -134,9 +148,25 @@ const MentionModelsButton: FC = ({ multiple: true, afterAction({ item }) { item.isSelected = !item.isSelected + }, + onClose({ action }) { + // ESC或Backspace关闭时的特殊处理 + if (action === 'esc' || action === 'delete-symbol') { + // 如果有模型选择动作发生,删除@字符 + if (hasModelActionRef.current) { + // 使用React的setText来更新状态,而不是直接操作DOM + setText((currentText) => { + const lastAtIndex = currentText.lastIndexOf('@') + if (lastAtIndex !== -1) { + return currentText.slice(0, lastAtIndex) + currentText.slice(lastAtIndex + 1) + } + return currentText + }) + } + } } }) - }, [modelItems, quickPanel, t]) + }, [modelItems, quickPanel, t, setText]) const handleOpenQuickPanel = useCallback(() => { if (quickPanel.isVisible && quickPanel.symbol === '@') {