mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-21 09:17:53 +00:00
174 lines
4.5 KiB
TypeScript
174 lines
4.5 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Qwen
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import { useCallback, useRef } from 'react';
|
|
import { Box, Text } from 'ink';
|
|
import Spinner from 'ink-spinner';
|
|
import { WizardStepProps, WizardAction } from '../types.js';
|
|
import { sanitizeInput } from '../utils.js';
|
|
import { Config, subagentGenerator } from '@qwen-code/qwen-code-core';
|
|
import { useKeypress, Key } from '../../../hooks/useKeypress.js';
|
|
import { keyMatchers, Command } from '../../../keyMatchers.js';
|
|
import { theme } from '../../../semantic-colors.js';
|
|
import { Colors } from '../../../colors.js';
|
|
import { TextInput } from '../../shared/TextInput.js';
|
|
|
|
/**
|
|
* Step 3: Description input with LLM generation.
|
|
*/
|
|
export function DescriptionInput({
|
|
state,
|
|
dispatch,
|
|
onNext,
|
|
config,
|
|
}: WizardStepProps) {
|
|
const abortControllerRef = useRef<AbortController | null>(null);
|
|
|
|
const handleTextChange = useCallback(
|
|
(text: string) => {
|
|
const sanitized = sanitizeInput(text);
|
|
dispatch({
|
|
type: 'SET_USER_DESCRIPTION',
|
|
description: sanitized,
|
|
});
|
|
},
|
|
[dispatch],
|
|
);
|
|
|
|
// TextInput will manage its own buffer; we just pass value and handlers
|
|
|
|
const handleGenerate = useCallback(
|
|
async (
|
|
userDescription: string,
|
|
dispatch: (action: WizardAction) => void,
|
|
config: Config,
|
|
): Promise<void> => {
|
|
const abortController = new AbortController();
|
|
abortControllerRef.current = abortController;
|
|
|
|
try {
|
|
const generated = await subagentGenerator(
|
|
userDescription,
|
|
config.getGeminiClient(),
|
|
abortController.signal,
|
|
);
|
|
|
|
// Only dispatch if not aborted
|
|
if (!abortController.signal.aborted) {
|
|
dispatch({
|
|
type: 'SET_GENERATED_CONTENT',
|
|
name: generated.name,
|
|
description: generated.description,
|
|
systemPrompt: generated.systemPrompt,
|
|
});
|
|
onNext();
|
|
}
|
|
} finally {
|
|
abortControllerRef.current = null;
|
|
}
|
|
},
|
|
[onNext],
|
|
);
|
|
|
|
const handleSubmit = useCallback(async () => {
|
|
if (!state.canProceed || state.isGenerating) {
|
|
return;
|
|
}
|
|
|
|
const inputValue = state.userDescription.trim();
|
|
if (!inputValue) {
|
|
return;
|
|
}
|
|
|
|
// Start LLM generation
|
|
dispatch({ type: 'SET_GENERATING', isGenerating: true });
|
|
|
|
try {
|
|
if (!config) {
|
|
throw new Error('Configuration not available');
|
|
}
|
|
|
|
// Use real LLM integration
|
|
await handleGenerate(inputValue, dispatch, config);
|
|
} catch (error) {
|
|
dispatch({ type: 'SET_GENERATING', isGenerating: false });
|
|
|
|
// Don't show error if it was cancelled by user
|
|
if (error instanceof Error && error.name === 'AbortError') {
|
|
return;
|
|
}
|
|
|
|
dispatch({
|
|
type: 'SET_VALIDATION_ERRORS',
|
|
errors: [
|
|
`Failed to generate subagent: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
],
|
|
});
|
|
}
|
|
}, [
|
|
state.canProceed,
|
|
state.isGenerating,
|
|
state.userDescription,
|
|
dispatch,
|
|
config,
|
|
handleGenerate,
|
|
]);
|
|
|
|
// Handle keyboard input during generation
|
|
const handleGenerationKeypress = useCallback(
|
|
(key: Key) => {
|
|
if (keyMatchers[Command.ESCAPE](key)) {
|
|
if (abortControllerRef.current) {
|
|
// Cancel the ongoing generation
|
|
abortControllerRef.current.abort();
|
|
dispatch({ type: 'SET_GENERATING', isGenerating: false });
|
|
}
|
|
}
|
|
},
|
|
[dispatch],
|
|
);
|
|
|
|
// Use separate keypress handlers for different states
|
|
useKeypress(handleGenerationKeypress, {
|
|
isActive: state.isGenerating,
|
|
});
|
|
|
|
const placeholder =
|
|
'e.g., Expert code reviewer that reviews code based on best practices...';
|
|
|
|
return (
|
|
<Box flexDirection="column" gap={1}>
|
|
<Box>
|
|
<Text color={Colors.Gray}>
|
|
Describe what this subagent should do and when it should be used. (Be
|
|
comprehensive for best results)
|
|
</Text>
|
|
</Box>
|
|
|
|
{state.isGenerating ? (
|
|
<Box>
|
|
<Box marginRight={1}>
|
|
<Spinner />
|
|
</Box>
|
|
<Text color={theme.text.accent}>
|
|
Generating subagent configuration...
|
|
</Text>
|
|
</Box>
|
|
) : (
|
|
<TextInput
|
|
value={state.userDescription || ''}
|
|
onChange={handleTextChange}
|
|
onSubmit={handleSubmit}
|
|
placeholder={placeholder}
|
|
height={10}
|
|
isActive={!state.isGenerating}
|
|
validationErrors={state.validationErrors}
|
|
/>
|
|
)}
|
|
</Box>
|
|
);
|
|
}
|