Update files
This commit is contained in:
@@ -101,6 +101,7 @@ export function AgentInputArea({
|
|||||||
const [isProcessing, setIsProcessing] = useState(false);
|
const [isProcessing, setIsProcessing] = useState(false);
|
||||||
const [selectedImages, setSelectedImages] = useState<ImageAttachment[]>([]);
|
const [selectedImages, setSelectedImages] = useState<ImageAttachment[]>([]);
|
||||||
const [isCancellingAgent, setIsCancellingAgent] = useState(false);
|
const [isCancellingAgent, setIsCancellingAgent] = useState(false);
|
||||||
|
const [sendError, setSendError] = useState<string | null>(null);
|
||||||
const [commandSelectedIndex, setCommandSelectedIndex] = useState(0);
|
const [commandSelectedIndex, setCommandSelectedIndex] = useState(0);
|
||||||
const lastHandledFocusRequestIdRef = useRef<number | null>(null);
|
const lastHandledFocusRequestIdRef = useRef<number | null>(null);
|
||||||
|
|
||||||
@@ -130,6 +131,13 @@ export function AgentInputArea({
|
|||||||
setCommandSelectedIndex(0);
|
setCommandSelectedIndex(0);
|
||||||
}, [commandFilter]);
|
}, [commandFilter]);
|
||||||
|
|
||||||
|
// Clear send error when user edits the input
|
||||||
|
useEffect(() => {
|
||||||
|
if (sendError && userInput) {
|
||||||
|
setSendError(null);
|
||||||
|
}
|
||||||
|
}, [userInput, sendError]);
|
||||||
|
|
||||||
const { pickImages } = useImageAttachmentPicker();
|
const { pickImages } = useImageAttachmentPicker();
|
||||||
const agentIdRef = useRef(agentId);
|
const agentIdRef = useRef(agentId);
|
||||||
const sendAgentMessageRef = useRef<
|
const sendAgentMessageRef = useRef<
|
||||||
@@ -256,28 +264,38 @@ export function AgentInputArea({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isControlledLocal = value !== undefined;
|
// Clear input optimistically before awaiting server ack.
|
||||||
setSelectedImages([]);
|
// Save values so we can restore on error.
|
||||||
setIsProcessing(true);
|
const savedImages = imageAttachments;
|
||||||
|
if (!onSubmitMessageRef.current) {
|
||||||
try {
|
if (value !== undefined) {
|
||||||
await submitMessage(trimmedMessage, imageAttachments);
|
|
||||||
// Clear input only after successful submission
|
|
||||||
// For controlled inputs with onSubmitMessage, the parent handles clearing
|
|
||||||
// because agent creation is async (WebSocket) and errors come back later
|
|
||||||
if (onSubmitMessageRef.current) {
|
|
||||||
// Parent manages input state - don't clear here
|
|
||||||
// Parent will clear on success via onChangeText
|
|
||||||
} else if (isControlledLocal) {
|
|
||||||
onChangeText?.("");
|
onChangeText?.("");
|
||||||
} else {
|
} else {
|
||||||
setUserInput("");
|
setUserInput("");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
setSelectedImages([]);
|
||||||
|
setSendError(null);
|
||||||
|
setIsProcessing(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await submitMessage(trimmedMessage, imageAttachments);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[AgentInput] Failed to send message:", error);
|
console.error("[AgentInput] Failed to send message:", error);
|
||||||
if (imageAttachments) {
|
// Restore input so the user never loses their message
|
||||||
setSelectedImages(imageAttachments);
|
if (!onSubmitMessageRef.current) {
|
||||||
|
if (value !== undefined) {
|
||||||
|
onChangeText?.(trimmedMessage);
|
||||||
|
} else {
|
||||||
|
setUserInput(trimmedMessage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (savedImages) {
|
||||||
|
setSelectedImages(savedImages);
|
||||||
|
}
|
||||||
|
setSendError(
|
||||||
|
error instanceof Error ? error.message : "Failed to send message"
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
setIsProcessing(false);
|
setIsProcessing(false);
|
||||||
}
|
}
|
||||||
@@ -449,7 +467,14 @@ export function AgentInputArea({
|
|||||||
|
|
||||||
// Cancels current agent run before sending queued prompt
|
// Cancels current agent run before sending queued prompt
|
||||||
handleCancelAgent();
|
handleCancelAgent();
|
||||||
await submitMessage(item.text, item.images);
|
try {
|
||||||
|
await submitMessage(item.text, item.images);
|
||||||
|
} catch (error) {
|
||||||
|
updateQueue((current) => [item, ...current]);
|
||||||
|
setSendError(
|
||||||
|
error instanceof Error ? error.message : "Failed to send message"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleQueue = useCallback((payload: MessagePayload) => {
|
const handleQueue = useCallback((payload: MessagePayload) => {
|
||||||
@@ -616,6 +641,10 @@ export function AgentInputArea({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{sendError && (
|
||||||
|
<Text style={styles.sendErrorText}>{sendError}</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* MessageInput handles everything: text, dictation, attachments, all buttons */}
|
{/* MessageInput handles everything: text, dictation, attachments, all buttons */}
|
||||||
<MessageInput
|
<MessageInput
|
||||||
ref={messageInputRef}
|
ref={messageInputRef}
|
||||||
@@ -623,7 +652,7 @@ export function AgentInputArea({
|
|||||||
onChangeText={setUserInput}
|
onChangeText={setUserInput}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
isSubmitDisabled={isProcessing || isSubmitLoading}
|
isSubmitDisabled={isProcessing || isSubmitLoading}
|
||||||
isSubmitLoading={isSubmitLoading}
|
isSubmitLoading={isProcessing || isSubmitLoading}
|
||||||
images={selectedImages}
|
images={selectedImages}
|
||||||
onPickImages={handlePickImage}
|
onPickImages={handlePickImage}
|
||||||
onRemoveImage={handleRemoveImage}
|
onRemoveImage={handleRemoveImage}
|
||||||
@@ -738,4 +767,8 @@ const styles = StyleSheet.create(((theme: Theme) => ({
|
|||||||
queueSendButton: {
|
queueSendButton: {
|
||||||
backgroundColor: theme.colors.palette.blue[600],
|
backgroundColor: theme.colors.palette.blue[600],
|
||||||
},
|
},
|
||||||
|
sendErrorText: {
|
||||||
|
color: theme.colors.palette.red[500],
|
||||||
|
fontSize: theme.fontSize.sm,
|
||||||
|
},
|
||||||
})) as any) as Record<string, any>;
|
})) as any) as Record<string, any>;
|
||||||
|
|||||||
@@ -826,8 +826,7 @@ function WorkingIndicator() {
|
|||||||
withTiming(1, { duration: bounceDuration }),
|
withTiming(1, { duration: bounceDuration }),
|
||||||
withTiming(0, { duration: bounceDuration })
|
withTiming(0, { duration: bounceDuration })
|
||||||
),
|
),
|
||||||
-1,
|
-1
|
||||||
true
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
View,
|
View,
|
||||||
TextInput,
|
TextInput,
|
||||||
Pressable,
|
Pressable,
|
||||||
|
ActivityIndicator,
|
||||||
NativeSyntheticEvent,
|
NativeSyntheticEvent,
|
||||||
TextInputContentSizeChangeEventData,
|
TextInputContentSizeChangeEventData,
|
||||||
TextInputKeyPressEventData,
|
TextInputKeyPressEventData,
|
||||||
@@ -533,7 +534,7 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
|||||||
|
|
||||||
const hasImages = images.length > 0;
|
const hasImages = images.length > 0;
|
||||||
const hasSendableContent = value.trim().length > 0 || hasImages;
|
const hasSendableContent = value.trim().length > 0 || hasImages;
|
||||||
const shouldShowSendButton = hasSendableContent;
|
const shouldShowSendButton = hasSendableContent || isSubmitLoading;
|
||||||
const isConnected = client?.isConnected ?? false;
|
const isConnected = client?.isConnected ?? false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -675,7 +676,11 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
|||||||
styles.buttonDisabled,
|
styles.buttonDisabled,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<ArrowUp size={20} color="white" />
|
{isSubmitLoading ? (
|
||||||
|
<ActivityIndicator size="small" color="white" />
|
||||||
|
) : (
|
||||||
|
<ArrowUp size={20} color="white" />
|
||||||
|
)}
|
||||||
</Pressable>
|
</Pressable>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
Reference in New Issue
Block a user