mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-09 01:45:00 +00:00
chore(lint): no-nested-ternary in app batch 1 (18 files)
This commit is contained in:
@@ -649,17 +649,14 @@ const AgentStreamViewComponent = forwardRef<AgentStreamViewHandle, AgentStreamVi
|
||||
renderStreamItem(item, index, items, index === 0 ? lastHistoryItem : null),
|
||||
[lastHistoryItem, renderStreamItem],
|
||||
);
|
||||
const liveAuxiliaryHeaderStyle = useMemo(
|
||||
() => [
|
||||
stylesheet.listHeaderContent,
|
||||
boundary.hasLiveHead
|
||||
? streamRenderStrategy.getFlatListInverted()
|
||||
? { paddingBottom: looseGap }
|
||||
: { paddingTop: looseGap }
|
||||
: null,
|
||||
],
|
||||
[boundary.hasLiveHead, streamRenderStrategy, looseGap],
|
||||
);
|
||||
const liveAuxiliaryHeaderStyle = useMemo(() => {
|
||||
let headerPadding: { paddingBottom: number } | { paddingTop: number } | null;
|
||||
if (!boundary.hasLiveHead) headerPadding = null;
|
||||
else if (streamRenderStrategy.getFlatListInverted())
|
||||
headerPadding = { paddingBottom: looseGap };
|
||||
else headerPadding = { paddingTop: looseGap };
|
||||
return [stylesheet.listHeaderContent, headerPadding];
|
||||
}, [boundary.hasLiveHead, streamRenderStrategy, looseGap]);
|
||||
const renderLiveAuxiliary = useCallback<StreamSegmentRenderers["renderLiveAuxiliary"]>(() => {
|
||||
if (!auxiliary.pendingPermissions && !auxiliary.workingIndicator) {
|
||||
return null;
|
||||
@@ -1080,12 +1077,11 @@ function PermissionRequestCard({
|
||||
const textColor = isPrimary ? theme.colors.foreground : theme.colors.foregroundMuted;
|
||||
const iconColor = textColor;
|
||||
const Icon = action.behavior === "allow" ? Check : X;
|
||||
const testID =
|
||||
action.behavior === "deny"
|
||||
? "permission-request-deny"
|
||||
: action.id === "accept" || action.id === "implement"
|
||||
? "permission-request-accept"
|
||||
: `permission-request-action-${action.id}`;
|
||||
let testID: string;
|
||||
if (action.behavior === "deny") testID = "permission-request-deny";
|
||||
else if (action.id === "accept" || action.id === "implement")
|
||||
testID = "permission-request-accept";
|
||||
else testID = `permission-request-action-${action.id}`;
|
||||
|
||||
return (
|
||||
<PermissionActionButton
|
||||
|
||||
@@ -83,12 +83,11 @@ export function CalloutCard({
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
{hasDescription ? (
|
||||
typeof description === "string" ? (
|
||||
<CalloutDescriptionText>{description}</CalloutDescriptionText>
|
||||
) : (
|
||||
<View style={styles.descriptionSlot}>{description}</View>
|
||||
)
|
||||
{hasDescription && typeof description === "string" ? (
|
||||
<CalloutDescriptionText>{description}</CalloutDescriptionText>
|
||||
) : null}
|
||||
{hasDescription && typeof description !== "string" ? (
|
||||
<View style={styles.descriptionSlot}>{description}</View>
|
||||
) : null}
|
||||
|
||||
{visibleActions.length > 0 ? (
|
||||
|
||||
@@ -98,7 +98,8 @@ export function DictationControls({
|
||||
<View style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="small" color={theme.colors.foreground} />
|
||||
</View>
|
||||
) : isFailed ? (
|
||||
) : null}
|
||||
{!actionsDisabled && isFailed ? (
|
||||
<Pressable
|
||||
onPress={onRetry}
|
||||
accessibilityLabel="Retry dictation"
|
||||
@@ -106,7 +107,8 @@ export function DictationControls({
|
||||
>
|
||||
<RefreshCcw size={theme.iconSize.sm} color={theme.colors.surface0} />
|
||||
</Pressable>
|
||||
) : (
|
||||
) : null}
|
||||
{!actionsDisabled && !isFailed ? (
|
||||
<>
|
||||
<Pressable
|
||||
onPress={onAccept}
|
||||
@@ -123,7 +125,7 @@ export function DictationControls({
|
||||
<ArrowUp size={theme.iconSize.sm} color={theme.colors.surface0} />
|
||||
</Pressable>
|
||||
</>
|
||||
)}
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
@@ -216,7 +218,8 @@ export function DictationOverlay({
|
||||
<View style={overlayStyles.loadingContainer}>
|
||||
<ActivityIndicator size="small" color={theme.colors.palette.white} />
|
||||
</View>
|
||||
) : isFailed ? (
|
||||
) : null}
|
||||
{!actionsDisabled && isFailed ? (
|
||||
<Pressable
|
||||
onPress={onRetry}
|
||||
accessibilityRole="button"
|
||||
@@ -225,7 +228,8 @@ export function DictationOverlay({
|
||||
>
|
||||
<RefreshCcw size={theme.iconSize.lg} color={theme.colors.accent} strokeWidth={2.5} />
|
||||
</Pressable>
|
||||
) : (
|
||||
) : null}
|
||||
{!actionsDisabled && !isFailed ? (
|
||||
<>
|
||||
<Pressable
|
||||
onPress={onAccept}
|
||||
@@ -248,7 +252,7 @@ export function DictationOverlay({
|
||||
<ArrowUp size={theme.iconSize.lg} color={theme.colors.accent} strokeWidth={2.5} />
|
||||
</Pressable>
|
||||
</>
|
||||
)}
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -3,10 +3,21 @@ import { ActivityIndicator, Pressable, Text, View } from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
import { Check, X, XCircle } from "lucide-react-native";
|
||||
import { useDownloadStore, formatSpeed, formatEta } from "@/stores/download-store";
|
||||
import { useDownloadStore, formatSpeed, formatEta, type Download } from "@/stores/download-store";
|
||||
|
||||
const AUTO_DISMISS_DELAY = 3000;
|
||||
|
||||
function getDownloadStatusText(download: Download): string {
|
||||
if (download.status === "downloading") {
|
||||
if (download.progress) {
|
||||
return `${Math.round(download.progress.percent * 100)}% · ${formatSpeed(download.progress.speed)} · ${formatEta(download.progress.eta)}`;
|
||||
}
|
||||
return "Starting...";
|
||||
}
|
||||
if (download.status === "complete") return "Download complete";
|
||||
return download.message ?? "Download failed";
|
||||
}
|
||||
|
||||
export function DownloadToast() {
|
||||
const { theme } = useUnistyles();
|
||||
const insets = useSafeAreaInsets();
|
||||
@@ -56,24 +67,18 @@ export function DownloadToast() {
|
||||
<View style={styles.toast}>
|
||||
{activeDownload.status === "downloading" ? (
|
||||
<ActivityIndicator size="small" color={theme.colors.foreground} />
|
||||
) : activeDownload.status === "complete" ? (
|
||||
) : null}
|
||||
{activeDownload.status === "complete" ? (
|
||||
<Check size={18} color={theme.colors.primary} />
|
||||
) : (
|
||||
) : null}
|
||||
{activeDownload.status !== "downloading" && activeDownload.status !== "complete" ? (
|
||||
<XCircle size={18} color={theme.colors.destructive} />
|
||||
)}
|
||||
) : null}
|
||||
<View style={styles.textContainer}>
|
||||
<Text style={styles.fileName} numberOfLines={1}>
|
||||
{activeDownload.fileName}
|
||||
</Text>
|
||||
<Text style={styles.status}>
|
||||
{activeDownload.status === "downloading"
|
||||
? activeDownload.progress
|
||||
? `${Math.round(activeDownload.progress.percent * 100)}% · ${formatSpeed(activeDownload.progress.speed)} · ${formatEta(activeDownload.progress.eta)}`
|
||||
: "Starting..."
|
||||
: activeDownload.status === "complete"
|
||||
? "Download complete"
|
||||
: (activeDownload.message ?? "Download failed")}
|
||||
</Text>
|
||||
<Text style={styles.status}>{getDownloadStatusText(activeDownload)}</Text>
|
||||
{activeDownload.status === "downloading" && activeDownload.progress && (
|
||||
<View style={styles.progressBar}>
|
||||
<ProgressFill percent={activeDownload.progress.percent} />
|
||||
|
||||
@@ -286,7 +286,10 @@ export function ExplorerSidebar({
|
||||
|
||||
// Mobile: full-screen overlay with gesture.
|
||||
// On web, keep it interactive only while open so closed sidebars don't eat taps.
|
||||
const overlayPointerEvents = isWeb ? (isOpen ? "auto" : "none") : "box-none";
|
||||
let overlayPointerEvents: "auto" | "none" | "box-none";
|
||||
if (!isWeb) overlayPointerEvents = "box-none";
|
||||
else if (isOpen) overlayPointerEvents = "auto";
|
||||
else overlayPointerEvents = "none";
|
||||
|
||||
// Navigation stacks can keep previous screens mounted; hide sidebars for unfocused
|
||||
// screens so only the active screen exposes explorer/terminal surfaces.
|
||||
|
||||
@@ -183,7 +183,10 @@ function getElementDescriptor(element: HTMLElement | null): string | null {
|
||||
const id = element.id ? `#${element.id}` : "";
|
||||
const testId = element.getAttribute?.("data-testid");
|
||||
const label = element.getAttribute?.("aria-label");
|
||||
const suffix = testId ? `[data-testid="${testId}"]` : label ? `[aria-label="${label}"]` : "";
|
||||
let suffix: string;
|
||||
if (testId) suffix = `[data-testid="${testId}"]`;
|
||||
else if (label) suffix = `[aria-label="${label}"]`;
|
||||
else suffix = "";
|
||||
return `${tag}${id}${suffix}`;
|
||||
}
|
||||
|
||||
@@ -576,8 +579,10 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(funct
|
||||
}
|
||||
void voice.startVoice(voiceServerId, voiceAgentId).catch((error) => {
|
||||
console.error("[MessageInput] Failed to start realtime voice", error);
|
||||
const message =
|
||||
error instanceof Error ? error.message : typeof error === "string" ? error : null;
|
||||
let message: string | null;
|
||||
if (error instanceof Error) message = error.message;
|
||||
else if (typeof error === "string") message = error;
|
||||
else message = null;
|
||||
if (message && message.trim().length > 0) {
|
||||
toast.error(message);
|
||||
}
|
||||
@@ -675,11 +680,11 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(funct
|
||||
const getWebElement = useCallback((target: "root" | "wrapper"): HTMLElement | null => {
|
||||
const current = target === "root" ? rootRef.current : inputWrapperRef.current;
|
||||
if (!current) return null;
|
||||
return current instanceof HTMLElement
|
||||
? current
|
||||
: (current as unknown as { getBoundingClientRect?: () => DOMRect }).getBoundingClientRect
|
||||
? (current as unknown as HTMLElement)
|
||||
: null;
|
||||
if (current instanceof HTMLElement) return current;
|
||||
if ((current as unknown as { getBoundingClientRect?: () => DOMRect }).getBoundingClientRect) {
|
||||
return current as unknown as HTMLElement;
|
||||
}
|
||||
return null;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -924,16 +929,30 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(funct
|
||||
const isSendButtonDisabled =
|
||||
disabled || (!canPressLoadingButton && (isSubmitDisabled || isSubmitLoading));
|
||||
const defaultActionQueues = defaultSendBehavior === "queue" && isAgentRunning;
|
||||
const defaultSubmitAccessibilityLabel = canPressLoadingButton
|
||||
? "Interrupt agent"
|
||||
: defaultActionQueues
|
||||
? "Queue message"
|
||||
: isAgentRunning
|
||||
? "Send and interrupt"
|
||||
: "Send message";
|
||||
let defaultSubmitAccessibilityLabel: string;
|
||||
if (canPressLoadingButton) defaultSubmitAccessibilityLabel = "Interrupt agent";
|
||||
else if (defaultActionQueues) defaultSubmitAccessibilityLabel = "Queue message";
|
||||
else if (isAgentRunning) defaultSubmitAccessibilityLabel = "Send and interrupt";
|
||||
else defaultSubmitAccessibilityLabel = "Send message";
|
||||
const submitAccessibilityLabel =
|
||||
submitButtonAccessibilityLabel ?? defaultSubmitAccessibilityLabel;
|
||||
|
||||
let voiceButtonAccessibilityLabel: string;
|
||||
if (isRealtimeVoiceForCurrentAgent) {
|
||||
voiceButtonAccessibilityLabel = voice?.isMuted ? "Unmute Voice mode" : "Mute Voice mode";
|
||||
} else if (isDictating) {
|
||||
voiceButtonAccessibilityLabel = "Stop dictation";
|
||||
} else {
|
||||
voiceButtonAccessibilityLabel = "Start dictation";
|
||||
}
|
||||
|
||||
let voiceTooltipText: string;
|
||||
if (isRealtimeVoiceForCurrentAgent) {
|
||||
voiceTooltipText = voice?.isMuted ? "Unmute voice" : "Mute voice";
|
||||
} else {
|
||||
voiceTooltipText = "Dictation";
|
||||
}
|
||||
|
||||
const handleInputChange = useCallback(
|
||||
(nextValue: string) => {
|
||||
markScrollInvestigationEvent(investigationComponentId, "inputChange");
|
||||
@@ -1107,42 +1126,32 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(funct
|
||||
onPress={handleVoicePress}
|
||||
disabled={!isDictationStartEnabled}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={
|
||||
isRealtimeVoiceForCurrentAgent
|
||||
? voice?.isMuted
|
||||
? "Unmute Voice mode"
|
||||
: "Mute Voice mode"
|
||||
: isDictating
|
||||
? "Stop dictation"
|
||||
: "Start dictation"
|
||||
}
|
||||
accessibilityLabel={voiceButtonAccessibilityLabel}
|
||||
style={voiceButtonStyle}
|
||||
>
|
||||
{({ hovered }) =>
|
||||
isDictating ? (
|
||||
<Square size={buttonIconSize} color="white" fill="white" />
|
||||
) : isRealtimeVoiceForCurrentAgent && voice?.isMuted ? (
|
||||
<MicOff
|
||||
size={buttonIconSize}
|
||||
color={hovered ? theme.colors.foreground : theme.colors.foregroundMuted}
|
||||
/>
|
||||
) : (
|
||||
{({ hovered }) => {
|
||||
if (isDictating) {
|
||||
return <Square size={buttonIconSize} color="white" fill="white" />;
|
||||
}
|
||||
if (isRealtimeVoiceForCurrentAgent && voice?.isMuted) {
|
||||
return (
|
||||
<MicOff
|
||||
size={buttonIconSize}
|
||||
color={hovered ? theme.colors.foreground : theme.colors.foregroundMuted}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Mic
|
||||
size={buttonIconSize}
|
||||
color={hovered ? theme.colors.foreground : theme.colors.foregroundMuted}
|
||||
/>
|
||||
)
|
||||
}
|
||||
);
|
||||
}}
|
||||
</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>
|
||||
<Text style={styles.tooltipText}>{voiceTooltipText}</Text>
|
||||
{(isRealtimeVoiceForCurrentAgent ? voiceMuteToggleKeys : dictationToggleKeys) ? (
|
||||
<Shortcut
|
||||
chord={
|
||||
@@ -1166,13 +1175,13 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(funct
|
||||
accessibilityRole="button"
|
||||
style={sendButtonCombinedStyle}
|
||||
>
|
||||
{isSubmitLoading ? (
|
||||
<ActivityIndicator size="small" color="white" />
|
||||
) : submitIcon === "return" ? (
|
||||
{isSubmitLoading ? <ActivityIndicator size="small" color="white" /> : null}
|
||||
{!isSubmitLoading && submitIcon === "return" ? (
|
||||
<CornerDownLeft size={buttonIconSize} color="white" />
|
||||
) : (
|
||||
) : null}
|
||||
{!isSubmitLoading && submitIcon !== "return" ? (
|
||||
<ArrowUp size={buttonIconSize} color="white" />
|
||||
)}
|
||||
) : null}
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" align="center" offset={8}>
|
||||
<View style={styles.tooltipRow}>
|
||||
@@ -1204,7 +1213,8 @@ export const MessageInput = forwardRef<MessageInputRef, MessageInputProps>(funct
|
||||
onRetry={dictationStatus === "failed" ? handleRetryFailedRecording : undefined}
|
||||
onDiscard={dictationStatus === "failed" ? handleDiscardFailedRecording : undefined}
|
||||
/>
|
||||
) : showRealtimeOverlay && voice ? (
|
||||
) : null}
|
||||
{!showDictationOverlay && showRealtimeOverlay && voice ? (
|
||||
<RealtimeVoiceOverlay
|
||||
isMuted={voice.isMuted}
|
||||
isSwitching={voice.isVoiceSwitching}
|
||||
|
||||
@@ -248,11 +248,11 @@ export function ProjectPickerModal() {
|
||||
keyboardShouldPersistTaps="always"
|
||||
showsVerticalScrollIndicator={false}
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<Text style={emptyTextStyle}>Opening project...</Text>
|
||||
) : options.length === 0 && !query.trim() ? (
|
||||
{isSubmitting ? <Text style={emptyTextStyle}>Opening project...</Text> : null}
|
||||
{!isSubmitting && options.length === 0 && !query.trim() ? (
|
||||
<Text style={emptyTextStyle}>Start typing a path</Text>
|
||||
) : (
|
||||
) : null}
|
||||
{!isSubmitting && !(options.length === 0 && !query.trim()) ? (
|
||||
<>
|
||||
{options.map((path, index) => (
|
||||
<PathRow
|
||||
@@ -263,7 +263,7 @@ export function ProjectPickerModal() {
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
) : null}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -242,13 +242,13 @@ export function ToastViewport({
|
||||
return null;
|
||||
}
|
||||
|
||||
const icon =
|
||||
toast.icon ??
|
||||
(toast.variant === "success" ? (
|
||||
<CheckCircle2 size={18} color={theme.colors.primary} />
|
||||
) : toast.variant === "error" ? (
|
||||
<AlertTriangle size={18} color={theme.colors.destructive} />
|
||||
) : null);
|
||||
let defaultIcon: ReactNode = null;
|
||||
if (toast.variant === "success") {
|
||||
defaultIcon = <CheckCircle2 size={18} color={theme.colors.primary} />;
|
||||
} else if (toast.variant === "error") {
|
||||
defaultIcon = <AlertTriangle size={18} color={theme.colors.destructive} />;
|
||||
}
|
||||
const icon = toast.icon ?? defaultIcon;
|
||||
|
||||
const content = (
|
||||
<View style={styles.container} pointerEvents="box-none">
|
||||
|
||||
@@ -285,8 +285,14 @@ export function DropdownMenuTrigger({
|
||||
}
|
||||
|
||||
function getTransformOrigin(placement: Placement, alignment: Alignment): string {
|
||||
const vertical = placement === "bottom" ? "top" : placement === "top" ? "bottom" : "center";
|
||||
const horizontal = alignment === "start" ? "left" : alignment === "end" ? "right" : "center";
|
||||
let vertical: string;
|
||||
if (placement === "bottom") vertical = "top";
|
||||
else if (placement === "top") vertical = "bottom";
|
||||
else vertical = "center";
|
||||
let horizontal: string;
|
||||
if (alignment === "start") horizontal = "left";
|
||||
else if (alignment === "end") horizontal = "right";
|
||||
else horizontal = "center";
|
||||
return `${vertical} ${horizontal}`;
|
||||
}
|
||||
|
||||
@@ -610,21 +616,23 @@ export function DropdownMenuItem({
|
||||
}, [isDisabled, selectItem, onSelect, closeOnSelect]);
|
||||
|
||||
const itemPressableStyle = useCallback(
|
||||
({ pressed, hovered = false }: PressableStateCallbackType & { hovered?: boolean }) => [
|
||||
styles.item,
|
||||
selected
|
||||
? selectedVariant === "accent"
|
||||
? styles.itemSelectedAccent
|
||||
: styles.itemSelected
|
||||
: null,
|
||||
selected && (Boolean(hovered) || pressed) && selectedVariant !== "accent"
|
||||
? styles.itemSelectedInteractive
|
||||
: null,
|
||||
isDisabled ? styles.itemDisabled : null,
|
||||
muted && !isDisabled ? styles.itemMuted : null,
|
||||
Boolean(hovered) && !pressed && !isDisabled ? styles.itemHovered : null,
|
||||
pressed && !isDisabled ? styles.itemPressed : null,
|
||||
],
|
||||
({ pressed, hovered = false }: PressableStateCallbackType & { hovered?: boolean }) => {
|
||||
let selectedStyle: typeof styles.itemSelectedAccent | typeof styles.itemSelected | null;
|
||||
if (!selected) selectedStyle = null;
|
||||
else if (selectedVariant === "accent") selectedStyle = styles.itemSelectedAccent;
|
||||
else selectedStyle = styles.itemSelected;
|
||||
return [
|
||||
styles.item,
|
||||
selectedStyle,
|
||||
selected && (Boolean(hovered) || pressed) && selectedVariant !== "accent"
|
||||
? styles.itemSelectedInteractive
|
||||
: null,
|
||||
isDisabled ? styles.itemDisabled : null,
|
||||
muted && !isDisabled ? styles.itemMuted : null,
|
||||
Boolean(hovered) && !pressed && !isDisabled ? styles.itemHovered : null,
|
||||
pressed && !isDisabled ? styles.itemPressed : null,
|
||||
];
|
||||
},
|
||||
[selected, selectedVariant, isDisabled, muted],
|
||||
);
|
||||
|
||||
|
||||
@@ -119,7 +119,10 @@ export function VolumeMeter({
|
||||
// Create animated styles unconditionally at top level
|
||||
const line1Style = useAnimatedStyle(() => {
|
||||
const isActive = isSpeaking;
|
||||
const baseOpacity = isMuted ? 0.3 : isActive ? 0.9 : 0.5;
|
||||
let baseOpacity: number;
|
||||
if (isMuted) baseOpacity = 0.3;
|
||||
else if (isActive) baseOpacity = 0.9;
|
||||
else baseOpacity = 0.5;
|
||||
const currentVolume = isMuted ? 0 : animatedVolume.value;
|
||||
const currentHeight = MIN_HEIGHT + MAX_HEIGHT * currentVolume * 1.2;
|
||||
const volumeBoost = isMuted || !isActive ? 0 : currentVolume * 0.3;
|
||||
@@ -131,7 +134,10 @@ export function VolumeMeter({
|
||||
|
||||
const line2Style = useAnimatedStyle(() => {
|
||||
const isActive = isSpeaking;
|
||||
const baseOpacity = isMuted ? 0.3 : isActive ? 0.9 : 0.5;
|
||||
let baseOpacity: number;
|
||||
if (isMuted) baseOpacity = 0.3;
|
||||
else if (isActive) baseOpacity = 0.9;
|
||||
else baseOpacity = 0.5;
|
||||
const currentVolume = isMuted ? 0 : animatedVolume.value;
|
||||
const currentHeight = MIN_HEIGHT + MAX_HEIGHT * currentVolume * 1.05;
|
||||
const volumeBoost = isMuted || !isActive ? 0 : currentVolume * 0.3;
|
||||
@@ -143,7 +149,10 @@ export function VolumeMeter({
|
||||
|
||||
const line3Style = useAnimatedStyle(() => {
|
||||
const isActive = isSpeaking;
|
||||
const baseOpacity = isMuted ? 0.3 : isActive ? 0.9 : 0.5;
|
||||
let baseOpacity: number;
|
||||
if (isMuted) baseOpacity = 0.3;
|
||||
else if (isActive) baseOpacity = 0.9;
|
||||
else baseOpacity = 0.5;
|
||||
const currentVolume = isMuted ? 0 : animatedVolume.value;
|
||||
const currentHeight = MIN_HEIGHT + MAX_HEIGHT * currentVolume * 0.9;
|
||||
const volumeBoost = isMuted || !isActive ? 0 : currentVolume * 0.3;
|
||||
|
||||
@@ -338,13 +338,11 @@ export function WebDesktopScrollbarOverlay({
|
||||
}
|
||||
|
||||
const handleVisible = isDragging || isScrollVisible || isHandleHovered;
|
||||
const handleOpacity = isDragging
|
||||
? HANDLE_OPACITY_DRAGGING
|
||||
: isHandleHovered
|
||||
? HANDLE_OPACITY_HOVERED
|
||||
: isScrollVisible
|
||||
? HANDLE_OPACITY_VISIBLE
|
||||
: 0;
|
||||
let handleOpacity: number;
|
||||
if (isDragging) handleOpacity = HANDLE_OPACITY_DRAGGING;
|
||||
else if (isHandleHovered) handleOpacity = HANDLE_OPACITY_HOVERED;
|
||||
else if (isScrollVisible) handleOpacity = HANDLE_OPACITY_VISIBLE;
|
||||
else handleOpacity = 0;
|
||||
const handleWidth = isDragging || isHandleHovered ? HANDLE_WIDTH_ACTIVE : HANDLE_WIDTH_IDLE;
|
||||
const handleColor = theme.colors.scrollbarHandle;
|
||||
const handleCursor = isDragging ? "grabbing" : "grab";
|
||||
|
||||
@@ -229,22 +229,22 @@ export function StartupSplashScreen({ bootstrapState }: StartupSplashScreenProps
|
||||
};
|
||||
}, [isError]);
|
||||
|
||||
const progressSteps =
|
||||
phase === "starting-daemon"
|
||||
? [{ key: "starting-daemon", label: "Starting local server...", status: "active" as const }]
|
||||
: phase === "connecting"
|
||||
? [
|
||||
{ key: "starting-daemon", label: "Started local server", status: "complete" as const },
|
||||
{
|
||||
key: "connecting",
|
||||
label: "Connecting to local server...",
|
||||
status: "active" as const,
|
||||
},
|
||||
]
|
||||
: [
|
||||
{ key: "starting-daemon", label: "Started local server", status: "complete" as const },
|
||||
{ key: "connecting", label: "Connected to local server", status: "complete" as const },
|
||||
];
|
||||
let progressSteps: { key: string; label: string; status: "active" | "complete" }[];
|
||||
if (phase === "starting-daemon") {
|
||||
progressSteps = [
|
||||
{ key: "starting-daemon", label: "Starting local server...", status: "active" },
|
||||
];
|
||||
} else if (phase === "connecting") {
|
||||
progressSteps = [
|
||||
{ key: "starting-daemon", label: "Started local server", status: "complete" },
|
||||
{ key: "connecting", label: "Connecting to local server...", status: "active" },
|
||||
];
|
||||
} else {
|
||||
progressSteps = [
|
||||
{ key: "starting-daemon", label: "Started local server", status: "complete" },
|
||||
{ key: "connecting", label: "Connected to local server", status: "complete" },
|
||||
];
|
||||
}
|
||||
|
||||
const logsText = useMemo(() => {
|
||||
if (isLoadingLogs) {
|
||||
|
||||
@@ -342,19 +342,18 @@ function TabChip({
|
||||
<View style={styles.tabIcon}>
|
||||
<WorkspaceTabIcon presentation={presentation} active={isHighlighted} />
|
||||
</View>
|
||||
{showLabel ? (
|
||||
presentation.titleState === "loading" ? (
|
||||
<View style={tabLabelSkeletonStyle} />
|
||||
) : (
|
||||
<Text
|
||||
style={tabLabelStyle}
|
||||
selectable={false}
|
||||
numberOfLines={1}
|
||||
ellipsizeMode="tail"
|
||||
>
|
||||
{presentation.label}
|
||||
</Text>
|
||||
)
|
||||
{showLabel && presentation.titleState === "loading" ? (
|
||||
<View style={tabLabelSkeletonStyle} />
|
||||
) : null}
|
||||
{showLabel && presentation.titleState !== "loading" ? (
|
||||
<Text
|
||||
style={tabLabelStyle}
|
||||
selectable={false}
|
||||
numberOfLines={1}
|
||||
ellipsizeMode="tail"
|
||||
>
|
||||
{presentation.label}
|
||||
</Text>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
|
||||
@@ -192,14 +192,18 @@ export function WorkspaceDraftAgentTab({
|
||||
if (!provider) {
|
||||
throw new Error("Select a model");
|
||||
}
|
||||
let modeIdOverride: { modeId: string } | Record<string, never>;
|
||||
if (autoSubmitConfig?.modeId) {
|
||||
modeIdOverride = { modeId: autoSubmitConfig.modeId };
|
||||
} else if (composerState.modeOptions.length > 0 && composerState.selectedMode !== "") {
|
||||
modeIdOverride = { modeId: composerState.selectedMode };
|
||||
} else {
|
||||
modeIdOverride = {};
|
||||
}
|
||||
const config = buildWorkspaceDraftAgentConfig({
|
||||
provider,
|
||||
cwd: workspaceDirectory,
|
||||
...(autoSubmitConfig?.modeId
|
||||
? { modeId: autoSubmitConfig.modeId }
|
||||
: composerState.modeOptions.length > 0 && composerState.selectedMode !== ""
|
||||
? { modeId: composerState.selectedMode }
|
||||
: {}),
|
||||
...modeIdOverride,
|
||||
model: autoSubmitConfig?.model ?? (composerState.effectiveModelId || undefined),
|
||||
thinkingOptionId:
|
||||
autoSubmitConfig?.thinkingOptionId ??
|
||||
|
||||
@@ -205,25 +205,25 @@ export const useWorkspaceTabsStore = create<WorkspaceTabsState>()(
|
||||
const currentOrder = state.tabOrderByWorkspace[key] ?? [];
|
||||
const nextOrder = currentOrder.filter((value) => value !== normalizedTabId);
|
||||
|
||||
const nextUiTabsByWorkspace =
|
||||
nextTabs.length === 0
|
||||
? (() => {
|
||||
const { [key]: _removed, ...rest } = state.uiTabsByWorkspace;
|
||||
return rest;
|
||||
})()
|
||||
: nextTabs.length === currentTabs.length
|
||||
? state.uiTabsByWorkspace
|
||||
: { ...state.uiTabsByWorkspace, [key]: nextTabs };
|
||||
let nextUiTabsByWorkspace: typeof state.uiTabsByWorkspace;
|
||||
if (nextTabs.length === 0) {
|
||||
const { [key]: _removed, ...rest } = state.uiTabsByWorkspace;
|
||||
nextUiTabsByWorkspace = rest;
|
||||
} else if (nextTabs.length === currentTabs.length) {
|
||||
nextUiTabsByWorkspace = state.uiTabsByWorkspace;
|
||||
} else {
|
||||
nextUiTabsByWorkspace = { ...state.uiTabsByWorkspace, [key]: nextTabs };
|
||||
}
|
||||
|
||||
const nextTabOrderByWorkspace =
|
||||
nextOrder.length === 0
|
||||
? (() => {
|
||||
const { [key]: _removed, ...rest } = state.tabOrderByWorkspace;
|
||||
return rest;
|
||||
})()
|
||||
: nextOrder.length === currentOrder.length
|
||||
? state.tabOrderByWorkspace
|
||||
: { ...state.tabOrderByWorkspace, [key]: nextOrder };
|
||||
let nextTabOrderByWorkspace: typeof state.tabOrderByWorkspace;
|
||||
if (nextOrder.length === 0) {
|
||||
const { [key]: _removed, ...rest } = state.tabOrderByWorkspace;
|
||||
nextTabOrderByWorkspace = rest;
|
||||
} else if (nextOrder.length === currentOrder.length) {
|
||||
nextTabOrderByWorkspace = state.tabOrderByWorkspace;
|
||||
} else {
|
||||
nextTabOrderByWorkspace = { ...state.tabOrderByWorkspace, [key]: nextOrder };
|
||||
}
|
||||
|
||||
const currentFocused = state.focusedTabIdByWorkspace[key] ?? null;
|
||||
const nextFocused =
|
||||
|
||||
@@ -43,9 +43,14 @@ export function buildSidebarProjectRowModel(input: {
|
||||
|
||||
const collapsible = input.project.projectKind === "git" || input.project.workspaces.length > 1;
|
||||
|
||||
let chevron: "expand" | "collapse" | null;
|
||||
if (!collapsible) chevron = null;
|
||||
else if (input.collapsed) chevron = "expand";
|
||||
else chevron = "collapse";
|
||||
|
||||
return {
|
||||
kind: "project_section",
|
||||
chevron: collapsible ? (input.collapsed ? "expand" : "collapse") : null,
|
||||
chevron,
|
||||
trailingAction: input.project.projectKind === "git" ? "new_worktree" : "none",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -59,10 +59,9 @@ export async function buildClientConfig(
|
||||
suppressSendErrors: true,
|
||||
reconnect: { enabled: false },
|
||||
...(isDev ? { runtimeMetricsIntervalMs: 10_000 } : {}),
|
||||
...(connection.type === "directSocket" || connection.type === "directPipe"
|
||||
? localTransportFactory
|
||||
? { transportFactory: localTransportFactory }
|
||||
: {}
|
||||
...((connection.type === "directSocket" || connection.type === "directPipe") &&
|
||||
localTransportFactory
|
||||
? { transportFactory: localTransportFactory }
|
||||
: {}),
|
||||
};
|
||||
|
||||
|
||||
@@ -271,12 +271,10 @@ export function createVoiceRuntime(deps: VoiceRuntimeDeps): VoiceRuntime {
|
||||
bytes: Uint8Array,
|
||||
format: string,
|
||||
): { arrayBuffer(): Promise<ArrayBuffer>; size: number; type: string } {
|
||||
const mimeType =
|
||||
format === "pcm"
|
||||
? "audio/pcm;rate=24000;bits=16"
|
||||
: format === "mp3"
|
||||
? "audio/mpeg"
|
||||
: `audio/${format}`;
|
||||
let mimeType: string;
|
||||
if (format === "pcm") mimeType = "audio/pcm;rate=24000;bits=16";
|
||||
else if (format === "mp3") mimeType = "audio/mpeg";
|
||||
else mimeType = `audio/${format}`;
|
||||
|
||||
return {
|
||||
size: bytes.byteLength,
|
||||
|
||||
Reference in New Issue
Block a user