201 lines
5.7 KiB
TypeScript
201 lines
5.7 KiB
TypeScript
|
|
/**
|
||
|
|
* @license
|
||
|
|
* Copyright 2025 Qwen
|
||
|
|
* SPDX-License-Identifier: Apache-2.0
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { useState, useMemo } from 'react';
|
||
|
|
import { Box, Text } from 'ink';
|
||
|
|
import { RadioButtonSelect } from '../shared/RadioButtonSelect.js';
|
||
|
|
import { WizardStepProps, ToolCategory } from './types.js';
|
||
|
|
import { Kind } from '@qwen-code/qwen-code-core';
|
||
|
|
import { Colors } from '../../colors.js';
|
||
|
|
|
||
|
|
interface ToolOption {
|
||
|
|
label: string;
|
||
|
|
value: string;
|
||
|
|
category: ToolCategory;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Step 4: Tool selection with categories.
|
||
|
|
*/
|
||
|
|
export function ToolSelector({
|
||
|
|
state: _state,
|
||
|
|
dispatch,
|
||
|
|
onNext,
|
||
|
|
onPrevious: _onPrevious,
|
||
|
|
config,
|
||
|
|
}: WizardStepProps) {
|
||
|
|
const [selectedCategory, setSelectedCategory] = useState<string>('all');
|
||
|
|
|
||
|
|
// Generate tool categories from actual tool registry
|
||
|
|
const { toolCategories, readTools, editTools, executeTools } = useMemo(() => {
|
||
|
|
if (!config) {
|
||
|
|
// Fallback categories if config not available
|
||
|
|
return {
|
||
|
|
toolCategories: [
|
||
|
|
{
|
||
|
|
id: 'all',
|
||
|
|
name: 'All Tools (Default)',
|
||
|
|
tools: [],
|
||
|
|
},
|
||
|
|
],
|
||
|
|
readTools: [],
|
||
|
|
editTools: [],
|
||
|
|
executeTools: [],
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
const toolRegistry = config.getToolRegistry();
|
||
|
|
const allTools = toolRegistry.getAllTools();
|
||
|
|
|
||
|
|
// Categorize tools by Kind
|
||
|
|
const readTools = allTools
|
||
|
|
.filter(
|
||
|
|
(tool) =>
|
||
|
|
tool.kind === Kind.Read ||
|
||
|
|
tool.kind === Kind.Search ||
|
||
|
|
tool.kind === Kind.Fetch,
|
||
|
|
)
|
||
|
|
.map((tool) => tool.displayName)
|
||
|
|
.sort();
|
||
|
|
|
||
|
|
const editTools = allTools
|
||
|
|
.filter(
|
||
|
|
(tool) =>
|
||
|
|
tool.kind === Kind.Edit ||
|
||
|
|
tool.kind === Kind.Delete ||
|
||
|
|
tool.kind === Kind.Move ||
|
||
|
|
tool.kind === Kind.Think,
|
||
|
|
)
|
||
|
|
.map((tool) => tool.displayName)
|
||
|
|
.sort();
|
||
|
|
|
||
|
|
const executeTools = allTools
|
||
|
|
.filter((tool) => tool.kind === Kind.Execute)
|
||
|
|
.map((tool) => tool.displayName)
|
||
|
|
.sort();
|
||
|
|
|
||
|
|
const toolCategories = [
|
||
|
|
{
|
||
|
|
id: 'all',
|
||
|
|
name: 'All Tools',
|
||
|
|
tools: [],
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: 'read',
|
||
|
|
name: 'Read-only Tools',
|
||
|
|
tools: readTools,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: 'edit',
|
||
|
|
name: 'Read & Edit Tools',
|
||
|
|
tools: [...readTools, ...editTools],
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: 'execute',
|
||
|
|
name: 'Read & Edit & Execution Tools',
|
||
|
|
tools: [...readTools, ...editTools, ...executeTools],
|
||
|
|
},
|
||
|
|
].filter((category) => category.id === 'all' || category.tools.length > 0);
|
||
|
|
|
||
|
|
return { toolCategories, readTools, editTools, executeTools };
|
||
|
|
}, [config]);
|
||
|
|
|
||
|
|
const toolOptions: ToolOption[] = toolCategories.map((category) => ({
|
||
|
|
label: category.name,
|
||
|
|
value: category.id,
|
||
|
|
category,
|
||
|
|
}));
|
||
|
|
|
||
|
|
const handleHighlight = (selectedValue: string) => {
|
||
|
|
setSelectedCategory(selectedValue);
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleSelect = (selectedValue: string) => {
|
||
|
|
const category = toolCategories.find((cat) => cat.id === selectedValue);
|
||
|
|
if (category) {
|
||
|
|
if (category.id === 'all') {
|
||
|
|
dispatch({ type: 'SET_TOOLS', tools: 'all' });
|
||
|
|
} else {
|
||
|
|
dispatch({ type: 'SET_TOOLS', tools: category.tools });
|
||
|
|
}
|
||
|
|
onNext();
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// Get the currently selected category for displaying tools
|
||
|
|
const currentCategory = toolCategories.find(
|
||
|
|
(cat) => cat.id === selectedCategory,
|
||
|
|
);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<Box flexDirection="column" gap={1}>
|
||
|
|
<Box flexDirection="column">
|
||
|
|
<RadioButtonSelect
|
||
|
|
items={toolOptions.map((option) => ({
|
||
|
|
label: option.label,
|
||
|
|
value: option.value,
|
||
|
|
}))}
|
||
|
|
initialIndex={toolOptions.findIndex(
|
||
|
|
(opt) => opt.value === selectedCategory,
|
||
|
|
)}
|
||
|
|
onSelect={handleSelect}
|
||
|
|
onHighlight={handleHighlight}
|
||
|
|
isFocused={true}
|
||
|
|
/>
|
||
|
|
</Box>
|
||
|
|
|
||
|
|
{/* Show help information or tools for selected category */}
|
||
|
|
{currentCategory && (
|
||
|
|
<Box flexDirection="column">
|
||
|
|
{currentCategory.id === 'all' ? (
|
||
|
|
<Text color={Colors.Gray}>
|
||
|
|
All tools selected, including MCP tools
|
||
|
|
</Text>
|
||
|
|
) : currentCategory.tools.length > 0 ? (
|
||
|
|
<>
|
||
|
|
<Text color={Colors.Gray}>Selected tools:</Text>
|
||
|
|
<Box flexDirection="column" marginLeft={2}>
|
||
|
|
{(() => {
|
||
|
|
// Filter the already categorized tools to show only those in current category
|
||
|
|
const categoryReadTools = currentCategory.tools.filter(
|
||
|
|
(tool) => readTools.includes(tool),
|
||
|
|
);
|
||
|
|
const categoryEditTools = currentCategory.tools.filter(
|
||
|
|
(tool) => editTools.includes(tool),
|
||
|
|
);
|
||
|
|
const categoryExecuteTools = currentCategory.tools.filter(
|
||
|
|
(tool) => executeTools.includes(tool),
|
||
|
|
);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<>
|
||
|
|
{categoryReadTools.length > 0 && (
|
||
|
|
<Text color={Colors.Gray}>
|
||
|
|
• Read-only tools: {categoryReadTools.join(', ')}
|
||
|
|
</Text>
|
||
|
|
)}
|
||
|
|
{categoryEditTools.length > 0 && (
|
||
|
|
<Text color={Colors.Gray}>
|
||
|
|
• Edit tools: {categoryEditTools.join(', ')}
|
||
|
|
</Text>
|
||
|
|
)}
|
||
|
|
{categoryExecuteTools.length > 0 && (
|
||
|
|
<Text color={Colors.Gray}>
|
||
|
|
• Execution tools: {categoryExecuteTools.join(', ')}
|
||
|
|
</Text>
|
||
|
|
)}
|
||
|
|
</>
|
||
|
|
);
|
||
|
|
})()}
|
||
|
|
</Box>
|
||
|
|
</>
|
||
|
|
) : null}
|
||
|
|
</Box>
|
||
|
|
)}
|
||
|
|
</Box>
|
||
|
|
);
|
||
|
|
}
|