Files
ai-terminal-assistant/packages/ui/src/hooks/useCommands.ts
T
kurihada db711648e0 feat(ui): 添加斜杠命令输入支持
- 新增 useCommands hook 用于加载和搜索命令
- 新增 CommandMenu 组件,支持键盘导航和选择
- ChatInput 支持 / 触发命令菜单
- 导出命令相关 API 和类型
2025-12-12 18:38:43 +08:00

144 lines
3.5 KiB
TypeScript

/**
* Commands Hook
*
* 管理斜杠命令的加载、搜索和缓存
*/
import { useState, useEffect, useCallback, useRef } from 'react';
import { listCommands, searchCommands } from '../api/client.js';
import type { CommandListResponse } from '../api/types.js';
type CommandItem = CommandListResponse['commands'][number];
interface UseCommandsOptions {
/** 是否在挂载时自动加载命令列表 */
autoLoad?: boolean;
}
interface UseCommandsState {
/** 所有命令列表 */
commands: CommandItem[];
/** 过滤后的命令列表 */
filteredCommands: CommandItem[];
/** 是否正在加载 */
isLoading: boolean;
/** 错误信息 */
error: string | null;
}
export function useCommands(options: UseCommandsOptions = {}) {
const { autoLoad = true } = options;
const [state, setState] = useState<UseCommandsState>({
commands: [],
filteredCommands: [],
isLoading: false,
error: null,
});
const loadedRef = useRef(false);
// 加载命令列表
const loadCommands = useCallback(async () => {
if (state.isLoading) return;
setState((prev) => ({ ...prev, isLoading: true, error: null }));
try {
const result = await listCommands();
if (result.success) {
setState({
commands: result.data.commands,
filteredCommands: result.data.commands,
isLoading: false,
error: null,
});
} else {
setState((prev) => ({
...prev,
isLoading: false,
error: 'Failed to load commands',
}));
}
} catch (error) {
setState((prev) => ({
...prev,
isLoading: false,
error: error instanceof Error ? error.message : 'Unknown error',
}));
}
}, [state.isLoading]);
// 搜索/过滤命令
const filterCommands = useCallback(
async (query: string) => {
// 空查询显示所有命令
if (!query.trim()) {
setState((prev) => ({
...prev,
filteredCommands: prev.commands,
}));
return;
}
// 本地过滤(快速响应)
const queryLower = query.toLowerCase();
const localFiltered = state.commands.filter(
(cmd) =>
cmd.name.toLowerCase().includes(queryLower) ||
cmd.description?.toLowerCase().includes(queryLower)
);
// 如果本地过滤有结果,直接使用
if (localFiltered.length > 0) {
setState((prev) => ({
...prev,
filteredCommands: localFiltered,
}));
return;
}
// 否则尝试服务端搜索(可能有模糊匹配)
try {
const result = await searchCommands(query, 10);
if (result.success && result.data.length > 0) {
setState((prev) => ({
...prev,
filteredCommands: result.data.map((r) => ({
name: r.name,
description: r.description,
source: r.source,
})),
}));
} else {
setState((prev) => ({
...prev,
filteredCommands: [],
}));
}
} catch {
// 搜索失败,保持本地过滤结果
setState((prev) => ({
...prev,
filteredCommands: localFiltered,
}));
}
},
[state.commands]
);
// 自动加载
useEffect(() => {
if (autoLoad && !loadedRef.current) {
loadedRef.current = true;
loadCommands();
}
}, [autoLoad, loadCommands]);
return {
...state,
loadCommands,
filterCommands,
};
}