feat: 添加系统命令支持 (:clear)
- 新增系统命令模块 (core/system-commands) - 支持 :clear/:cls/:c 清空对话历史 - 命令注册表支持别名 - 可扩展的命令执行器 - Server 端支持 - 新增 /api/system-commands API - WebSocket 处理系统命令消息 - 会话清空 API 端点 - UI 端支持 - 新增 SystemCommandMenu 组件 - 输入 : 时显示命令建议菜单 - 键盘导航和选择 - 底部提示添加 : 快捷键
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
*
|
||||
* 支持响应式:responsive=true 时适配移动端键盘和触摸操作
|
||||
* 支持斜杠命令:输入 / 时显示命令菜单
|
||||
* 支持系统命令:输入 : 时显示系统命令菜单
|
||||
* 支持文件提及:输入 @ 时显示文件搜索菜单
|
||||
*/
|
||||
|
||||
@@ -11,10 +12,12 @@ import { Square, Sparkles } from 'lucide-react';
|
||||
import { motion } from 'framer-motion';
|
||||
import clsx from 'clsx';
|
||||
import { CommandMenu, type CommandMenuItem } from './CommandMenu.js';
|
||||
import { SystemCommandMenu, type SystemCommandMenuItem } from './SystemCommandMenu.js';
|
||||
import { FileMenu, type FileMenuItem } from './FileMenu.js';
|
||||
import { FileMentionTag } from './FileMentionTag.js';
|
||||
import { AgentModeSelector, type AgentModeType } from './AgentModeSelector.js';
|
||||
import { useCommands } from '../hooks/useCommands.js';
|
||||
import { useSystemCommands } from '../hooks/useSystemCommands.js';
|
||||
import { useFileMention } from '../hooks/useFileMention.js';
|
||||
|
||||
interface ChatInputProps {
|
||||
@@ -54,6 +57,8 @@ export function ChatInput({
|
||||
const [input, setInput] = useState('');
|
||||
const [showCommandMenu, setShowCommandMenu] = useState(false);
|
||||
const [selectedCommandIndex, setSelectedCommandIndex] = useState(0);
|
||||
const [showSystemCommandMenu, setShowSystemCommandMenu] = useState(false);
|
||||
const [selectedSystemCommandIndex, setSelectedSystemCommandIndex] = useState(0);
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
// 命令系统
|
||||
@@ -63,6 +68,13 @@ export function ChatInput({
|
||||
filterCommands,
|
||||
} = useCommands({ autoLoad: enableCommands });
|
||||
|
||||
// 系统命令系统
|
||||
const {
|
||||
filteredCommands: filteredSystemCommands,
|
||||
isLoading: systemCommandsLoading,
|
||||
filterCommands: filterSystemCommands,
|
||||
} = useSystemCommands({ autoLoad: enableCommands });
|
||||
|
||||
// 文件提及系统
|
||||
const {
|
||||
isOpen: showFileMenu,
|
||||
@@ -130,17 +142,41 @@ export function ChatInput({
|
||||
[enableCommands, filterCommands]
|
||||
);
|
||||
|
||||
// 检测系统命令输入
|
||||
const checkSystemCommandTrigger = useCallback(
|
||||
(value: string) => {
|
||||
if (!enableCommands) return;
|
||||
|
||||
// 检测是否在输入系统命令
|
||||
// 条件:以 : 开头,且 : 后面没有空格(还在输入命令名)
|
||||
const colonMatch = value.match(/^:(\S*)$/);
|
||||
|
||||
if (colonMatch) {
|
||||
const query = colonMatch[1]; // : 后面的内容
|
||||
setShowSystemCommandMenu(true);
|
||||
setSelectedSystemCommandIndex(0);
|
||||
filterSystemCommands(query);
|
||||
} else {
|
||||
setShowSystemCommandMenu(false);
|
||||
}
|
||||
},
|
||||
[enableCommands, filterSystemCommands]
|
||||
);
|
||||
|
||||
// 处理输入变化
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
const value = e.target.value;
|
||||
const cursorPos = e.target.selectionStart;
|
||||
setInput(value);
|
||||
|
||||
// 检查命令触发
|
||||
// 检查斜杠命令触发
|
||||
checkCommandTrigger(value);
|
||||
|
||||
// 检查系统命令触发
|
||||
checkSystemCommandTrigger(value);
|
||||
|
||||
// 检查文件提及触发(只在非命令输入模式下)
|
||||
if (enableFileMention && !value.startsWith('/')) {
|
||||
if (enableFileMention && !value.startsWith('/') && !value.startsWith(':')) {
|
||||
checkFileTrigger(value, cursorPos);
|
||||
} else {
|
||||
closeFileMenu();
|
||||
@@ -189,12 +225,28 @@ export function ChatInput({
|
||||
setShowCommandMenu(false);
|
||||
}, []);
|
||||
|
||||
// 选择系统命令
|
||||
const handleSystemCommandSelect = useCallback((command: SystemCommandMenuItem) => {
|
||||
// 直接发送系统命令
|
||||
setInput(`:${command.name}`);
|
||||
setShowSystemCommandMenu(false);
|
||||
|
||||
// 聚焦输入框
|
||||
textareaRef.current?.focus();
|
||||
}, []);
|
||||
|
||||
// 关闭系统命令菜单
|
||||
const handleSystemCommandMenuClose = useCallback(() => {
|
||||
setShowSystemCommandMenu(false);
|
||||
}, []);
|
||||
|
||||
const handleSubmit = () => {
|
||||
const trimmed = input.trim();
|
||||
if (!trimmed || isLoading || disabled) return;
|
||||
|
||||
// 关闭菜单
|
||||
// 关闭所有菜单
|
||||
setShowCommandMenu(false);
|
||||
setShowSystemCommandMenu(false);
|
||||
closeFileMenu();
|
||||
|
||||
onSend(trimmed);
|
||||
@@ -222,7 +274,7 @@ export function ChatInput({
|
||||
}
|
||||
}
|
||||
|
||||
// 命令菜单处理
|
||||
// 斜杠命令菜单处理
|
||||
if (showCommandMenu && filteredCommands.length > 0) {
|
||||
if (['ArrowUp', 'ArrowDown', 'Tab', 'Escape'].includes(e.key)) {
|
||||
// 这些键由 CommandMenu 处理,阻止默认行为
|
||||
@@ -238,6 +290,22 @@ export function ChatInput({
|
||||
}
|
||||
}
|
||||
|
||||
// 系统命令菜单处理
|
||||
if (showSystemCommandMenu && filteredSystemCommands.length > 0) {
|
||||
if (['ArrowUp', 'ArrowDown', 'Tab', 'Escape'].includes(e.key)) {
|
||||
// 这些键由 SystemCommandMenu 处理,阻止默认行为
|
||||
return;
|
||||
}
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
// Enter 选择系统命令
|
||||
e.preventDefault();
|
||||
if (filteredSystemCommands[selectedSystemCommandIndex]) {
|
||||
handleSystemCommandSelect(filteredSystemCommands[selectedSystemCommandIndex]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Enter 发送,Shift+Enter 换行
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
@@ -252,7 +320,7 @@ export function ChatInput({
|
||||
responsive ? 'p-3 md:p-4 safe-area-pb' : 'p-4'
|
||||
)}
|
||||
>
|
||||
{/* Command Menu */}
|
||||
{/* Command Menu (斜杠命令) */}
|
||||
{enableCommands && (
|
||||
<CommandMenu
|
||||
commands={filteredCommands}
|
||||
@@ -265,6 +333,19 @@ export function ChatInput({
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* System Command Menu (系统命令) */}
|
||||
{enableCommands && (
|
||||
<SystemCommandMenu
|
||||
commands={filteredSystemCommands}
|
||||
isOpen={showSystemCommandMenu}
|
||||
selectedIndex={selectedSystemCommandIndex}
|
||||
onSelect={handleSystemCommandSelect}
|
||||
onClose={handleSystemCommandMenuClose}
|
||||
onSelectedIndexChange={setSelectedSystemCommandIndex}
|
||||
isLoading={systemCommandsLoading}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* File Menu */}
|
||||
{enableFileMention && (
|
||||
<FileMenu
|
||||
@@ -382,6 +463,10 @@ export function ChatInput({
|
||||
<kbd className="px-1.5 py-0.5 rounded bg-surface-subtle text-fg-muted font-mono text-[10px]">/</kbd>
|
||||
<span>commands</span>
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<kbd className="px-1.5 py-0.5 rounded bg-surface-subtle text-fg-muted font-mono text-[10px]">:</kbd>
|
||||
<span>system</span>
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<kbd className="px-1.5 py-0.5 rounded bg-surface-subtle text-fg-muted font-mono text-[10px]">@</kbd>
|
||||
<span>files</span>
|
||||
|
||||
Reference in New Issue
Block a user