mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
refactor: replace settings buttons with switches and simplify provider sheet
Swap daemon management and keep-running-after-quit buttons for Switch toggles. Remove the restart/start daemon control. Simplify the provider diagnostic sheet to reuse shared settings styles and SettingsSection. Drop the unused "sm" size variant from the Switch component. Remove trailing periods from hint text for consistency.
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { AlertCircle, Plus, RotateCw, Search, Trash2 } from "lucide-react-native";
|
import { AlertCircle, RotateCw, Search, Trash2 } from "lucide-react-native";
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import {
|
import {
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
@@ -10,12 +10,15 @@ import {
|
|||||||
} from "react-native";
|
} from "react-native";
|
||||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||||
import { AdaptiveModalSheet, AdaptiveTextInput } from "@/components/adaptive-modal-sheet";
|
import { AdaptiveModalSheet, AdaptiveTextInput } from "@/components/adaptive-modal-sheet";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
import { LoadingSpinner } from "@/components/ui/loading-spinner";
|
import { LoadingSpinner } from "@/components/ui/loading-spinner";
|
||||||
import { isWeb } from "@/constants/platform";
|
import { isWeb } from "@/constants/platform";
|
||||||
import { Fonts } from "@/constants/theme";
|
import { Fonts } from "@/constants/theme";
|
||||||
import { useDaemonConfig } from "@/hooks/use-daemon-config";
|
import { useDaemonConfig } from "@/hooks/use-daemon-config";
|
||||||
import { useProvidersSnapshot } from "@/hooks/use-providers-snapshot";
|
import { useProvidersSnapshot } from "@/hooks/use-providers-snapshot";
|
||||||
import { useHostRuntimeClient } from "@/runtime/host-runtime";
|
import { useHostRuntimeClient } from "@/runtime/host-runtime";
|
||||||
|
import { SettingsSection } from "@/screens/settings/settings-section";
|
||||||
|
import { settingsStyles } from "@/styles/settings";
|
||||||
import { resolveProviderLabel } from "@/utils/provider-definitions";
|
import { resolveProviderLabel } from "@/utils/provider-definitions";
|
||||||
import { formatTimeAgo } from "@/utils/time";
|
import { formatTimeAgo } from "@/utils/time";
|
||||||
import type { AgentModelDefinition, AgentProvider } from "@server/server/agent/agent-sdk-types";
|
import type { AgentModelDefinition, AgentProvider } from "@server/server/agent/agent-sdk-types";
|
||||||
@@ -28,35 +31,28 @@ interface ProviderDiagnosticSheetProps {
|
|||||||
serverId: string;
|
serverId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ModelRow({ model, isFirst }: { model: AgentModelDefinition; isFirst: boolean }) {
|
function ModelRow({ model }: { model: AgentModelDefinition }) {
|
||||||
const rowStyle = useMemo(
|
|
||||||
() => [sheetStyles.modelRow, !isFirst && sheetStyles.modelRowBorder],
|
|
||||||
[isFirst],
|
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<View style={rowStyle}>
|
<View style={MODEL_ROW_STYLE}>
|
||||||
<Text style={sheetStyles.modelLabel} numberOfLines={1}>
|
<View style={settingsStyles.rowContent}>
|
||||||
{model.label}
|
<Text style={settingsStyles.rowTitle} numberOfLines={1}>
|
||||||
</Text>
|
{model.label}
|
||||||
<Text style={sheetStyles.modelId} numberOfLines={1} selectable>
|
</Text>
|
||||||
{model.id}
|
<Text style={sheetStyles.monoHint} numberOfLines={1} selectable>
|
||||||
</Text>
|
{model.id}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AdditionalModelRow(props: {
|
function CustomModelRow(props: {
|
||||||
model: ProviderProfileModel;
|
model: ProviderProfileModel;
|
||||||
isFirst: boolean;
|
|
||||||
deleting: boolean;
|
deleting: boolean;
|
||||||
onDelete: (modelId: string) => void;
|
onDelete: (modelId: string) => void;
|
||||||
}) {
|
}) {
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
const { model, isFirst, deleting, onDelete } = props;
|
const { model, deleting, onDelete } = props;
|
||||||
const rowStyle = useMemo(
|
|
||||||
() => [sheetStyles.additionalModelRow, !isFirst && sheetStyles.modelRowBorder],
|
|
||||||
[isFirst],
|
|
||||||
);
|
|
||||||
const handleDelete = useCallback(() => onDelete(model.id), [model.id, onDelete]);
|
const handleDelete = useCallback(() => onDelete(model.id), [model.id, onDelete]);
|
||||||
const deleteButtonStyle = useCallback(
|
const deleteButtonStyle = useCallback(
|
||||||
({ hovered, pressed }: PressableStateCallbackType & { hovered?: boolean }) => [
|
({ hovered, pressed }: PressableStateCallbackType & { hovered?: boolean }) => [
|
||||||
@@ -68,12 +64,12 @@ function AdditionalModelRow(props: {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={rowStyle}>
|
<View style={MODEL_ROW_STYLE}>
|
||||||
<View style={sheetStyles.additionalModelText}>
|
<View style={settingsStyles.rowContent}>
|
||||||
<Text style={sheetStyles.modelLabel} numberOfLines={1}>
|
<Text style={settingsStyles.rowTitle} numberOfLines={1}>
|
||||||
{model.label}
|
{model.label}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={sheetStyles.modelId} numberOfLines={1} selectable>
|
<Text style={sheetStyles.monoHint} numberOfLines={1} selectable>
|
||||||
{model.id}
|
{model.id}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
@@ -91,7 +87,7 @@ function AdditionalModelRow(props: {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function AdditionalModelsEditor(props: {
|
function CustomModelsSection(props: {
|
||||||
provider: string;
|
provider: string;
|
||||||
serverId: string;
|
serverId: string;
|
||||||
refresh: (providers?: AgentProvider[]) => Promise<void>;
|
refresh: (providers?: AgentProvider[]) => Promise<void>;
|
||||||
@@ -162,20 +158,10 @@ function AdditionalModelsEditor(props: {
|
|||||||
[additionalModels, patchAdditionalModels],
|
[additionalModels, patchAdditionalModels],
|
||||||
);
|
);
|
||||||
|
|
||||||
const addButtonStyle = useCallback(
|
|
||||||
({ hovered, pressed }: PressableStateCallbackType & { hovered?: boolean }) => [
|
|
||||||
sheetStyles.addModelButton,
|
|
||||||
(Boolean(hovered) || pressed) && sheetStyles.addModelButtonHovered,
|
|
||||||
!canAdd || saving ? sheetStyles.disabled : null,
|
|
||||||
],
|
|
||||||
[canAdd, saving],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={sheetStyles.section}>
|
<SettingsSection title="Custom models">
|
||||||
<Text style={sheetStyles.sectionTitle}>Additional Models</Text>
|
<View style={settingsStyles.card}>
|
||||||
<View style={sheetStyles.addModelRow}>
|
<View style={INLINE_ROW_STYLE}>
|
||||||
<View style={sheetStyles.addModelInputContainer}>
|
|
||||||
<AdaptiveTextInput
|
<AdaptiveTextInput
|
||||||
value={input}
|
value={input}
|
||||||
onChangeText={setInput}
|
onChangeText={setInput}
|
||||||
@@ -186,38 +172,29 @@ function AdditionalModelsEditor(props: {
|
|||||||
autoCorrect={false}
|
autoCorrect={false}
|
||||||
returnKeyType="done"
|
returnKeyType="done"
|
||||||
// @ts-expect-error - outlineStyle is web-only
|
// @ts-expect-error - outlineStyle is web-only
|
||||||
style={DIAGNOSTIC_ADD_MODEL_INPUT_STYLE}
|
style={DIAGNOSTIC_INLINE_INPUT_STYLE}
|
||||||
/>
|
/>
|
||||||
|
<Button
|
||||||
|
variant="default"
|
||||||
|
size="sm"
|
||||||
|
onPress={handleAdd}
|
||||||
|
disabled={!canAdd || saving}
|
||||||
|
accessibilityLabel="Add model"
|
||||||
|
>
|
||||||
|
{saving ? "Adding…" : "Add"}
|
||||||
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
<Pressable
|
{additionalModels.map((model) => (
|
||||||
onPress={handleAdd}
|
<CustomModelRow
|
||||||
disabled={!canAdd || saving}
|
key={model.id}
|
||||||
style={addButtonStyle}
|
model={model}
|
||||||
accessibilityRole="button"
|
deleting={deletingModelId === model.id}
|
||||||
accessibilityLabel="Add model"
|
onDelete={handleDelete}
|
||||||
>
|
/>
|
||||||
{saving ? (
|
))}
|
||||||
<LoadingSpinner size={theme.iconSize.sm} color={theme.colors.accentForeground} />
|
|
||||||
) : (
|
|
||||||
<Plus size={theme.iconSize.sm} color={theme.colors.accentForeground} />
|
|
||||||
)}
|
|
||||||
</Pressable>
|
|
||||||
</View>
|
</View>
|
||||||
{error ? <Text style={sheetStyles.errorText}>{error}</Text> : null}
|
{error ? <Text style={sheetStyles.errorText}>{error}</Text> : null}
|
||||||
{additionalModels.length > 0 ? (
|
</SettingsSection>
|
||||||
<View style={sheetStyles.additionalModelsList}>
|
|
||||||
{additionalModels.map((model, index) => (
|
|
||||||
<AdditionalModelRow
|
|
||||||
key={model.id}
|
|
||||||
model={model}
|
|
||||||
isFirst={index === 0}
|
|
||||||
deleting={deletingModelId === model.id}
|
|
||||||
onDelete={handleDelete}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</View>
|
|
||||||
) : null}
|
|
||||||
</View>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,7 +228,7 @@ function DiagnosticCodeBlock(props: {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<View style={sheetStyles.codeBlockLoading}>
|
<View style={sheetStyles.codeBlockLoading}>
|
||||||
<Text style={sheetStyles.mutedText}>No diagnostic available.</Text>
|
<Text style={sheetStyles.mutedText}>No diagnostic available</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -288,7 +265,6 @@ export function ProviderDiagnosticSheet({
|
|||||||
}, [visible]);
|
}, [visible]);
|
||||||
const fetchedAtLabel = useMemo(() => {
|
const fetchedAtLabel = useMemo(() => {
|
||||||
if (!providerEntry?.fetchedAt) return null;
|
if (!providerEntry?.fetchedAt) return null;
|
||||||
// clockTick is referenced so the label recomputes each timer tick.
|
|
||||||
void clockTick;
|
void clockTick;
|
||||||
return formatTimeAgo(new Date(providerEntry.fetchedAt));
|
return formatTimeAgo(new Date(providerEntry.fetchedAt));
|
||||||
}, [providerEntry?.fetchedAt, clockTick]);
|
}, [providerEntry?.fetchedAt, clockTick]);
|
||||||
@@ -375,10 +351,27 @@ export function ProviderDiagnosticSheet({
|
|||||||
}
|
}
|
||||||
}, [visible, fetchDiagnostic]);
|
}, [visible, fetchDiagnostic]);
|
||||||
|
|
||||||
|
const modelsTrailing = useMemo(() => {
|
||||||
|
if (models.length === 0 && !fetchedAtLabel) return undefined;
|
||||||
|
return (
|
||||||
|
<View style={sheetStyles.modelsTrailing}>
|
||||||
|
{models.length > 0 ? (
|
||||||
|
<Text style={settingsStyles.sectionHeaderTitle}>{models.length}</Text>
|
||||||
|
) : null}
|
||||||
|
{models.length > 0 && fetchedAtLabel ? (
|
||||||
|
<Text style={settingsStyles.sectionHeaderTitle}>·</Text>
|
||||||
|
) : null}
|
||||||
|
{fetchedAtLabel ? (
|
||||||
|
<Text style={settingsStyles.sectionHeaderTitle}>Updated {fetchedAtLabel}</Text>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}, [models.length, fetchedAtLabel]);
|
||||||
|
|
||||||
function renderModelsBody() {
|
function renderModelsBody() {
|
||||||
if (models.length === 0 && providerSnapshotRefreshing) {
|
if (models.length === 0 && providerSnapshotRefreshing) {
|
||||||
return (
|
return (
|
||||||
<View style={sheetStyles.emptyState}>
|
<View style={sheetStyles.emptyRow}>
|
||||||
<ActivityIndicator size="small" color={theme.colors.foregroundMuted} />
|
<ActivityIndicator size="small" color={theme.colors.foregroundMuted} />
|
||||||
<Text style={sheetStyles.mutedText}>Loading models…</Text>
|
<Text style={sheetStyles.mutedText}>Loading models…</Text>
|
||||||
</View>
|
</View>
|
||||||
@@ -386,7 +379,7 @@ export function ProviderDiagnosticSheet({
|
|||||||
}
|
}
|
||||||
if (models.length === 0 && providerErrorMessage) {
|
if (models.length === 0 && providerErrorMessage) {
|
||||||
return (
|
return (
|
||||||
<View style={sheetStyles.emptyState}>
|
<View style={sheetStyles.emptyRow}>
|
||||||
<AlertCircle size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
|
<AlertCircle size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
|
||||||
<Text style={sheetStyles.mutedText}>{providerErrorMessage}</Text>
|
<Text style={sheetStyles.mutedText}>{providerErrorMessage}</Text>
|
||||||
</View>
|
</View>
|
||||||
@@ -394,21 +387,21 @@ export function ProviderDiagnosticSheet({
|
|||||||
}
|
}
|
||||||
if (models.length === 0) {
|
if (models.length === 0) {
|
||||||
return (
|
return (
|
||||||
<View style={sheetStyles.emptyState}>
|
<View style={sheetStyles.emptyRow}>
|
||||||
<Text style={sheetStyles.mutedText}>No models detected.</Text>
|
<Text style={sheetStyles.mutedText}>No models detected</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (filteredModels.length === 0) {
|
if (filteredModels.length === 0) {
|
||||||
return (
|
return (
|
||||||
<View style={sheetStyles.emptyState}>
|
<View style={sheetStyles.emptyRow}>
|
||||||
<Search size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
|
<Search size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
|
||||||
<Text style={sheetStyles.mutedText}>No models match your search</Text>
|
<Text style={sheetStyles.mutedText}>No models match your search</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return filteredModels.map((model: AgentModelDefinition, index) => (
|
return filteredModels.map((model: AgentModelDefinition) => (
|
||||||
<ModelRow key={model.id} model={model} isFirst={index === 0} />
|
<ModelRow key={model.id} model={model} />
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,35 +414,26 @@ export function ProviderDiagnosticSheet({
|
|||||||
scrollable={false}
|
scrollable={false}
|
||||||
headerActions={headerActions}
|
headerActions={headerActions}
|
||||||
>
|
>
|
||||||
<View style={sheetStyles.section}>
|
<SettingsSection title="Diagnostic">
|
||||||
<Text style={sheetStyles.sectionTitle}>Diagnostic</Text>
|
<View style={settingsStyles.card}>
|
||||||
<View style={sheetStyles.codeBlock}>
|
|
||||||
<DiagnosticCodeBlock
|
<DiagnosticCodeBlock
|
||||||
loading={loading}
|
loading={loading}
|
||||||
diagnostic={diagnostic}
|
diagnostic={diagnostic}
|
||||||
foregroundMutedColor={theme.colors.foregroundMuted}
|
foregroundMutedColor={theme.colors.foregroundMuted}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</SettingsSection>
|
||||||
|
|
||||||
|
<CustomModelsSection provider={provider} serverId={serverId} refresh={refresh} />
|
||||||
|
|
||||||
<View style={sheetStyles.modelsSection}>
|
<View style={sheetStyles.modelsSection}>
|
||||||
<AdditionalModelsEditor provider={provider} serverId={serverId} refresh={refresh} />
|
|
||||||
|
|
||||||
<View style={sheetStyles.modelsHeader}>
|
<View style={sheetStyles.modelsHeader}>
|
||||||
<Text style={sheetStyles.sectionTitle}>Models</Text>
|
<Text style={settingsStyles.sectionHeaderTitle}>Models</Text>
|
||||||
<View style={sheetStyles.modelsHeaderMeta}>
|
{modelsTrailing}
|
||||||
<Text style={sheetStyles.countText}>{models.length}</Text>
|
|
||||||
{fetchedAtLabel ? (
|
|
||||||
<>
|
|
||||||
<Text style={sheetStyles.metaDot}>·</Text>
|
|
||||||
<Text style={sheetStyles.countText}>Updated {fetchedAtLabel}</Text>
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
</View>
|
|
||||||
</View>
|
</View>
|
||||||
{models.length > 0 ? (
|
<View style={MODELS_CARD_STYLE}>
|
||||||
<View style={sheetStyles.searchContainer}>
|
<View style={INLINE_ROW_STYLE}>
|
||||||
<Search size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
|
<Search size={theme.iconSize.sm} color={theme.colors.foregroundMuted} />
|
||||||
<AdaptiveTextInput
|
<AdaptiveTextInput
|
||||||
value={query}
|
value={query}
|
||||||
onChangeText={setQuery}
|
onChangeText={setQuery}
|
||||||
@@ -461,36 +445,36 @@ export function ProviderDiagnosticSheet({
|
|||||||
style={DIAGNOSTIC_SEARCH_INPUT_STYLE}
|
style={DIAGNOSTIC_SEARCH_INPUT_STYLE}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
) : null}
|
<ScrollView
|
||||||
<ScrollView
|
style={sheetStyles.modelsScroll}
|
||||||
style={sheetStyles.modelsScroll}
|
contentContainerStyle={sheetStyles.modelsScrollContent}
|
||||||
contentContainerStyle={sheetStyles.modelsScrollContent}
|
keyboardShouldPersistTaps="handled"
|
||||||
keyboardShouldPersistTaps="handled"
|
showsVerticalScrollIndicator={false}
|
||||||
showsVerticalScrollIndicator={false}
|
>
|
||||||
>
|
{renderModelsBody()}
|
||||||
{renderModelsBody()}
|
</ScrollView>
|
||||||
</ScrollView>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</AdaptiveModalSheet>
|
</AdaptiveModalSheet>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const sheetStyles = StyleSheet.create((theme) => ({
|
const sheetStyles = StyleSheet.create((theme) => ({
|
||||||
section: {
|
|
||||||
gap: theme.spacing[2],
|
|
||||||
},
|
|
||||||
sectionTitle: {
|
|
||||||
color: theme.colors.foregroundMuted,
|
|
||||||
fontSize: theme.fontSize.xs,
|
|
||||||
fontWeight: theme.fontWeight.normal,
|
|
||||||
},
|
|
||||||
mutedText: {
|
mutedText: {
|
||||||
fontSize: theme.fontSize.sm,
|
fontSize: theme.fontSize.sm,
|
||||||
color: theme.colors.foregroundMuted,
|
color: theme.colors.foregroundMuted,
|
||||||
},
|
},
|
||||||
|
monoHint: {
|
||||||
|
fontFamily: Fonts.mono,
|
||||||
|
fontSize: theme.fontSize.xs,
|
||||||
|
color: theme.colors.foregroundMuted,
|
||||||
|
marginTop: theme.spacing[1],
|
||||||
|
},
|
||||||
errorText: {
|
errorText: {
|
||||||
fontSize: theme.fontSize.xs,
|
fontSize: theme.fontSize.xs,
|
||||||
color: theme.colors.destructive,
|
color: theme.colors.destructive,
|
||||||
|
marginTop: theme.spacing[2],
|
||||||
|
marginLeft: theme.spacing[1],
|
||||||
},
|
},
|
||||||
iconButton: {
|
iconButton: {
|
||||||
width: 30,
|
width: 30,
|
||||||
@@ -505,20 +489,24 @@ const sheetStyles = StyleSheet.create((theme) => ({
|
|||||||
disabled: {
|
disabled: {
|
||||||
opacity: 0.5,
|
opacity: 0.5,
|
||||||
},
|
},
|
||||||
codeBlock: {
|
inlineRow: {
|
||||||
borderWidth: 1,
|
flexDirection: "row",
|
||||||
borderColor: theme.colors.border,
|
alignItems: "center",
|
||||||
borderRadius: theme.borderRadius.base,
|
gap: theme.spacing[2],
|
||||||
backgroundColor: theme.colors.surface2,
|
},
|
||||||
overflow: "hidden",
|
inlineInput: {
|
||||||
maxHeight: 180,
|
flex: 1,
|
||||||
|
minWidth: 0,
|
||||||
|
paddingVertical: 0,
|
||||||
|
color: theme.colors.foreground,
|
||||||
|
fontSize: theme.fontSize.sm,
|
||||||
},
|
},
|
||||||
codeScroll: {
|
codeScroll: {
|
||||||
maxHeight: 180,
|
maxHeight: 200,
|
||||||
},
|
},
|
||||||
codeContent: {
|
codeContent: {
|
||||||
paddingVertical: theme.spacing[3],
|
paddingVertical: theme.spacing[3],
|
||||||
paddingHorizontal: theme.spacing[3],
|
paddingHorizontal: theme.spacing[4],
|
||||||
},
|
},
|
||||||
codeText: {
|
codeText: {
|
||||||
fontFamily: Fonts.mono,
|
fontFamily: Fonts.mono,
|
||||||
@@ -528,7 +516,7 @@ const sheetStyles = StyleSheet.create((theme) => ({
|
|||||||
},
|
},
|
||||||
codeBlockLoading: {
|
codeBlockLoading: {
|
||||||
paddingVertical: theme.spacing[4],
|
paddingVertical: theme.spacing[4],
|
||||||
paddingHorizontal: theme.spacing[3],
|
paddingHorizontal: theme.spacing[4],
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: theme.spacing[2],
|
gap: theme.spacing[2],
|
||||||
@@ -536,118 +524,46 @@ const sheetStyles = StyleSheet.create((theme) => ({
|
|||||||
modelsSection: {
|
modelsSection: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minHeight: 0,
|
minHeight: 0,
|
||||||
gap: theme.spacing[2],
|
|
||||||
},
|
},
|
||||||
modelsHeader: {
|
modelsHeader: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
|
gap: theme.spacing[2],
|
||||||
|
marginBottom: theme.spacing[3],
|
||||||
|
marginLeft: theme.spacing[1],
|
||||||
},
|
},
|
||||||
modelsHeaderMeta: {
|
flexCard: {
|
||||||
|
flex: 1,
|
||||||
|
minHeight: 0,
|
||||||
|
},
|
||||||
|
modelsTrailing: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
gap: theme.spacing[1],
|
gap: theme.spacing[1],
|
||||||
},
|
},
|
||||||
metaDot: {
|
|
||||||
fontSize: theme.fontSize.xs,
|
|
||||||
color: theme.colors.foregroundMuted,
|
|
||||||
},
|
|
||||||
countText: {
|
|
||||||
fontSize: theme.fontSize.xs,
|
|
||||||
color: theme.colors.foregroundMuted,
|
|
||||||
},
|
|
||||||
searchContainer: {
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: theme.spacing[2],
|
|
||||||
backgroundColor: theme.colors.surface2,
|
|
||||||
borderRadius: theme.borderRadius.md,
|
|
||||||
paddingHorizontal: theme.spacing[3],
|
|
||||||
},
|
|
||||||
addModelRow: {
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: theme.spacing[2],
|
|
||||||
},
|
|
||||||
addModelInputContainer: {
|
|
||||||
flex: 1,
|
|
||||||
minWidth: 0,
|
|
||||||
backgroundColor: theme.colors.surface2,
|
|
||||||
borderRadius: theme.borderRadius.md,
|
|
||||||
paddingHorizontal: theme.spacing[3],
|
|
||||||
},
|
|
||||||
addModelInput: {
|
|
||||||
paddingVertical: theme.spacing[2],
|
|
||||||
color: theme.colors.foreground,
|
|
||||||
fontSize: theme.fontSize.sm,
|
|
||||||
},
|
|
||||||
addModelButton: {
|
|
||||||
width: 34,
|
|
||||||
height: 34,
|
|
||||||
borderRadius: theme.borderRadius.full,
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
backgroundColor: theme.colors.accent,
|
|
||||||
},
|
|
||||||
addModelButtonHovered: {
|
|
||||||
backgroundColor: theme.colors.accentBright,
|
|
||||||
},
|
|
||||||
additionalModelsList: {
|
|
||||||
borderTopWidth: 1,
|
|
||||||
borderTopColor: theme.colors.border,
|
|
||||||
},
|
|
||||||
additionalModelRow: {
|
|
||||||
minHeight: 48,
|
|
||||||
paddingVertical: theme.spacing[2],
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
gap: theme.spacing[2],
|
|
||||||
},
|
|
||||||
additionalModelText: {
|
|
||||||
flex: 1,
|
|
||||||
minWidth: 0,
|
|
||||||
},
|
|
||||||
searchInput: {
|
|
||||||
flex: 1,
|
|
||||||
paddingVertical: theme.spacing[2],
|
|
||||||
color: theme.colors.foreground,
|
|
||||||
fontSize: theme.fontSize.sm,
|
|
||||||
},
|
|
||||||
modelsScroll: {
|
modelsScroll: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minHeight: 0,
|
minHeight: 0,
|
||||||
},
|
},
|
||||||
modelsScrollContent: {
|
modelsScrollContent: {
|
||||||
paddingBottom: theme.spacing[2],
|
paddingBottom: 0,
|
||||||
},
|
},
|
||||||
modelRow: {
|
emptyRow: {
|
||||||
paddingVertical: theme.spacing[3],
|
paddingVertical: theme.spacing[6],
|
||||||
},
|
paddingHorizontal: theme.spacing[4],
|
||||||
modelRowBorder: {
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
gap: theme.spacing[2],
|
||||||
borderTopWidth: 1,
|
borderTopWidth: 1,
|
||||||
borderTopColor: theme.colors.border,
|
borderTopColor: theme.colors.border,
|
||||||
},
|
},
|
||||||
modelLabel: {
|
|
||||||
fontSize: theme.fontSize.sm,
|
|
||||||
color: theme.colors.foreground,
|
|
||||||
},
|
|
||||||
modelId: {
|
|
||||||
fontSize: theme.fontSize.xs,
|
|
||||||
color: theme.colors.foregroundMuted,
|
|
||||||
fontFamily: Fonts.mono,
|
|
||||||
marginTop: 2,
|
|
||||||
},
|
|
||||||
emptyState: {
|
|
||||||
paddingVertical: theme.spacing[6],
|
|
||||||
alignItems: "center",
|
|
||||||
gap: theme.spacing[2],
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const DIAGNOSTIC_SHEET_SNAP_POINTS = ["50%", "85%"];
|
const DIAGNOSTIC_SHEET_SNAP_POINTS = ["50%", "85%"];
|
||||||
const DIAGNOSTIC_SEARCH_INPUT_STYLE = [sheetStyles.searchInput, isWeb && { outlineStyle: "none" }];
|
const DIAGNOSTIC_SEARCH_INPUT_STYLE = [sheetStyles.inlineInput, isWeb && { outlineStyle: "none" }];
|
||||||
const DIAGNOSTIC_ADD_MODEL_INPUT_STYLE = [
|
const DIAGNOSTIC_INLINE_INPUT_STYLE = [sheetStyles.inlineInput, isWeb && { outlineStyle: "none" }];
|
||||||
sheetStyles.addModelInput,
|
const MODEL_ROW_STYLE = [settingsStyles.row, settingsStyles.rowBorder];
|
||||||
isWeb && { outlineStyle: "none" },
|
const INLINE_ROW_STYLE = [settingsStyles.row, sheetStyles.inlineRow];
|
||||||
];
|
const MODELS_CARD_STYLE = [settingsStyles.card, sheetStyles.flexCard];
|
||||||
|
|||||||
@@ -14,22 +14,17 @@ import Animated, {
|
|||||||
} from "react-native-reanimated";
|
} from "react-native-reanimated";
|
||||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||||
|
|
||||||
type SwitchSize = "sm" | "md";
|
|
||||||
|
|
||||||
interface SwitchProps {
|
interface SwitchProps {
|
||||||
value: boolean;
|
value: boolean;
|
||||||
onValueChange?: (value: boolean) => void;
|
onValueChange?: (value: boolean) => void;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
size?: SwitchSize;
|
|
||||||
accessibilityLabel?: string;
|
accessibilityLabel?: string;
|
||||||
testID?: string;
|
testID?: string;
|
||||||
style?: StyleProp<ViewStyle>;
|
style?: StyleProp<ViewStyle>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SIZES: Record<SwitchSize, { track: { width: number; height: number }; thumb: number }> = {
|
const TRACK = { width: 34, height: 20 };
|
||||||
md: { track: { width: 44, height: 26 }, thumb: 22 },
|
const THUMB = 16;
|
||||||
sm: { track: { width: 34, height: 20 }, thumb: 16 },
|
|
||||||
};
|
|
||||||
|
|
||||||
const TIMING = { duration: 180, easing: Easing.inOut(Easing.ease) };
|
const TIMING = { duration: 180, easing: Easing.inOut(Easing.ease) };
|
||||||
|
|
||||||
@@ -37,13 +32,13 @@ export function Switch({
|
|||||||
value,
|
value,
|
||||||
onValueChange,
|
onValueChange,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
size = "md",
|
|
||||||
accessibilityLabel,
|
accessibilityLabel,
|
||||||
testID,
|
testID,
|
||||||
style,
|
style,
|
||||||
}: SwitchProps) {
|
}: SwitchProps) {
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
const { track, thumb } = SIZES[size];
|
const track = TRACK;
|
||||||
|
const thumb = THUMB;
|
||||||
const padding = (track.height - thumb) / 2;
|
const padding = (track.height - thumb) / 2;
|
||||||
const thumbTravel = track.width - thumb - padding * 2;
|
const thumbTravel = track.width - thumb - padding * 2;
|
||||||
|
|
||||||
|
|||||||
@@ -98,9 +98,6 @@ vi.mock("lucide-react-native", () => {
|
|||||||
ArrowUpRight: icon("ArrowUpRight"),
|
ArrowUpRight: icon("ArrowUpRight"),
|
||||||
Copy: icon("Copy"),
|
Copy: icon("Copy"),
|
||||||
FileText: icon("FileText"),
|
FileText: icon("FileText"),
|
||||||
Pause: icon("Pause"),
|
|
||||||
Play: icon("Play"),
|
|
||||||
RotateCw: icon("RotateCw"),
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -163,6 +160,28 @@ vi.mock("@/components/ui/button", () => ({
|
|||||||
),
|
),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock("@/components/ui/switch", () => ({
|
||||||
|
Switch: ({
|
||||||
|
value,
|
||||||
|
onValueChange,
|
||||||
|
disabled,
|
||||||
|
accessibilityLabel,
|
||||||
|
}: {
|
||||||
|
value: boolean;
|
||||||
|
onValueChange?: (next: boolean) => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
accessibilityLabel?: string;
|
||||||
|
}) =>
|
||||||
|
React.createElement("button", {
|
||||||
|
type: "button",
|
||||||
|
role: "switch",
|
||||||
|
"aria-label": accessibilityLabel,
|
||||||
|
"aria-checked": value,
|
||||||
|
disabled,
|
||||||
|
onClick: () => onValueChange?.(!value),
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
vi.mock("@/desktop/settings/desktop-settings", () => ({
|
vi.mock("@/desktop/settings/desktop-settings", () => ({
|
||||||
useDesktopSettings: () => ({
|
useDesktopSettings: () => ({
|
||||||
...settingsState,
|
...settingsState,
|
||||||
@@ -189,16 +208,12 @@ vi.mock("@/desktop/updates/desktop-updates", () => ({
|
|||||||
|
|
||||||
const daemonCommandMocks = vi.hoisted(() => ({
|
const daemonCommandMocks = vi.hoisted(() => ({
|
||||||
getCliDaemonStatusMock: vi.fn(),
|
getCliDaemonStatusMock: vi.fn(),
|
||||||
restartDesktopDaemonMock: vi.fn(),
|
|
||||||
startDesktopDaemonMock: vi.fn(),
|
|
||||||
stopDesktopDaemonMock: vi.fn(),
|
stopDesktopDaemonMock: vi.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock("@/desktop/daemon/desktop-daemon", () => ({
|
vi.mock("@/desktop/daemon/desktop-daemon", () => ({
|
||||||
getCliDaemonStatus: daemonCommandMocks.getCliDaemonStatusMock,
|
getCliDaemonStatus: daemonCommandMocks.getCliDaemonStatusMock,
|
||||||
restartDesktopDaemon: daemonCommandMocks.restartDesktopDaemonMock,
|
|
||||||
shouldUseDesktopDaemon: vi.fn(() => true),
|
shouldUseDesktopDaemon: vi.fn(() => true),
|
||||||
startDesktopDaemon: daemonCommandMocks.startDesktopDaemonMock,
|
|
||||||
stopDesktopDaemon: daemonCommandMocks.stopDesktopDaemonMock,
|
stopDesktopDaemon: daemonCommandMocks.stopDesktopDaemonMock,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -221,32 +236,40 @@ describe("LocalDaemonSection", () => {
|
|||||||
daemonStatusState.data.status.status = "running";
|
daemonStatusState.data.status.status = "running";
|
||||||
daemonStatusState.setStatus.mockReset();
|
daemonStatusState.setStatus.mockReset();
|
||||||
daemonStatusState.refetch.mockReset();
|
daemonStatusState.refetch.mockReset();
|
||||||
daemonCommandMocks.startDesktopDaemonMock.mockReset();
|
|
||||||
daemonCommandMocks.stopDesktopDaemonMock.mockReset();
|
daemonCommandMocks.stopDesktopDaemonMock.mockReset();
|
||||||
daemonCommandMocks.stopDesktopDaemonMock.mockResolvedValue({
|
daemonCommandMocks.stopDesktopDaemonMock.mockResolvedValue({
|
||||||
...daemonStatusState.data.status,
|
...daemonStatusState.data.status,
|
||||||
status: "stopped",
|
status: "stopped",
|
||||||
});
|
});
|
||||||
daemonCommandMocks.restartDesktopDaemonMock.mockReset();
|
|
||||||
daemonCommandMocks.getCliDaemonStatusMock.mockReset();
|
daemonCommandMocks.getCliDaemonStatusMock.mockReset();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows the keep-running-after-quit control enabled by default", () => {
|
it("renders the daemon toggles as switches reflecting current settings", () => {
|
||||||
const screen = render(<LocalDaemonSection />);
|
const screen = render(<LocalDaemonSection />);
|
||||||
|
|
||||||
expect(screen.getByText("Keep daemon running after quit")).toBeTruthy();
|
expect(screen.getByText("Keep daemon running after quit")).toBeTruthy();
|
||||||
expect(
|
expect(screen.getByText("Manage built-in daemon")).toBeTruthy();
|
||||||
screen.getByText(
|
|
||||||
"Enabled. The built-in daemon keeps running after you close the desktop app.",
|
const keepRunningSwitch = screen.getByRole("switch", {
|
||||||
),
|
name: "Keep daemon running after quit",
|
||||||
).toBeTruthy();
|
});
|
||||||
expect(screen.getByRole("button", { name: "Disable" })).toBeTruthy();
|
expect(keepRunningSwitch.getAttribute("aria-checked")).toBe("true");
|
||||||
|
|
||||||
|
const manageSwitch = screen.getByRole("switch", { name: "Manage built-in daemon" });
|
||||||
|
expect(manageSwitch.getAttribute("aria-checked")).toBe("true");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not render a duplicate restart-daemon control", () => {
|
||||||
|
const screen = render(<LocalDaemonSection />);
|
||||||
|
|
||||||
|
expect(screen.queryByText("Restart daemon")).toBeNull();
|
||||||
|
expect(screen.queryByText("Start daemon")).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("updates keep-running-after-quit without changing daemon lifecycle", async () => {
|
it("updates keep-running-after-quit without changing daemon lifecycle", async () => {
|
||||||
const screen = render(<LocalDaemonSection />);
|
const screen = render(<LocalDaemonSection />);
|
||||||
|
|
||||||
fireEvent.click(screen.getByRole("button", { name: "Disable" }));
|
fireEvent.click(screen.getByRole("switch", { name: "Keep daemon running after quit" }));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(settingsState.updateSettings).toHaveBeenCalledWith({
|
expect(settingsState.updateSettings).toHaveBeenCalledWith({
|
||||||
@@ -256,16 +279,14 @@ describe("LocalDaemonSection", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
expect(confirmDialogMock).not.toHaveBeenCalled();
|
expect(confirmDialogMock).not.toHaveBeenCalled();
|
||||||
expect(daemonCommandMocks.startDesktopDaemonMock).not.toHaveBeenCalled();
|
|
||||||
expect(daemonCommandMocks.stopDesktopDaemonMock).not.toHaveBeenCalled();
|
expect(daemonCommandMocks.stopDesktopDaemonMock).not.toHaveBeenCalled();
|
||||||
expect(daemonCommandMocks.restartDesktopDaemonMock).not.toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("pauses built-in daemon management and persists the setting through desktop settings", async () => {
|
it("pauses built-in daemon management and persists the setting through desktop settings", async () => {
|
||||||
confirmDialogMock.mockResolvedValue(true);
|
confirmDialogMock.mockResolvedValue(true);
|
||||||
const screen = render(<LocalDaemonSection />);
|
const screen = render(<LocalDaemonSection />);
|
||||||
|
|
||||||
fireEvent.click(screen.getByRole("button", { name: "Pause" }));
|
fireEvent.click(screen.getByRole("switch", { name: "Manage built-in daemon" }));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(daemonCommandMocks.stopDesktopDaemonMock).toHaveBeenCalledTimes(1);
|
expect(daemonCommandMocks.stopDesktopDaemonMock).toHaveBeenCalledTimes(1);
|
||||||
|
|||||||
@@ -4,17 +4,16 @@ import * as Clipboard from "expo-clipboard";
|
|||||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||||
import { settingsStyles } from "@/styles/settings";
|
import { settingsStyles } from "@/styles/settings";
|
||||||
import { SettingsSection } from "@/screens/settings/settings-section";
|
import { SettingsSection } from "@/screens/settings/settings-section";
|
||||||
import { ArrowUpRight, Play, Pause, RotateCw, Copy, FileText, Activity } from "lucide-react-native";
|
import { ArrowUpRight, Copy, FileText, Activity } from "lucide-react-native";
|
||||||
import { AdaptiveModalSheet } from "@/components/adaptive-modal-sheet";
|
import { AdaptiveModalSheet } from "@/components/adaptive-modal-sheet";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { confirmDialog } from "@/utils/confirm-dialog";
|
import { confirmDialog } from "@/utils/confirm-dialog";
|
||||||
import { openExternalUrl } from "@/utils/open-external-url";
|
import { openExternalUrl } from "@/utils/open-external-url";
|
||||||
import { isVersionMismatch } from "@/desktop/updates/desktop-updates";
|
import { isVersionMismatch } from "@/desktop/updates/desktop-updates";
|
||||||
import {
|
import {
|
||||||
getCliDaemonStatus,
|
getCliDaemonStatus,
|
||||||
restartDesktopDaemon,
|
|
||||||
shouldUseDesktopDaemon,
|
shouldUseDesktopDaemon,
|
||||||
startDesktopDaemon,
|
|
||||||
stopDesktopDaemon,
|
stopDesktopDaemon,
|
||||||
} from "@/desktop/daemon/desktop-daemon";
|
} from "@/desktop/daemon/desktop-daemon";
|
||||||
import { useDaemonStatus } from "@/desktop/hooks/use-daemon-status";
|
import { useDaemonStatus } from "@/desktop/hooks/use-daemon-status";
|
||||||
@@ -24,101 +23,14 @@ import { resolveAppVersion } from "@/utils/app-version";
|
|||||||
|
|
||||||
type DesktopDaemonSettings = DesktopSettings["daemon"];
|
type DesktopDaemonSettings = DesktopSettings["daemon"];
|
||||||
|
|
||||||
function managementLabel(isUpdating: boolean, isPaused: boolean): string {
|
|
||||||
if (isUpdating) return isPaused ? "Resuming..." : "Pausing...";
|
|
||||||
return isPaused ? "Resume" : "Pause";
|
|
||||||
}
|
|
||||||
|
|
||||||
function keepRunningLabel(isUpdating: boolean, isEnabled: boolean): string {
|
|
||||||
if (isUpdating) return isEnabled ? "Disabling..." : "Enabling...";
|
|
||||||
return isEnabled ? "Disable" : "Enable";
|
|
||||||
}
|
|
||||||
|
|
||||||
function restartLabel(isRestarting: boolean, isRunning: boolean, idleLabel: string): string {
|
|
||||||
if (!isRestarting) return idleLabel;
|
|
||||||
return isRunning ? "Restarting..." : "Starting...";
|
|
||||||
}
|
|
||||||
|
|
||||||
function useDaemonRestartAction(args: {
|
|
||||||
showSection: boolean;
|
|
||||||
daemonStatus: DesktopDaemonStatus | null;
|
|
||||||
daemonActionLabel: string;
|
|
||||||
setStatus: (status: DesktopDaemonStatus) => void;
|
|
||||||
refetch: () => void;
|
|
||||||
}) {
|
|
||||||
const { showSection, daemonStatus, daemonActionLabel, setStatus, refetch } = args;
|
|
||||||
const [isRestartingDaemon, setIsRestartingDaemon] = useState(false);
|
|
||||||
const [statusMessage, setStatusMessage] = useState<string | null>(null);
|
|
||||||
|
|
||||||
const handleUpdateLocalDaemon = useCallback(() => {
|
|
||||||
if (!showSection || isRestartingDaemon) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
void confirmDialog({
|
|
||||||
title: daemonActionLabel,
|
|
||||||
message:
|
|
||||||
daemonStatus?.status === "running"
|
|
||||||
? "This will restart the built-in daemon. The app will reconnect automatically."
|
|
||||||
: "This will start the built-in daemon.",
|
|
||||||
confirmLabel: daemonActionLabel,
|
|
||||||
cancelLabel: "Cancel",
|
|
||||||
})
|
|
||||||
.then((confirmed) => {
|
|
||||||
if (!confirmed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsRestartingDaemon(true);
|
|
||||||
setStatusMessage(null);
|
|
||||||
|
|
||||||
const action =
|
|
||||||
daemonStatus?.status === "running" ? restartDesktopDaemon : startDesktopDaemon;
|
|
||||||
|
|
||||||
void action()
|
|
||||||
.then((newStatus) => {
|
|
||||||
setStatus(newStatus);
|
|
||||||
setStatusMessage(
|
|
||||||
daemonStatus?.status === "running" ? "Daemon restarted." : "Daemon started.",
|
|
||||||
);
|
|
||||||
refetch();
|
|
||||||
return;
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error("[Settings] Failed to change desktop daemon state", error);
|
|
||||||
const message = error instanceof Error ? error.message : String(error);
|
|
||||||
setStatusMessage(`${daemonActionLabel} failed: ${message}`);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setIsRestartingDaemon(false);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error("[Settings] Failed to open desktop daemon action confirmation", error);
|
|
||||||
Alert.alert("Error", "Unable to open the daemon confirmation dialog.");
|
|
||||||
});
|
|
||||||
}, [
|
|
||||||
daemonActionLabel,
|
|
||||||
daemonStatus?.status,
|
|
||||||
isRestartingDaemon,
|
|
||||||
refetch,
|
|
||||||
setStatus,
|
|
||||||
showSection,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return { isRestartingDaemon, statusMessage, setStatusMessage, handleUpdateLocalDaemon };
|
|
||||||
}
|
|
||||||
|
|
||||||
function useDaemonManagementToggle(args: {
|
function useDaemonManagementToggle(args: {
|
||||||
daemonStatus: DesktopDaemonStatus | null;
|
daemonStatus: DesktopDaemonStatus | null;
|
||||||
settings: DesktopDaemonSettings;
|
settings: DesktopDaemonSettings;
|
||||||
updateSettings: (next: Partial<DesktopDaemonSettings>) => Promise<unknown>;
|
updateSettings: (next: Partial<DesktopDaemonSettings>) => Promise<unknown>;
|
||||||
setStatus: (status: DesktopDaemonStatus) => void;
|
setStatus: (status: DesktopDaemonStatus) => void;
|
||||||
setStatusMessage: (message: string | null) => void;
|
|
||||||
refetch: () => void;
|
refetch: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { daemonStatus, settings, updateSettings, setStatus, setStatusMessage, refetch } = args;
|
const { daemonStatus, settings, updateSettings, setStatus, refetch } = args;
|
||||||
const [isUpdatingDaemonManagement, setIsUpdatingDaemonManagement] = useState(false);
|
const [isUpdatingDaemonManagement, setIsUpdatingDaemonManagement] = useState(false);
|
||||||
|
|
||||||
const handleToggleDaemonManagement = useCallback(() => {
|
const handleToggleDaemonManagement = useCallback(() => {
|
||||||
@@ -128,12 +40,7 @@ function useDaemonManagementToggle(args: {
|
|||||||
|
|
||||||
if (!settings.manageBuiltInDaemon) {
|
if (!settings.manageBuiltInDaemon) {
|
||||||
setIsUpdatingDaemonManagement(true);
|
setIsUpdatingDaemonManagement(true);
|
||||||
setStatusMessage(null);
|
|
||||||
void updateSettings({ manageBuiltInDaemon: true })
|
void updateSettings({ manageBuiltInDaemon: true })
|
||||||
.then(() => {
|
|
||||||
setStatusMessage("Built-in daemon management resumed.");
|
|
||||||
return;
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error("[Settings] Failed to update built-in daemon management", error);
|
console.error("[Settings] Failed to update built-in daemon management", error);
|
||||||
Alert.alert("Error", "Unable to update built-in daemon management.");
|
Alert.alert("Error", "Unable to update built-in daemon management.");
|
||||||
@@ -158,7 +65,6 @@ function useDaemonManagementToggle(args: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setIsUpdatingDaemonManagement(true);
|
setIsUpdatingDaemonManagement(true);
|
||||||
setStatusMessage(null);
|
|
||||||
|
|
||||||
const stopPromise =
|
const stopPromise =
|
||||||
daemonStatus?.status === "running"
|
daemonStatus?.status === "running"
|
||||||
@@ -174,7 +80,6 @@ function useDaemonManagementToggle(args: {
|
|||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
refetch();
|
refetch();
|
||||||
setStatusMessage("Built-in daemon paused and stopped.");
|
|
||||||
return;
|
return;
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
@@ -195,7 +100,6 @@ function useDaemonManagementToggle(args: {
|
|||||||
isUpdatingDaemonManagement,
|
isUpdatingDaemonManagement,
|
||||||
refetch,
|
refetch,
|
||||||
setStatus,
|
setStatus,
|
||||||
setStatusMessage,
|
|
||||||
settings.manageBuiltInDaemon,
|
settings.manageBuiltInDaemon,
|
||||||
updateSettings,
|
updateSettings,
|
||||||
]);
|
]);
|
||||||
@@ -318,7 +222,7 @@ function DaemonLogsModal({ visible, onClose, daemonLogs }: DaemonLogsModalProps)
|
|||||||
snapPoints={LOGS_MODAL_SNAP_POINTS}
|
snapPoints={LOGS_MODAL_SNAP_POINTS}
|
||||||
>
|
>
|
||||||
<View style={styles.modalBody}>
|
<View style={styles.modalBody}>
|
||||||
<Text style={settingsStyles.rowHint}>{daemonLogs?.logPath ?? "Log path unavailable."}</Text>
|
<Text style={settingsStyles.rowHint}>{daemonLogs?.logPath ?? "Log path unavailable"}</Text>
|
||||||
<Text style={styles.logOutput} selectable>
|
<Text style={styles.logOutput} selectable>
|
||||||
{daemonLogs?.contents?.length ? daemonLogs.contents : "(log file is empty)"}
|
{daemonLogs?.contents?.length ? daemonLogs.contents : "(log file is empty)"}
|
||||||
</Text>
|
</Text>
|
||||||
@@ -369,9 +273,6 @@ interface DaemonInfoCardProps {
|
|||||||
daemonStatusStateText: string;
|
daemonStatusStateText: string;
|
||||||
daemonStatusDetailText: string;
|
daemonStatusDetailText: string;
|
||||||
isDaemonManagementPaused: boolean;
|
isDaemonManagementPaused: boolean;
|
||||||
playIcon: ReactElement;
|
|
||||||
pauseIcon: ReactElement;
|
|
||||||
rotateIcon: ReactElement;
|
|
||||||
copyIcon: ReactElement;
|
copyIcon: ReactElement;
|
||||||
fileTextIcon: ReactElement;
|
fileTextIcon: ReactElement;
|
||||||
activityIcon: ReactElement;
|
activityIcon: ReactElement;
|
||||||
@@ -380,12 +281,6 @@ interface DaemonInfoCardProps {
|
|||||||
keepRunningAfterQuit: boolean;
|
keepRunningAfterQuit: boolean;
|
||||||
handleToggleKeepRunningAfterQuit: () => void;
|
handleToggleKeepRunningAfterQuit: () => void;
|
||||||
isUpdatingKeepRunningAfterQuit: boolean;
|
isUpdatingKeepRunningAfterQuit: boolean;
|
||||||
daemonActionLabel: string;
|
|
||||||
daemonActionMessage: string;
|
|
||||||
statusMessage: string | null;
|
|
||||||
handleUpdateLocalDaemon: () => void;
|
|
||||||
isRestartingDaemon: boolean;
|
|
||||||
daemonStatus: DesktopDaemonStatus | null;
|
|
||||||
daemonLogs: { logPath?: string } | null;
|
daemonLogs: { logPath?: string } | null;
|
||||||
handleCopyLogPath: () => void;
|
handleCopyLogPath: () => void;
|
||||||
handleOpenLogs: () => void;
|
handleOpenLogs: () => void;
|
||||||
@@ -398,9 +293,6 @@ function DaemonInfoCard(props: DaemonInfoCardProps) {
|
|||||||
daemonStatusStateText,
|
daemonStatusStateText,
|
||||||
daemonStatusDetailText,
|
daemonStatusDetailText,
|
||||||
isDaemonManagementPaused,
|
isDaemonManagementPaused,
|
||||||
playIcon,
|
|
||||||
pauseIcon,
|
|
||||||
rotateIcon,
|
|
||||||
copyIcon,
|
copyIcon,
|
||||||
fileTextIcon,
|
fileTextIcon,
|
||||||
activityIcon,
|
activityIcon,
|
||||||
@@ -409,12 +301,6 @@ function DaemonInfoCard(props: DaemonInfoCardProps) {
|
|||||||
keepRunningAfterQuit,
|
keepRunningAfterQuit,
|
||||||
handleToggleKeepRunningAfterQuit,
|
handleToggleKeepRunningAfterQuit,
|
||||||
isUpdatingKeepRunningAfterQuit,
|
isUpdatingKeepRunningAfterQuit,
|
||||||
daemonActionLabel,
|
|
||||||
daemonActionMessage,
|
|
||||||
statusMessage,
|
|
||||||
handleUpdateLocalDaemon,
|
|
||||||
isRestartingDaemon,
|
|
||||||
daemonStatus,
|
|
||||||
daemonLogs,
|
daemonLogs,
|
||||||
handleCopyLogPath,
|
handleCopyLogPath,
|
||||||
handleOpenLogs,
|
handleOpenLogs,
|
||||||
@@ -422,28 +308,12 @@ function DaemonInfoCard(props: DaemonInfoCardProps) {
|
|||||||
isLoadingCliStatus,
|
isLoadingCliStatus,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const managementButtonLabel = managementLabel(
|
|
||||||
isUpdatingDaemonManagement,
|
|
||||||
isDaemonManagementPaused,
|
|
||||||
);
|
|
||||||
const keepRunningButtonLabel = keepRunningLabel(
|
|
||||||
isUpdatingKeepRunningAfterQuit,
|
|
||||||
keepRunningAfterQuit,
|
|
||||||
);
|
|
||||||
const restartButtonLabel = restartLabel(
|
|
||||||
isRestartingDaemon,
|
|
||||||
daemonStatus?.status === "running",
|
|
||||||
daemonActionLabel,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={settingsStyles.card}>
|
<View style={settingsStyles.card}>
|
||||||
<View style={settingsStyles.row}>
|
<View style={settingsStyles.row}>
|
||||||
<View style={settingsStyles.rowContent}>
|
<View style={settingsStyles.rowContent}>
|
||||||
<Text style={settingsStyles.rowTitle}>Status</Text>
|
<Text style={settingsStyles.rowTitle}>Status</Text>
|
||||||
<Text style={settingsStyles.rowHint}>
|
<Text style={settingsStyles.rowHint}>Only the built-in desktop daemon is shown here</Text>
|
||||||
Only the built-in desktop daemon is shown here.
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.statusValueGroup}>
|
<View style={styles.statusValueGroup}>
|
||||||
<Text style={styles.valueText}>{daemonStatusStateText}</Text>
|
<Text style={styles.valueText}>{daemonStatusStateText}</Text>
|
||||||
@@ -452,62 +322,33 @@ function DaemonInfoCard(props: DaemonInfoCardProps) {
|
|||||||
</View>
|
</View>
|
||||||
<View style={ROW_WITH_BORDER_STYLE}>
|
<View style={ROW_WITH_BORDER_STYLE}>
|
||||||
<View style={settingsStyles.rowContent}>
|
<View style={settingsStyles.rowContent}>
|
||||||
<Text style={settingsStyles.rowTitle}>Daemon management</Text>
|
<Text style={settingsStyles.rowTitle}>Manage built-in daemon</Text>
|
||||||
<Text style={settingsStyles.rowHint}>
|
<Text style={settingsStyles.rowHint}>Let Paseo start and stop the built-in daemon</Text>
|
||||||
{isDaemonManagementPaused
|
|
||||||
? "Paused. The built-in daemon stays stopped until you start it again."
|
|
||||||
: "Enabled. Paseo can manage the built-in daemon from the desktop app."}
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
<Button
|
<Switch
|
||||||
variant="outline"
|
value={!isDaemonManagementPaused}
|
||||||
size="sm"
|
onValueChange={handleToggleDaemonManagement}
|
||||||
leftIcon={isDaemonManagementPaused ? playIcon : pauseIcon}
|
|
||||||
onPress={handleToggleDaemonManagement}
|
|
||||||
disabled={isUpdatingDaemonManagement}
|
disabled={isUpdatingDaemonManagement}
|
||||||
>
|
accessibilityLabel="Manage built-in daemon"
|
||||||
{managementButtonLabel}
|
/>
|
||||||
</Button>
|
|
||||||
</View>
|
</View>
|
||||||
<View style={ROW_WITH_BORDER_STYLE}>
|
<View style={ROW_WITH_BORDER_STYLE}>
|
||||||
<View style={settingsStyles.rowContent}>
|
<View style={settingsStyles.rowContent}>
|
||||||
<Text style={settingsStyles.rowTitle}>Keep daemon running after quit</Text>
|
<Text style={settingsStyles.rowTitle}>Keep daemon running after quit</Text>
|
||||||
<Text style={settingsStyles.rowHint}>
|
<Text style={settingsStyles.rowHint}>Daemon keeps running when you quit Paseo</Text>
|
||||||
{keepRunningAfterQuit
|
|
||||||
? "Enabled. The built-in daemon keeps running after you close the desktop app."
|
|
||||||
: "Disabled. Quitting the desktop app stops the built-in daemon if desktop management is enabled."}
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
<Button
|
<Switch
|
||||||
variant="outline"
|
value={keepRunningAfterQuit}
|
||||||
size="sm"
|
onValueChange={handleToggleKeepRunningAfterQuit}
|
||||||
onPress={handleToggleKeepRunningAfterQuit}
|
|
||||||
disabled={isUpdatingKeepRunningAfterQuit}
|
disabled={isUpdatingKeepRunningAfterQuit}
|
||||||
>
|
accessibilityLabel="Keep daemon running after quit"
|
||||||
{keepRunningButtonLabel}
|
/>
|
||||||
</Button>
|
|
||||||
</View>
|
|
||||||
<View style={ROW_WITH_BORDER_STYLE}>
|
|
||||||
<View style={settingsStyles.rowContent}>
|
|
||||||
<Text style={settingsStyles.rowTitle}>{daemonActionLabel}</Text>
|
|
||||||
<Text style={settingsStyles.rowHint}>{daemonActionMessage}</Text>
|
|
||||||
{statusMessage ? <Text style={styles.statusText}>{statusMessage}</Text> : null}
|
|
||||||
</View>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
leftIcon={rotateIcon}
|
|
||||||
onPress={handleUpdateLocalDaemon}
|
|
||||||
disabled={isRestartingDaemon}
|
|
||||||
>
|
|
||||||
{restartButtonLabel}
|
|
||||||
</Button>
|
|
||||||
</View>
|
</View>
|
||||||
<View style={ROW_WITH_BORDER_STYLE}>
|
<View style={ROW_WITH_BORDER_STYLE}>
|
||||||
<View style={settingsStyles.rowContent}>
|
<View style={settingsStyles.rowContent}>
|
||||||
<Text style={settingsStyles.rowTitle}>Log file</Text>
|
<Text style={settingsStyles.rowTitle}>Log file</Text>
|
||||||
<Text style={settingsStyles.rowHint}>
|
<Text style={settingsStyles.rowHint}>
|
||||||
{daemonLogs?.logPath ?? "Log path unavailable."}
|
{daemonLogs?.logPath ?? "Log path unavailable"}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.actionGroup}>
|
<View style={styles.actionGroup}>
|
||||||
@@ -531,7 +372,7 @@ function DaemonInfoCard(props: DaemonInfoCardProps) {
|
|||||||
<View style={settingsStyles.rowContent}>
|
<View style={settingsStyles.rowContent}>
|
||||||
<Text style={settingsStyles.rowTitle}>Full status</Text>
|
<Text style={settingsStyles.rowTitle}>Full status</Text>
|
||||||
<Text style={settingsStyles.rowHint}>
|
<Text style={settingsStyles.rowHint}>
|
||||||
Runs `paseo daemon status` and shows the output.
|
Runs `paseo daemon status` and shows the output
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<Button
|
<Button
|
||||||
@@ -569,27 +410,12 @@ export function LocalDaemonSection() {
|
|||||||
statusError ?? (daemonStatus?.status === "running" ? daemonStatus.status : "not running");
|
statusError ?? (daemonStatus?.status === "running" ? daemonStatus.status : "not running");
|
||||||
const daemonStatusDetailText = `PID ${daemonStatus?.pid ? daemonStatus.pid : "—"}`;
|
const daemonStatusDetailText = `PID ${daemonStatus?.pid ? daemonStatus.pid : "—"}`;
|
||||||
const isDaemonManagementPaused = !daemonSettings.manageBuiltInDaemon;
|
const isDaemonManagementPaused = !daemonSettings.manageBuiltInDaemon;
|
||||||
const daemonActionLabel = daemonStatus?.status === "running" ? "Restart daemon" : "Start daemon";
|
|
||||||
const daemonActionMessage =
|
|
||||||
daemonStatus?.status === "running"
|
|
||||||
? "Restarts the built-in daemon."
|
|
||||||
: "Starts the built-in daemon.";
|
|
||||||
|
|
||||||
const { isRestartingDaemon, statusMessage, setStatusMessage, handleUpdateLocalDaemon } =
|
|
||||||
useDaemonRestartAction({
|
|
||||||
showSection,
|
|
||||||
daemonStatus,
|
|
||||||
daemonActionLabel,
|
|
||||||
setStatus,
|
|
||||||
refetch,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { isUpdatingDaemonManagement, handleToggleDaemonManagement } = useDaemonManagementToggle({
|
const { isUpdatingDaemonManagement, handleToggleDaemonManagement } = useDaemonManagementToggle({
|
||||||
daemonStatus,
|
daemonStatus,
|
||||||
settings: daemonSettings,
|
settings: daemonSettings,
|
||||||
updateSettings: updateDaemonSettings,
|
updateSettings: updateDaemonSettings,
|
||||||
setStatus,
|
setStatus,
|
||||||
setStatusMessage,
|
|
||||||
refetch,
|
refetch,
|
||||||
});
|
});
|
||||||
const { isUpdatingKeepRunningAfterQuit, handleToggleKeepRunningAfterQuit } =
|
const { isUpdatingKeepRunningAfterQuit, handleToggleKeepRunningAfterQuit } =
|
||||||
@@ -622,18 +448,6 @@ export function LocalDaemonSection() {
|
|||||||
() => <ArrowUpRight size={theme.iconSize.sm} color={theme.colors.foregroundMuted} />,
|
() => <ArrowUpRight size={theme.iconSize.sm} color={theme.colors.foregroundMuted} />,
|
||||||
[theme.iconSize.sm, theme.colors.foregroundMuted],
|
[theme.iconSize.sm, theme.colors.foregroundMuted],
|
||||||
);
|
);
|
||||||
const playIcon = useMemo(
|
|
||||||
() => <Play size={theme.iconSize.sm} color={theme.colors.foreground} />,
|
|
||||||
[theme.iconSize.sm, theme.colors.foreground],
|
|
||||||
);
|
|
||||||
const pauseIcon = useMemo(
|
|
||||||
() => <Pause size={theme.iconSize.sm} color={theme.colors.foreground} />,
|
|
||||||
[theme.iconSize.sm, theme.colors.foreground],
|
|
||||||
);
|
|
||||||
const rotateIcon = useMemo(
|
|
||||||
() => <RotateCw size={theme.iconSize.sm} color={theme.colors.foreground} />,
|
|
||||||
[theme.iconSize.sm, theme.colors.foreground],
|
|
||||||
);
|
|
||||||
const copyIcon = useMemo(
|
const copyIcon = useMemo(
|
||||||
() => <Copy size={theme.iconSize.sm} color={theme.colors.foreground} />,
|
() => <Copy size={theme.iconSize.sm} color={theme.colors.foreground} />,
|
||||||
[theme.iconSize.sm, theme.colors.foreground],
|
[theme.iconSize.sm, theme.colors.foreground],
|
||||||
@@ -684,9 +498,6 @@ export function LocalDaemonSection() {
|
|||||||
daemonStatusStateText={daemonStatusStateText}
|
daemonStatusStateText={daemonStatusStateText}
|
||||||
daemonStatusDetailText={daemonStatusDetailText}
|
daemonStatusDetailText={daemonStatusDetailText}
|
||||||
isDaemonManagementPaused={isDaemonManagementPaused}
|
isDaemonManagementPaused={isDaemonManagementPaused}
|
||||||
playIcon={playIcon}
|
|
||||||
pauseIcon={pauseIcon}
|
|
||||||
rotateIcon={rotateIcon}
|
|
||||||
copyIcon={copyIcon}
|
copyIcon={copyIcon}
|
||||||
fileTextIcon={fileTextIcon}
|
fileTextIcon={fileTextIcon}
|
||||||
activityIcon={activityIcon}
|
activityIcon={activityIcon}
|
||||||
@@ -695,12 +506,6 @@ export function LocalDaemonSection() {
|
|||||||
keepRunningAfterQuit={daemonSettings.keepRunningAfterQuit}
|
keepRunningAfterQuit={daemonSettings.keepRunningAfterQuit}
|
||||||
handleToggleKeepRunningAfterQuit={handleToggleKeepRunningAfterQuit}
|
handleToggleKeepRunningAfterQuit={handleToggleKeepRunningAfterQuit}
|
||||||
isUpdatingKeepRunningAfterQuit={isUpdatingKeepRunningAfterQuit}
|
isUpdatingKeepRunningAfterQuit={isUpdatingKeepRunningAfterQuit}
|
||||||
daemonActionLabel={daemonActionLabel}
|
|
||||||
daemonActionMessage={daemonActionMessage}
|
|
||||||
statusMessage={statusMessage}
|
|
||||||
handleUpdateLocalDaemon={handleUpdateLocalDaemon}
|
|
||||||
isRestartingDaemon={isRestartingDaemon}
|
|
||||||
daemonStatus={daemonStatus}
|
|
||||||
daemonLogs={daemonLogs}
|
daemonLogs={daemonLogs}
|
||||||
handleCopyLogPath={handleCopyLogPath}
|
handleCopyLogPath={handleCopyLogPath}
|
||||||
handleOpenLogs={handleOpenLogs}
|
handleOpenLogs={handleOpenLogs}
|
||||||
@@ -762,11 +567,6 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
color: theme.colors.foregroundMuted,
|
color: theme.colors.foregroundMuted,
|
||||||
fontSize: theme.fontSize.xs,
|
fontSize: theme.fontSize.xs,
|
||||||
},
|
},
|
||||||
statusText: {
|
|
||||||
color: theme.colors.foregroundMuted,
|
|
||||||
fontSize: theme.fontSize.xs,
|
|
||||||
marginTop: theme.spacing[1],
|
|
||||||
},
|
|
||||||
warningCard: {
|
warningCard: {
|
||||||
marginTop: theme.spacing[3],
|
marginTop: theme.spacing[3],
|
||||||
borderRadius: theme.borderRadius.lg,
|
borderRadius: theme.borderRadius.lg,
|
||||||
|
|||||||
@@ -133,9 +133,7 @@ export function IntegrationsSection() {
|
|||||||
<Terminal size={theme.iconSize.md} color={theme.colors.foreground} />
|
<Terminal size={theme.iconSize.md} color={theme.colors.foreground} />
|
||||||
<Text style={settingsStyles.rowTitle}>Command line</Text>
|
<Text style={settingsStyles.rowTitle}>Command line</Text>
|
||||||
</View>
|
</View>
|
||||||
<Text style={settingsStyles.rowHint}>
|
<Text style={settingsStyles.rowHint}>Control and script agents from your terminal</Text>
|
||||||
Control and script agents from your terminal.
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
{cliStatus?.installed ? (
|
{cliStatus?.installed ? (
|
||||||
<View style={styles.installedLabel}>
|
<View style={styles.installedLabel}>
|
||||||
@@ -160,7 +158,7 @@ export function IntegrationsSection() {
|
|||||||
<Text style={settingsStyles.rowTitle}>Orchestration skills</Text>
|
<Text style={settingsStyles.rowTitle}>Orchestration skills</Text>
|
||||||
</View>
|
</View>
|
||||||
<Text style={settingsStyles.rowHint}>
|
<Text style={settingsStyles.rowHint}>
|
||||||
Teach your agents to orchestrate through the CLI.
|
Teach your agents to orchestrate through the CLI
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
{skillsStatus?.installed ? (
|
{skillsStatus?.installed ? (
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { formatConnectionStatus, getConnectionStatusTone } from "@/utils/daemons
|
|||||||
import { confirmDialog } from "@/utils/confirm-dialog";
|
import { confirmDialog } from "@/utils/confirm-dialog";
|
||||||
import { settingsStyles } from "@/styles/settings";
|
import { settingsStyles } from "@/styles/settings";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { SegmentedControl } from "@/components/ui/segmented-control";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { AdaptiveModalSheet } from "@/components/adaptive-modal-sheet";
|
import { AdaptiveModalSheet } from "@/components/adaptive-modal-sheet";
|
||||||
import { useDaemonConfig } from "@/hooks/use-daemon-config";
|
import { useDaemonConfig } from "@/hooks/use-daemon-config";
|
||||||
import { useIsLocalDaemon } from "@/hooks/use-is-local-daemon";
|
import { useIsLocalDaemon } from "@/hooks/use-is-local-daemon";
|
||||||
@@ -29,11 +29,6 @@ import { LocalDaemonSection } from "@/desktop/components/desktop-updates-section
|
|||||||
const RESTART_CONFIRMATION_MESSAGE =
|
const RESTART_CONFIRMATION_MESSAGE =
|
||||||
"This will restart the daemon. Agents running on it will keep going; the app will reconnect automatically.";
|
"This will restart the daemon. Agents running on it will keep going; the app will reconnect automatically.";
|
||||||
|
|
||||||
const INJECT_TOOLS_OPTIONS = [
|
|
||||||
{ value: "on", label: "On" },
|
|
||||||
{ value: "off", label: "Off" },
|
|
||||||
];
|
|
||||||
|
|
||||||
function formatHostConnectionLabel(connection: HostConnection): string {
|
function formatHostConnectionLabel(connection: HostConnection): string {
|
||||||
if (connection.type === "relay") {
|
if (connection.type === "relay") {
|
||||||
return `Relay (${connection.relayEndpoint})`;
|
return `Relay (${connection.relayEndpoint})`;
|
||||||
@@ -579,7 +574,7 @@ function RestartDaemonCard({ host }: { host: HostProfile }) {
|
|||||||
<View style={settingsStyles.rowContent}>
|
<View style={settingsStyles.rowContent}>
|
||||||
<Text style={settingsStyles.rowTitle}>Restart daemon</Text>
|
<Text style={settingsStyles.rowTitle}>Restart daemon</Text>
|
||||||
<Text style={settingsStyles.rowHint}>
|
<Text style={settingsStyles.rowHint}>
|
||||||
Restarts the daemon process. The app will reconnect automatically.
|
Restarts the daemon process. The app will reconnect automatically
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<Button
|
<Button
|
||||||
@@ -602,10 +597,10 @@ function InjectPaseoToolsCard({ serverId }: { serverId: string }) {
|
|||||||
const { config, patchConfig } = useDaemonConfig(serverId);
|
const { config, patchConfig } = useDaemonConfig(serverId);
|
||||||
|
|
||||||
const handleValueChange = useCallback(
|
const handleValueChange = useCallback(
|
||||||
(value: string) => {
|
(next: boolean) => {
|
||||||
void patchConfig({
|
void patchConfig({
|
||||||
mcp: {
|
mcp: {
|
||||||
injectIntoAgents: value === "on",
|
injectIntoAgents: next,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -623,11 +618,10 @@ function InjectPaseoToolsCard({ serverId }: { serverId: string }) {
|
|||||||
Automatically inject Paseo MCP tools into new agents
|
Automatically inject Paseo MCP tools into new agents
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<SegmentedControl
|
<Switch
|
||||||
size="sm"
|
value={config?.mcp.injectIntoAgents !== false}
|
||||||
value={config?.mcp.injectIntoAgents === false ? "off" : "on"}
|
|
||||||
onValueChange={handleValueChange}
|
onValueChange={handleValueChange}
|
||||||
options={INJECT_TOOLS_OPTIONS}
|
accessibilityLabel="Inject Paseo tools"
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -123,7 +123,6 @@ function ProviderRow({
|
|||||||
value={enabled}
|
value={enabled}
|
||||||
onValueChange={handleToggleValueChange}
|
onValueChange={handleToggleValueChange}
|
||||||
disabled={isToggling}
|
disabled={isToggling}
|
||||||
size="sm"
|
|
||||||
accessibilityLabel={`Enable ${def.label}`}
|
accessibilityLabel={`Enable ${def.label}`}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user