From 619cd2d2ddaf33b483d094b403a988c31b7aa20a Mon Sep 17 00:00:00 2001 From: kurihada Date: Wed, 17 Dec 2025 17:18:58 +0800 Subject: [PATCH] =?UTF-8?q?feat(ui):=20=E9=87=8D=E6=9E=84=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=E9=80=89=E6=8B=A9=E5=99=A8=E4=B8=BA=E7=82=B9=E5=87=BB?= =?UTF-8?q?=E5=88=87=E6=8D=A2=E4=BA=A4=E4=BA=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 Build/Plan 下拉框和 Auto-approve 开关合并为三种模式 - Plan: 调用 plan agent,只读分析 - Ask: 调用 general agent,执行操作前需确认 - Auto: 调用 general agent,自动执行无需确认 - 点击按钮即可循环切换:Ask → Auto → Plan - 每种模式有独特的颜色和图标便于区分 --- .../ui/src/components/AgentModeSelector.tsx | 245 ++++++++---------- packages/ui/src/components/ChatInput.tsx | 180 +++++++------ 2 files changed, 213 insertions(+), 212 deletions(-) diff --git a/packages/ui/src/components/AgentModeSelector.tsx b/packages/ui/src/components/AgentModeSelector.tsx index a85118b..a9cb782 100644 --- a/packages/ui/src/components/AgentModeSelector.tsx +++ b/packages/ui/src/components/AgentModeSelector.tsx @@ -1,16 +1,20 @@ /** * Agent Mode Selector Component * - * 在 ChatInput 左侧显示,用于切换 Build/Plan 模式和控制 Auto-approve + * 在 ChatInput 左侧显示,点击切换三种模式: + * - Plan: 调用 plan agent,只读分析 + * - Ask: 调用 general agent,执行操作需确认 + * - Auto: 调用 general agent,自动执行无需确认 */ -import { useState, useRef, useEffect } from 'react'; -import { Hammer, FileSearch, ChevronDown, Check } from 'lucide-react'; +import { FileSearch, MessageCircleQuestion, Zap } from 'lucide-react'; import clsx from 'clsx'; -import { Switch } from '../primitives/Switch.js'; export type AgentModeType = 'build' | 'plan'; +/** 组合模式:Plan / Ask / Auto */ +export type CombinedModeType = 'plan' | 'ask' | 'auto'; + interface AgentModeSelectorProps { /** 当前模式 */ mode: AgentModeType; @@ -24,23 +28,75 @@ interface AgentModeSelectorProps { disabled?: boolean; } -const modeConfig = { - build: { - label: 'Build', - icon: Hammer, - color: 'text-blue-500', - bgColor: 'bg-blue-500/10', - description: '可执行代码修改', - }, +const modeConfig: Record< + CombinedModeType, + { + label: string; + icon: typeof FileSearch; + color: string; + bgColor: string; + borderColor: string; + hoverBg: string; + description: string; + } +> = { plan: { label: 'Plan', icon: FileSearch, color: 'text-purple-500', bgColor: 'bg-purple-500/10', - description: '只读模式,仅分析', + borderColor: 'border-purple-500/30', + hoverBg: 'hover:bg-purple-500/15', + description: '只读模式,仅分析和规划', + }, + ask: { + label: 'Ask', + icon: MessageCircleQuestion, + color: 'text-blue-500', + bgColor: 'bg-blue-500/10', + borderColor: 'border-blue-500/30', + hoverBg: 'hover:bg-blue-500/15', + description: '执行操作前需要确认', + }, + auto: { + label: 'Auto', + icon: Zap, + color: 'text-orange-500', + bgColor: 'bg-orange-500/10', + borderColor: 'border-orange-500/30', + hoverBg: 'hover:bg-orange-500/15', + description: '自动执行,无需确认', }, }; +// 模式切换顺序:Ask -> Auto -> Plan -> Ask ... +const modeOrder: CombinedModeType[] = ['ask', 'auto', 'plan']; + +/** + * 根据 agentMode 和 autoApprove 计算当前组合模式 + */ +function getCombinedMode(agentMode: AgentModeType, autoApprove: boolean): CombinedModeType { + if (agentMode === 'plan') return 'plan'; + return autoApprove ? 'auto' : 'ask'; +} + +/** + * 根据组合模式计算 agentMode 和 autoApprove + */ +function getAgentConfig(combinedMode: CombinedModeType): { + agentMode: AgentModeType; + autoApprove: boolean; +} { + switch (combinedMode) { + case 'plan': + return { agentMode: 'plan', autoApprove: false }; + case 'ask': + return { agentMode: 'build', autoApprove: false }; + case 'auto': + return { agentMode: 'build', autoApprove: true }; + } +} + export function AgentModeSelector({ mode, onModeChange, @@ -48,138 +104,49 @@ export function AgentModeSelector({ onAutoApproveChange, disabled = false, }: AgentModeSelectorProps) { - const [isOpen, setIsOpen] = useState(false); - const dropdownRef = useRef(null); + // 计算当前组合模式 + const currentCombinedMode = getCombinedMode(mode, autoApprove); + const currentConfig = modeConfig[currentCombinedMode]; + const ModeIcon = currentConfig.icon; - const currentMode = modeConfig[mode]; - const ModeIcon = currentMode.icon; + // 点击切换到下一个模式 + const handleToggle = () => { + if (disabled) return; - // 点击外部关闭下拉菜单 - useEffect(() => { - function handleClickOutside(event: MouseEvent) { - if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) { - setIsOpen(false); - } - } + // 找到当前模式在顺序中的索引 + const currentIndex = modeOrder.indexOf(currentCombinedMode); + // 切换到下一个模式 + const nextIndex = (currentIndex + 1) % modeOrder.length; + const nextMode = modeOrder[nextIndex]; - if (isOpen) { - document.addEventListener('mousedown', handleClickOutside); - return () => document.removeEventListener('mousedown', handleClickOutside); - } - }, [isOpen]); - - // 键盘导航 - useEffect(() => { - function handleKeyDown(event: KeyboardEvent) { - if (!isOpen) return; - - if (event.key === 'Escape') { - setIsOpen(false); - } else if (event.key === 'ArrowDown' || event.key === 'ArrowUp') { - event.preventDefault(); - // 切换模式 - onModeChange(mode === 'build' ? 'plan' : 'build'); - } else if (event.key === 'Enter') { - setIsOpen(false); - } - } - - document.addEventListener('keydown', handleKeyDown); - return () => document.removeEventListener('keydown', handleKeyDown); - }, [isOpen, mode, onModeChange]); - - const handleModeSelect = (newMode: AgentModeType) => { - onModeChange(newMode); - setIsOpen(false); + // 获取新的配置并更新 + const { agentMode, autoApprove: newAutoApprove } = getAgentConfig(nextMode); + onModeChange(agentMode); + onAutoApproveChange(newAutoApprove); }; return ( -
- {/* 模式选择器 */} -
- - - {/* 下拉菜单 */} - {isOpen && ( -
- {(Object.entries(modeConfig) as [AgentModeType, typeof modeConfig.build][]).map( - ([modeKey, config]) => { - const Icon = config.icon; - const isSelected = modeKey === mode; - - return ( - - ); - } - )} -
- )} -
- - {/* Auto-approve 开关 - 仅在 Build 模式下显示 */} - {mode === 'build' && ( -
- - -
+ ); } diff --git a/packages/ui/src/components/ChatInput.tsx b/packages/ui/src/components/ChatInput.tsx index 27ef9ab..1b7af0b 100644 --- a/packages/ui/src/components/ChatInput.tsx +++ b/packages/ui/src/components/ChatInput.tsx @@ -7,7 +7,8 @@ */ import { useState, useRef, useEffect, useCallback, useMemo } from 'react'; -import { Send, Square } from 'lucide-react'; +import { Square, Sparkles } from 'lucide-react'; +import { motion } from 'framer-motion'; import clsx from 'clsx'; import { CommandMenu, type CommandMenuItem } from './CommandMenu.js'; import { FileMenu, type FileMenuItem } from './FileMenu.js'; @@ -247,7 +248,7 @@ export function ChatInput({ return (
@@ -278,83 +279,116 @@ export function ChatInput({ )}
- {/* 已选文件标签 */} - {mentionedFiles.length > 0 && ( -
- {mentionedFiles.map((file, index) => ( - handleRemoveFile(file)} - /> - ))} -
- )} - - {/* 输入区域 */} -
- {/* Agent 模式选择器 */} - {onAgentModeChange && ( -
- {})} - disabled={disabled || isLoading} - /> + {/* 主输入容器 - 现代化卡片设计 */} +
+ {/* 已选文件标签 */} + {mentionedFiles.length > 0 && ( +
+ {mentionedFiles.map((file, index) => ( + handleRemoveFile(file)} + /> + ))}
)} -
-