mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-06-11 16:23:03 +08:00
refactor(useChatComposerState): improve input handling and command execution flow
This commit is contained in:
@@ -129,6 +129,7 @@ export function useChatComposerState({
|
|||||||
const handleSubmitRef = useRef<
|
const handleSubmitRef = useRef<
|
||||||
((event: FormEvent<HTMLFormElement> | MouseEvent | TouchEvent | KeyboardEvent<HTMLTextAreaElement>) => Promise<void>) | null
|
((event: FormEvent<HTMLFormElement> | MouseEvent | TouchEvent | KeyboardEvent<HTMLTextAreaElement>) => Promise<void>) | null
|
||||||
>(null);
|
>(null);
|
||||||
|
const inputValueRef = useRef(input);
|
||||||
|
|
||||||
const handleBuiltInCommand = useCallback(
|
const handleBuiltInCommand = useCallback(
|
||||||
(result: CommandExecutionResult) => {
|
(result: CommandExecutionResult) => {
|
||||||
@@ -258,13 +259,16 @@ export function useChatComposerState({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setInput(content || '');
|
const commandContent = content || '';
|
||||||
|
setInput(commandContent);
|
||||||
|
inputValueRef.current = commandContent;
|
||||||
|
|
||||||
|
// Defer submit to next tick so the command text is reflected in UI before dispatching.
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (handleSubmitRef.current) {
|
if (handleSubmitRef.current) {
|
||||||
handleSubmitRef.current(createFakeSubmitEvent());
|
handleSubmitRef.current(createFakeSubmitEvent());
|
||||||
}
|
}
|
||||||
}, 50);
|
}, 0);
|
||||||
}, [setChatMessages]);
|
}, [setChatMessages]);
|
||||||
|
|
||||||
const executeCommand = useCallback(
|
const executeCommand = useCallback(
|
||||||
@@ -279,7 +283,7 @@ export function useChatComposerState({
|
|||||||
commandMatch && commandMatch[1] ? commandMatch[1].trim().split(/\s+/) : [];
|
commandMatch && commandMatch[1] ? commandMatch[1].trim().split(/\s+/) : [];
|
||||||
|
|
||||||
const context = {
|
const context = {
|
||||||
projectPath: selectedProject.path,
|
projectPath: selectedProject.fullPath || selectedProject.path,
|
||||||
projectName: selectedProject.name,
|
projectName: selectedProject.name,
|
||||||
sessionId: currentSessionId,
|
sessionId: currentSessionId,
|
||||||
provider,
|
provider,
|
||||||
@@ -314,11 +318,11 @@ export function useChatComposerState({
|
|||||||
const result = (await response.json()) as CommandExecutionResult;
|
const result = (await response.json()) as CommandExecutionResult;
|
||||||
if (result.type === 'builtin') {
|
if (result.type === 'builtin') {
|
||||||
handleBuiltInCommand(result);
|
handleBuiltInCommand(result);
|
||||||
|
setInput('');
|
||||||
|
inputValueRef.current = '';
|
||||||
} else if (result.type === 'custom') {
|
} else if (result.type === 'custom') {
|
||||||
await handleCustomCommand(result);
|
await handleCustomCommand(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
setInput('');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = error instanceof Error ? error.message : 'Unknown error';
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
||||||
console.error('Error executing command:', error);
|
console.error('Error executing command:', error);
|
||||||
@@ -465,14 +469,15 @@ export function useChatComposerState({
|
|||||||
event: FormEvent<HTMLFormElement> | MouseEvent | TouchEvent | KeyboardEvent<HTMLTextAreaElement>,
|
event: FormEvent<HTMLFormElement> | MouseEvent | TouchEvent | KeyboardEvent<HTMLTextAreaElement>,
|
||||||
) => {
|
) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (!input.trim() || isLoading || !selectedProject) {
|
const currentInput = inputValueRef.current;
|
||||||
|
if (!currentInput.trim() || isLoading || !selectedProject) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let messageContent = input;
|
let messageContent = currentInput;
|
||||||
const selectedThinkingMode = thinkingModes.find((mode: { id: string; prefix?: string }) => mode.id === thinkingMode);
|
const selectedThinkingMode = thinkingModes.find((mode: { id: string; prefix?: string }) => mode.id === thinkingMode);
|
||||||
if (selectedThinkingMode && selectedThinkingMode.prefix) {
|
if (selectedThinkingMode && selectedThinkingMode.prefix) {
|
||||||
messageContent = `${selectedThinkingMode.prefix}: ${input}`;
|
messageContent = `${selectedThinkingMode.prefix}: ${currentInput}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
let uploadedImages: unknown[] = [];
|
let uploadedImages: unknown[] = [];
|
||||||
@@ -512,7 +517,7 @@ export function useChatComposerState({
|
|||||||
|
|
||||||
const userMessage: ChatMessage = {
|
const userMessage: ChatMessage = {
|
||||||
type: 'user',
|
type: 'user',
|
||||||
content: input,
|
content: currentInput,
|
||||||
images: uploadedImages as any,
|
images: uploadedImages as any,
|
||||||
timestamp: new Date(),
|
timestamp: new Date(),
|
||||||
};
|
};
|
||||||
@@ -566,6 +571,7 @@ export function useChatComposerState({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const toolsSettings = getToolsSettings();
|
const toolsSettings = getToolsSettings();
|
||||||
|
const resolvedProjectPath = selectedProject.fullPath || selectedProject.path || '';
|
||||||
|
|
||||||
if (provider === 'cursor') {
|
if (provider === 'cursor') {
|
||||||
sendMessage({
|
sendMessage({
|
||||||
@@ -573,8 +579,8 @@ export function useChatComposerState({
|
|||||||
command: messageContent,
|
command: messageContent,
|
||||||
sessionId: effectiveSessionId,
|
sessionId: effectiveSessionId,
|
||||||
options: {
|
options: {
|
||||||
cwd: selectedProject.fullPath || selectedProject.path,
|
cwd: resolvedProjectPath,
|
||||||
projectPath: selectedProject.fullPath || selectedProject.path,
|
projectPath: resolvedProjectPath,
|
||||||
sessionId: effectiveSessionId,
|
sessionId: effectiveSessionId,
|
||||||
resume: Boolean(effectiveSessionId),
|
resume: Boolean(effectiveSessionId),
|
||||||
model: cursorModel,
|
model: cursorModel,
|
||||||
@@ -588,8 +594,8 @@ export function useChatComposerState({
|
|||||||
command: messageContent,
|
command: messageContent,
|
||||||
sessionId: effectiveSessionId,
|
sessionId: effectiveSessionId,
|
||||||
options: {
|
options: {
|
||||||
cwd: selectedProject.fullPath || selectedProject.path,
|
cwd: resolvedProjectPath,
|
||||||
projectPath: selectedProject.fullPath || selectedProject.path,
|
projectPath: resolvedProjectPath,
|
||||||
sessionId: effectiveSessionId,
|
sessionId: effectiveSessionId,
|
||||||
resume: Boolean(effectiveSessionId),
|
resume: Boolean(effectiveSessionId),
|
||||||
model: codexModel,
|
model: codexModel,
|
||||||
@@ -601,10 +607,10 @@ export function useChatComposerState({
|
|||||||
type: 'claude-command',
|
type: 'claude-command',
|
||||||
command: messageContent,
|
command: messageContent,
|
||||||
options: {
|
options: {
|
||||||
projectPath: selectedProject.path,
|
projectPath: resolvedProjectPath,
|
||||||
cwd: selectedProject.fullPath,
|
cwd: resolvedProjectPath,
|
||||||
sessionId: currentSessionId,
|
sessionId: effectiveSessionId,
|
||||||
resume: Boolean(currentSessionId),
|
resume: Boolean(effectiveSessionId),
|
||||||
toolsSettings,
|
toolsSettings,
|
||||||
permissionMode,
|
permissionMode,
|
||||||
model: claudeModel,
|
model: claudeModel,
|
||||||
@@ -614,6 +620,7 @@ export function useChatComposerState({
|
|||||||
}
|
}
|
||||||
|
|
||||||
setInput('');
|
setInput('');
|
||||||
|
inputValueRef.current = '';
|
||||||
resetCommandMenuState();
|
resetCommandMenuState();
|
||||||
setAttachedImages([]);
|
setAttachedImages([]);
|
||||||
setUploadingImages(new Map());
|
setUploadingImages(new Map());
|
||||||
@@ -633,7 +640,6 @@ export function useChatComposerState({
|
|||||||
codexModel,
|
codexModel,
|
||||||
currentSessionId,
|
currentSessionId,
|
||||||
cursorModel,
|
cursorModel,
|
||||||
input,
|
|
||||||
isLoading,
|
isLoading,
|
||||||
onSessionActive,
|
onSessionActive,
|
||||||
pendingViewSessionRef,
|
pendingViewSessionRef,
|
||||||
@@ -657,12 +663,20 @@ export function useChatComposerState({
|
|||||||
handleSubmitRef.current = handleSubmit;
|
handleSubmitRef.current = handleSubmit;
|
||||||
}, [handleSubmit]);
|
}, [handleSubmit]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
inputValueRef.current = input;
|
||||||
|
}, [input]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selectedProject) {
|
if (!selectedProject) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const savedInput = safeLocalStorage.getItem(`draft_input_${selectedProject.name}`) || '';
|
const savedInput = safeLocalStorage.getItem(`draft_input_${selectedProject.name}`) || '';
|
||||||
setInput((previous) => (previous === savedInput ? previous : savedInput));
|
setInput((previous) => {
|
||||||
|
const next = previous === savedInput ? previous : savedInput;
|
||||||
|
inputValueRef.current = next;
|
||||||
|
return next;
|
||||||
|
});
|
||||||
}, [selectedProject?.name]);
|
}, [selectedProject?.name]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -680,12 +694,13 @@ export function useChatComposerState({
|
|||||||
if (!textareaRef.current) {
|
if (!textareaRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Re-run when input changes so restored drafts get the same autosize behavior as typed text.
|
||||||
textareaRef.current.style.height = 'auto';
|
textareaRef.current.style.height = 'auto';
|
||||||
textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;
|
textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;
|
||||||
const lineHeight = parseInt(window.getComputedStyle(textareaRef.current).lineHeight);
|
const lineHeight = parseInt(window.getComputedStyle(textareaRef.current).lineHeight);
|
||||||
const expanded = textareaRef.current.scrollHeight > lineHeight * 2;
|
const expanded = textareaRef.current.scrollHeight > lineHeight * 2;
|
||||||
setIsTextareaExpanded(expanded);
|
setIsTextareaExpanded(expanded);
|
||||||
}, []);
|
}, [input]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!textareaRef.current || input.trim()) {
|
if (!textareaRef.current || input.trim()) {
|
||||||
@@ -701,6 +716,7 @@ export function useChatComposerState({
|
|||||||
const cursorPos = event.target.selectionStart;
|
const cursorPos = event.target.selectionStart;
|
||||||
|
|
||||||
setInput(newValue);
|
setInput(newValue);
|
||||||
|
inputValueRef.current = newValue;
|
||||||
setCursorPosition(cursorPos);
|
setCursorPosition(cursorPos);
|
||||||
|
|
||||||
if (!newValue.trim()) {
|
if (!newValue.trim()) {
|
||||||
@@ -779,6 +795,7 @@ export function useChatComposerState({
|
|||||||
|
|
||||||
const handleClearInput = useCallback(() => {
|
const handleClearInput = useCallback(() => {
|
||||||
setInput('');
|
setInput('');
|
||||||
|
inputValueRef.current = '';
|
||||||
resetCommandMenuState();
|
resetCommandMenuState();
|
||||||
if (textareaRef.current) {
|
if (textareaRef.current) {
|
||||||
textareaRef.current.style.height = 'auto';
|
textareaRef.current.style.height = 'auto';
|
||||||
@@ -827,6 +844,7 @@ export function useChatComposerState({
|
|||||||
|
|
||||||
setInput((previousInput) => {
|
setInput((previousInput) => {
|
||||||
const newInput = previousInput.trim() ? `${previousInput} ${text}` : text;
|
const newInput = previousInput.trim() ? `${previousInput} ${text}` : text;
|
||||||
|
inputValueRef.current = newInput;
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!textareaRef.current) {
|
if (!textareaRef.current) {
|
||||||
|
|||||||
Reference in New Issue
Block a user