Update files
This commit is contained in:
@@ -31,6 +31,8 @@ import { encodeImages } from "@/utils/encode-images";
|
||||
import { useKeyboardNavStore } from "@/stores/keyboard-nav-store";
|
||||
import { focusWithRetries } from "@/utils/web-focus";
|
||||
import { useVoiceOptional } from "@/contexts/voice-context";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { Shortcut } from "@/components/ui/shortcut";
|
||||
|
||||
type QueuedMessage = {
|
||||
id: string;
|
||||
@@ -512,10 +514,23 @@ export function AgentInputArea({
|
||||
},
|
||||
[setUserInput]
|
||||
);
|
||||
const hasSendableContent = userInput.trim().length > 0 || selectedImages.length > 0;
|
||||
|
||||
// Handle keyboard navigation for command autocomplete
|
||||
const handleCommandKeyPress = useCallback(
|
||||
(event: { key: string; preventDefault: () => void }) => {
|
||||
if (
|
||||
event.key === "Escape" &&
|
||||
isAgentRunning &&
|
||||
!hasSendableContent &&
|
||||
!isCancellingAgent &&
|
||||
isConnected
|
||||
) {
|
||||
event.preventDefault();
|
||||
handleCancelAgent();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!showCommandAutocomplete || filteredCommands.length === 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -558,54 +573,72 @@ export function AgentInputArea({
|
||||
filteredCommands,
|
||||
commandSelectedIndex,
|
||||
handleCommandSelect,
|
||||
hasSendableContent,
|
||||
isAgentRunning,
|
||||
isCancellingAgent,
|
||||
isConnected,
|
||||
setUserInput,
|
||||
]
|
||||
);
|
||||
|
||||
const hasSendableContent = userInput.trim().length > 0 || selectedImages.length > 0;
|
||||
|
||||
const cancelButton = isAgentRunning && !hasSendableContent ? (
|
||||
<Pressable
|
||||
onPress={handleCancelAgent}
|
||||
disabled={!isConnected || isCancellingAgent}
|
||||
accessibilityLabel={isCancellingAgent ? "Canceling agent" : "Stop agent"}
|
||||
accessibilityRole="button"
|
||||
style={[
|
||||
styles.cancelButton as any,
|
||||
(!isConnected || isCancellingAgent
|
||||
? styles.buttonDisabled
|
||||
: undefined) as any,
|
||||
]}
|
||||
>
|
||||
{isCancellingAgent ? (
|
||||
<ActivityIndicator size="small" color="white" />
|
||||
) : (
|
||||
<Square size={18} color="white" fill="white" />
|
||||
)}
|
||||
</Pressable>
|
||||
<Tooltip delayDuration={0} enabledOnDesktop enabledOnMobile={false}>
|
||||
<TooltipTrigger
|
||||
onPress={handleCancelAgent}
|
||||
disabled={!isConnected || isCancellingAgent}
|
||||
accessibilityLabel={isCancellingAgent ? "Canceling agent" : "Stop agent"}
|
||||
accessibilityRole="button"
|
||||
style={[
|
||||
styles.cancelButton as any,
|
||||
(!isConnected || isCancellingAgent
|
||||
? styles.buttonDisabled
|
||||
: undefined) as any,
|
||||
]}
|
||||
>
|
||||
{isCancellingAgent ? (
|
||||
<ActivityIndicator size="small" color="white" />
|
||||
) : (
|
||||
<Square size={18} color="white" fill="white" />
|
||||
)}
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" align="center" offset={8}>
|
||||
<View style={styles.tooltipRow}>
|
||||
<Text style={styles.tooltipText}>Interrupt</Text>
|
||||
<Shortcut keys={["Esc"]} style={styles.tooltipShortcut} />
|
||||
</View>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : null;
|
||||
|
||||
const rightContent = (
|
||||
<View style={styles.rightControls}>
|
||||
{!isVoiceModeForAgent ? (
|
||||
<Pressable
|
||||
onPress={handleToggleRealtimeVoice}
|
||||
disabled={!isConnected || voice?.isVoiceSwitching}
|
||||
accessibilityLabel="Enable realtime voice mode"
|
||||
accessibilityRole="button"
|
||||
style={[
|
||||
styles.realtimeVoiceButton as any,
|
||||
(!isConnected || voice?.isVoiceSwitching
|
||||
? styles.buttonDisabled
|
||||
: undefined) as any,
|
||||
]}
|
||||
>
|
||||
{voice?.isVoiceSwitching ? (
|
||||
<ActivityIndicator size="small" color="white" />
|
||||
) : (
|
||||
<AudioLines size={18} color={theme.colors.foreground} />
|
||||
)}
|
||||
</Pressable>
|
||||
<Tooltip delayDuration={0} enabledOnDesktop enabledOnMobile={false}>
|
||||
<TooltipTrigger
|
||||
onPress={handleToggleRealtimeVoice}
|
||||
disabled={!isConnected || voice?.isVoiceSwitching}
|
||||
accessibilityLabel="Enable Voice mode"
|
||||
accessibilityRole="button"
|
||||
style={[
|
||||
styles.realtimeVoiceButton as any,
|
||||
(!isConnected || voice?.isVoiceSwitching
|
||||
? styles.buttonDisabled
|
||||
: undefined) as any,
|
||||
]}
|
||||
>
|
||||
{voice?.isVoiceSwitching ? (
|
||||
<ActivityIndicator size="small" color="white" />
|
||||
) : (
|
||||
<AudioLines size={18} color={theme.colors.foreground} />
|
||||
)}
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" align="center" offset={8}>
|
||||
<View style={styles.tooltipRow}>
|
||||
<Text style={styles.tooltipText}>Voice mode</Text>
|
||||
<Shortcut keys={["mod", "shift", "D"]} style={styles.tooltipShortcut} />
|
||||
</View>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : null}
|
||||
{cancelButton}
|
||||
</View>
|
||||
@@ -647,7 +680,7 @@ export function AgentInputArea({
|
||||
onPress={() => handleSendQueuedNow(item.id)}
|
||||
style={[styles.queueActionButton, styles.queueSendButton]}
|
||||
>
|
||||
<ArrowUp size={14} color={theme.colors.background} />
|
||||
<ArrowUp size={14} color="white" />
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
@@ -752,6 +785,19 @@ const styles = StyleSheet.create(((theme: Theme) => ({
|
||||
backgroundColor: theme.colors.palette.green[600],
|
||||
borderColor: theme.colors.palette.green[800],
|
||||
},
|
||||
tooltipRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[2],
|
||||
},
|
||||
tooltipText: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
color: theme.colors.popoverForeground,
|
||||
},
|
||||
tooltipShortcut: {
|
||||
backgroundColor: theme.colors.surface3,
|
||||
borderColor: theme.colors.borderAccent,
|
||||
},
|
||||
buttonDisabled: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
@@ -765,7 +811,7 @@ const styles = StyleSheet.create(((theme: Theme) => ({
|
||||
justifyContent: "space-between",
|
||||
paddingHorizontal: theme.spacing[3],
|
||||
paddingVertical: theme.spacing[2],
|
||||
backgroundColor: theme.colors.surface2,
|
||||
backgroundColor: theme.colors.surface1,
|
||||
borderRadius: theme.borderRadius.lg,
|
||||
borderWidth: theme.borderWidth[1],
|
||||
borderColor: theme.colors.border,
|
||||
@@ -790,7 +836,7 @@ const styles = StyleSheet.create(((theme: Theme) => ({
|
||||
backgroundColor: theme.colors.surface2,
|
||||
},
|
||||
queueSendButton: {
|
||||
backgroundColor: theme.colors.palette.blue[600],
|
||||
backgroundColor: theme.colors.accent,
|
||||
},
|
||||
sendErrorText: {
|
||||
color: theme.colors.palette.red[500],
|
||||
|
||||
@@ -377,14 +377,14 @@ const styles = StyleSheet.create((theme) => ({
|
||||
modeBadge: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
backgroundColor: theme.colors.surface2,
|
||||
backgroundColor: "transparent",
|
||||
gap: theme.spacing[1],
|
||||
paddingHorizontal: theme.spacing[2],
|
||||
paddingVertical: theme.spacing[1],
|
||||
borderRadius: theme.borderRadius["2xl"],
|
||||
},
|
||||
modeBadgeHovered: {
|
||||
backgroundColor: theme.colors.surface1,
|
||||
backgroundColor: theme.colors.surface2,
|
||||
},
|
||||
modeBadgePressed: {
|
||||
backgroundColor: theme.colors.surface0,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
TextInput,
|
||||
Pressable,
|
||||
ActivityIndicator,
|
||||
@@ -31,6 +32,8 @@ import { RealtimeVoiceOverlay } from "./realtime-voice-overlay";
|
||||
import type { DaemonClient } from "@server/client/daemon-client";
|
||||
import { usePanelStore } from "@/stores/panel-store";
|
||||
import { useVoiceOptional } from "@/contexts/voice-context";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { Shortcut } from "@/components/ui/shortcut";
|
||||
|
||||
export interface ImageAttachment {
|
||||
uri: string;
|
||||
@@ -217,6 +220,7 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
||||
const socketConnected = client?.isConnected ?? false;
|
||||
return socketConnected;
|
||||
}, [client]);
|
||||
const isConnected = client?.isConnected ?? false;
|
||||
|
||||
const {
|
||||
isRecording: isDictating,
|
||||
@@ -263,9 +267,39 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
||||
sendAfterTranscriptRef.current = false;
|
||||
}, [dictationStatus, isDictating, isDictationProcessing]);
|
||||
|
||||
// Cmd+D to start/submit dictation, Escape to cancel
|
||||
// Cmd+D to start/submit dictation, Cmd+Shift+D toggles realtime voice, Escape cancels dictation
|
||||
useEffect(() => {
|
||||
if (!IS_WEB) return;
|
||||
const toggleRealtimeVoice = () => {
|
||||
if (!voice || !voiceServerId || !voiceAgentId || !isConnected || disabled) {
|
||||
return;
|
||||
}
|
||||
if (voice.isVoiceSwitching) {
|
||||
return;
|
||||
}
|
||||
if (voice.isVoiceModeForAgent(voiceServerId, voiceAgentId)) {
|
||||
const tasks: Promise<unknown>[] = [];
|
||||
if (isAgentRunning && client) {
|
||||
tasks.push(client.cancelAgent(voiceAgentId));
|
||||
}
|
||||
tasks.push(voice.stopVoice());
|
||||
void Promise.allSettled(tasks).then((results) => {
|
||||
results.forEach((result) => {
|
||||
if (result.status === "rejected") {
|
||||
console.error(
|
||||
"[MessageInput] Failed to stop realtime voice",
|
||||
result.reason
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
void voice.startVoice(voiceServerId, voiceAgentId).catch((error) => {
|
||||
console.error("[MessageInput] Failed to start realtime voice", error);
|
||||
});
|
||||
};
|
||||
|
||||
const resolveNativeInput = (): unknown => {
|
||||
const current = textInputRef.current as any;
|
||||
if (!current) return null;
|
||||
@@ -280,12 +314,32 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
||||
if (!isScreenFocused && !isInputFocusedRef.current && !isFromInput) {
|
||||
return;
|
||||
}
|
||||
const isMod = event.metaKey || event.ctrlKey;
|
||||
const isKeyD = event.code === "KeyD" || event.key.toLowerCase() === "d";
|
||||
|
||||
if (isMod && event.shiftKey && isKeyD && !event.repeat) {
|
||||
event.preventDefault();
|
||||
toggleRealtimeVoice();
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
isRealtimeVoiceForCurrentAgent &&
|
||||
!event.metaKey &&
|
||||
!event.ctrlKey &&
|
||||
!event.altKey &&
|
||||
!event.shiftKey &&
|
||||
(event.code === "Space" || event.key === " ") &&
|
||||
!event.repeat
|
||||
) {
|
||||
event.preventDefault();
|
||||
voice?.toggleMute();
|
||||
return;
|
||||
}
|
||||
|
||||
const dictating = isDictatingRef.current;
|
||||
// Cmd+D: start dictation or submit if already dictating
|
||||
if (
|
||||
(event.metaKey || event.ctrlKey) &&
|
||||
(event.code === "KeyD" || event.key.toLowerCase() === "d")
|
||||
) {
|
||||
if (isMod && isKeyD) {
|
||||
event.preventDefault();
|
||||
if (dictating) {
|
||||
sendAfterTranscriptRef.current = true;
|
||||
@@ -303,7 +357,20 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
||||
}
|
||||
window.addEventListener("keydown", handleKeyDown);
|
||||
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||
}, [cancelDictation, confirmDictation, isScreenFocused, startDictation]);
|
||||
}, [
|
||||
cancelDictation,
|
||||
client,
|
||||
confirmDictation,
|
||||
disabled,
|
||||
isAgentRunning,
|
||||
isConnected,
|
||||
isRealtimeVoiceForCurrentAgent,
|
||||
isScreenFocused,
|
||||
startDictation,
|
||||
voiceAgentId,
|
||||
voiceServerId,
|
||||
voice,
|
||||
]);
|
||||
|
||||
// Animate overlay
|
||||
useEffect(() => {
|
||||
@@ -381,6 +448,29 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
||||
});
|
||||
}, [client, isAgentRunning, isRealtimeVoiceForCurrentAgent, voice, voiceAgentId]);
|
||||
|
||||
const handleToggleRealtimeVoiceShortcut = useCallback(() => {
|
||||
if (!voice || !voiceServerId || !voiceAgentId || !isConnected || disabled) {
|
||||
return;
|
||||
}
|
||||
if (voice.isVoiceSwitching) {
|
||||
return;
|
||||
}
|
||||
if (voice.isVoiceModeForAgent(voiceServerId, voiceAgentId)) {
|
||||
void handleStopRealtimeVoice();
|
||||
return;
|
||||
}
|
||||
void voice.startVoice(voiceServerId, voiceAgentId).catch((error) => {
|
||||
console.error("[MessageInput] Failed to start realtime voice", error);
|
||||
});
|
||||
}, [
|
||||
disabled,
|
||||
handleStopRealtimeVoice,
|
||||
isConnected,
|
||||
voice,
|
||||
voiceAgentId,
|
||||
voiceServerId,
|
||||
]);
|
||||
|
||||
const handleSendMessage = useCallback(() => {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed && images.length === 0) return;
|
||||
@@ -514,6 +604,13 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
||||
return;
|
||||
}
|
||||
|
||||
// Cmd+Shift+D or Ctrl+Shift+D: toggle realtime voice mode
|
||||
if ((metaKey || ctrlKey) && shiftKey && key === "d") {
|
||||
event.preventDefault();
|
||||
handleToggleRealtimeVoiceShortcut();
|
||||
return;
|
||||
}
|
||||
|
||||
// Cmd+D or Ctrl+D: start dictation or submit if already dictating
|
||||
if ((metaKey || ctrlKey) && key === "d") {
|
||||
event.preventDefault();
|
||||
@@ -533,6 +630,18 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
isRealtimeVoiceForCurrentAgent &&
|
||||
!metaKey &&
|
||||
!ctrlKey &&
|
||||
!shiftKey &&
|
||||
event.nativeEvent.key === " "
|
||||
) {
|
||||
event.preventDefault();
|
||||
voice?.toggleMute();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.nativeEvent.key !== "Enter") return;
|
||||
|
||||
// Shift+Enter: add newline (default behavior, don't intercept)
|
||||
@@ -555,7 +664,6 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
||||
const hasImages = images.length > 0;
|
||||
const hasSendableContent = value.trim().length > 0 || hasImages;
|
||||
const shouldShowSendButton = hasSendableContent || isSubmitLoading;
|
||||
const isConnected = client?.isConnected ?? false;
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
@@ -636,90 +744,130 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(
|
||||
{/* Left: attachment button + leftContent slot */}
|
||||
<View style={styles.leftButtonGroup}>
|
||||
{onPickImages && (
|
||||
<Pressable
|
||||
onPress={onPickImages}
|
||||
disabled={!isConnected || disabled}
|
||||
style={[
|
||||
styles.attachButton,
|
||||
(!isConnected || disabled) && styles.buttonDisabled,
|
||||
]}
|
||||
>
|
||||
<Paperclip size={20} color={theme.colors.foreground} />
|
||||
</Pressable>
|
||||
<Tooltip delayDuration={0} enabledOnDesktop enabledOnMobile={false}>
|
||||
<TooltipTrigger
|
||||
onPress={onPickImages}
|
||||
disabled={!isConnected || disabled}
|
||||
accessibilityLabel="Attach images"
|
||||
accessibilityRole="button"
|
||||
style={[
|
||||
styles.attachButton,
|
||||
(!isConnected || disabled) && styles.buttonDisabled,
|
||||
]}
|
||||
>
|
||||
<Paperclip size={20} color={theme.colors.foreground} />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" align="center" offset={8}>
|
||||
<Text style={styles.tooltipText}>Attach images</Text>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
{leftContent}
|
||||
</View>
|
||||
|
||||
{/* Right: voice button, contextual button (realtime/send/cancel) */}
|
||||
<View style={styles.rightButtonGroup}>
|
||||
<Pressable
|
||||
onPress={handleVoicePress}
|
||||
disabled={!isConnected || disabled}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={
|
||||
isRealtimeVoiceForCurrentAgent
|
||||
? voice?.isMuted
|
||||
? "Unmute realtime voice"
|
||||
: "Mute realtime voice"
|
||||
: isDictating
|
||||
? "Stop dictation"
|
||||
: "Start dictation"
|
||||
}
|
||||
style={[
|
||||
styles.voiceButton,
|
||||
(!isConnected || disabled) && styles.buttonDisabled,
|
||||
isDictating && styles.voiceButtonRecording,
|
||||
]}
|
||||
>
|
||||
{isDictating ? (
|
||||
<Square size={14} color="white" fill="white" />
|
||||
) : isRealtimeVoiceForCurrentAgent && voice?.isMuted ? (
|
||||
<MicOff size={20} color={theme.colors.foreground} />
|
||||
) : (
|
||||
<Mic size={20} color={theme.colors.foreground} />
|
||||
)}
|
||||
</Pressable>
|
||||
<Tooltip delayDuration={0} enabledOnDesktop enabledOnMobile={false}>
|
||||
<TooltipTrigger
|
||||
onPress={handleVoicePress}
|
||||
disabled={!isConnected || disabled}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={
|
||||
isRealtimeVoiceForCurrentAgent
|
||||
? voice?.isMuted
|
||||
? "Unmute Voice mode"
|
||||
: "Mute Voice mode"
|
||||
: isDictating
|
||||
? "Stop dictation"
|
||||
: "Start dictation"
|
||||
}
|
||||
style={[
|
||||
styles.voiceButton,
|
||||
(!isConnected || disabled) && styles.buttonDisabled,
|
||||
isDictating && styles.voiceButtonRecording,
|
||||
]}
|
||||
>
|
||||
{isDictating ? (
|
||||
<Square size={14} color="white" fill="white" />
|
||||
) : isRealtimeVoiceForCurrentAgent && voice?.isMuted ? (
|
||||
<MicOff size={20} color={theme.colors.foreground} />
|
||||
) : (
|
||||
<Mic size={20} color={theme.colors.foreground} />
|
||||
)}
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" align="center" offset={8}>
|
||||
<View style={styles.tooltipRow}>
|
||||
<Text style={styles.tooltipText}>
|
||||
{isRealtimeVoiceForCurrentAgent
|
||||
? voice?.isMuted
|
||||
? "Unmute voice"
|
||||
: "Mute voice"
|
||||
: "Dictation"}
|
||||
</Text>
|
||||
<Shortcut
|
||||
keys={isRealtimeVoiceForCurrentAgent ? ["Space"] : ["mod", "D"]}
|
||||
style={styles.tooltipShortcut}
|
||||
/>
|
||||
</View>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
{rightContent}
|
||||
{shouldShowSendButton && isAgentRunning && onQueue && (
|
||||
<Pressable
|
||||
onPress={handleQueueMessage}
|
||||
disabled={!isConnected || disabled}
|
||||
accessibilityLabel="Queue message"
|
||||
accessibilityRole="button"
|
||||
style={[
|
||||
styles.queueButton,
|
||||
(!isConnected || disabled) && styles.buttonDisabled,
|
||||
]}
|
||||
>
|
||||
<Plus size={20} color="white" />
|
||||
</Pressable>
|
||||
<Tooltip delayDuration={0} enabledOnDesktop enabledOnMobile={false}>
|
||||
<TooltipTrigger
|
||||
onPress={handleQueueMessage}
|
||||
disabled={!isConnected || disabled}
|
||||
accessibilityLabel="Queue message"
|
||||
accessibilityRole="button"
|
||||
style={[
|
||||
styles.queueButton,
|
||||
(!isConnected || disabled) && styles.buttonDisabled,
|
||||
]}
|
||||
>
|
||||
<Plus size={20} color="white" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" align="center" offset={8}>
|
||||
<View style={styles.tooltipRow}>
|
||||
<Text style={styles.tooltipText}>Queue</Text>
|
||||
<Shortcut keys={["mod", "Enter"]} style={styles.tooltipShortcut} />
|
||||
</View>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
{shouldShowSendButton && (
|
||||
<Pressable
|
||||
onPress={handleSendMessage}
|
||||
disabled={
|
||||
!isConnected ||
|
||||
isSubmitDisabled ||
|
||||
isSubmitLoading ||
|
||||
disabled
|
||||
}
|
||||
accessibilityLabel={isAgentRunning ? "Send and interrupt" : "Send message"}
|
||||
accessibilityRole="button"
|
||||
style={[
|
||||
styles.sendButton,
|
||||
(!isConnected ||
|
||||
<Tooltip delayDuration={0} enabledOnDesktop enabledOnMobile={false}>
|
||||
<TooltipTrigger
|
||||
onPress={handleSendMessage}
|
||||
disabled={
|
||||
!isConnected ||
|
||||
isSubmitDisabled ||
|
||||
isSubmitLoading ||
|
||||
disabled) &&
|
||||
styles.buttonDisabled,
|
||||
]}
|
||||
>
|
||||
{isSubmitLoading ? (
|
||||
<ActivityIndicator size="small" color="white" />
|
||||
) : (
|
||||
<ArrowUp size={20} color="white" />
|
||||
)}
|
||||
</Pressable>
|
||||
disabled
|
||||
}
|
||||
accessibilityLabel={isAgentRunning ? "Send and interrupt" : "Send message"}
|
||||
accessibilityRole="button"
|
||||
style={[
|
||||
styles.sendButton,
|
||||
(!isConnected ||
|
||||
isSubmitDisabled ||
|
||||
isSubmitLoading ||
|
||||
disabled) &&
|
||||
styles.buttonDisabled,
|
||||
]}
|
||||
>
|
||||
{isSubmitLoading ? (
|
||||
<ActivityIndicator size="small" color="white" />
|
||||
) : (
|
||||
<ArrowUp size={20} color="white" />
|
||||
)}
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" align="center" offset={8}>
|
||||
<View style={styles.tooltipRow}>
|
||||
<Text style={styles.tooltipText}>Send</Text>
|
||||
<Shortcut keys={["Enter"]} style={styles.tooltipShortcut} />
|
||||
</View>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
@@ -775,7 +923,7 @@ const styles = StyleSheet.create(((theme: any) => ({
|
||||
inputWrapper: {
|
||||
flexDirection: "column",
|
||||
gap: theme.spacing[3],
|
||||
backgroundColor: theme.colors.surface2,
|
||||
backgroundColor: theme.colors.surface1,
|
||||
borderWidth: theme.borderWidth[1],
|
||||
borderColor: theme.colors.borderAccent,
|
||||
borderRadius: theme.borderRadius["2xl"],
|
||||
@@ -898,6 +1046,19 @@ const styles = StyleSheet.create(((theme: any) => ({
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
tooltipRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[2],
|
||||
},
|
||||
tooltipText: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
color: theme.colors.popoverForeground,
|
||||
},
|
||||
tooltipShortcut: {
|
||||
backgroundColor: theme.colors.surface3,
|
||||
borderColor: theme.colors.borderAccent,
|
||||
},
|
||||
buttonDisabled: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
|
||||
@@ -463,9 +463,9 @@ const styles = StyleSheet.create((theme) => ({
|
||||
content: {
|
||||
paddingVertical: theme.spacing[1],
|
||||
paddingHorizontal: theme.spacing[2],
|
||||
borderRadius: theme.borderRadius.lg,
|
||||
borderRadius: theme.borderRadius.xl,
|
||||
backgroundColor: theme.colors.popover,
|
||||
borderWidth: theme.borderWidth[1],
|
||||
borderWidth: theme.borderWidth[2],
|
||||
borderColor: theme.colors.border,
|
||||
shadowColor: "#000",
|
||||
shadowOpacity: 0.2,
|
||||
|
||||
Reference in New Issue
Block a user