1import { useNavigation } from "@react-navigation/native";
2import * as Haptics from "expo-haptics";
3import { useMemo, useState } from "react";
4
5import { AppText as Text } from "../../components/AppText";
6import { buildModelOptions, groupByProvider } from "../../lib/modelOptions";
7import { resolveProviderOptionDescriptors } from "../../lib/providerOptions";
8import { useEnvironmentServerConfig, useProjects } from "../../state/entities";
9import { useDebouncedValue, usePaginatedBranches } from "../../state/queries";
10import { BranchPickerScreen } from "../threads/NewTaskContextPickerScreens";
11import { ThreadSettingsPickerScreen } from "../threads/ThreadSettingsSheet";
12import { SettingsScreen } from "./components/SettingsScreen";
13import { useScheduledTaskEditor } from "./scheduled-task-editor";
14
15function MissingTaskDraft() {
16 return (
17 <SettingsScreen title="Scheduled task">
18 <Text className="p-5 text-base text-foreground-muted">Open a scheduled task form first.</Text>
19 </SettingsScreen>
20 );
21}
22
23export function ScheduledTaskModelPickerRouteScreen() {
24 const navigation = useNavigation();
25 const { editor, setEditor } = useScheduledTaskEditor();
26 const config = useEnvironmentServerConfig(editor?.environmentId ?? null);
27 const selectedModel = editor?.draft.modelSelection ?? null;
28 const projectId = editor?.draft.projectId ?? null;
29 const models = useMemo(
30 () => buildModelOptions(config, selectedModel, undefined, projectId),
31 [config, selectedModel, projectId],
32 );
33 const providerGroups = useMemo(() => groupByProvider(models), [models]);
34 const selectedOption = models.find(
35 (option) =>
36 option.selection.instanceId === selectedModel?.instanceId &&
37 option.selection.model === selectedModel.model,
38 );
39 const optionDescriptors = useMemo(
40 () =>
41 resolveProviderOptionDescriptors({
42 capabilities: selectedOption?.capabilities,
43 selections: selectedModel?.options,
44 }),
45 [selectedOption?.capabilities, selectedModel?.options],
46 );
47
48 if (!editor) return <MissingTaskDraft />;
49
50 return (
51 <ThreadSettingsPickerScreen
52 environmentId={editor.environmentId}
53 projectId={projectId}
54 providerGroups={providerGroups}
55 selectedModel={selectedModel}
56 onSelectModel={(option) =>
57 setEditor((current) =>
58 current
59 ? {
60 ...current,
61 draft: {
62 ...current.draft,
63 modelSelection: option.selection,
64 modelSelectionIsExplicit: true,
65 },
66 }
67 : current,
68 )
69 }
70 optionDescriptors={optionDescriptors}
71 onUpdateOptionSelections={(options) =>
72 setEditor((current) =>
73 current?.draft.modelSelection
74 ? {
75 ...current,
76 draft: {
77 ...current.draft,
78 modelSelection: { ...current.draft.modelSelection, options },
79 modelSelectionIsExplicit: true,
80 },
81 }
82 : current,
83 )
84 }
85 runtimeMode={editor.draft.runtimeMode}
86 onUpdateRuntimeMode={(runtimeMode) =>
87 setEditor((current) =>
88 current
89 ? {
90 ...current,
91 draft: { ...current.draft, runtimeMode },
92 }
93 : current,
94 )
95 }
96 onClose={() => navigation.goBack()}
97 />
98 );
99}
100
101export function ScheduledTaskBranchPickerRouteScreen() {
102 const navigation = useNavigation();
103 const { editor, setEditor } = useScheduledTaskEditor();
104 const projects = useProjects();
105 const project =
106 projects.find(
107 (entry) =>
108 entry.environmentId === editor?.environmentId && entry.id === editor.draft.projectId,
109 ) ?? null;
110 const [query, setQuery] = useState("");
111 const debouncedQuery = useDebouncedValue(query, 150);
112 const branches = usePaginatedBranches({
113 environmentId: editor?.environmentId ?? null,
114 cwd: project?.workspaceRoot ?? null,
115 query: debouncedQuery,
116 });
117 const visibleBranches = branches.refs.filter(
118 (branch) => !branch.isRemote && branch.name.toLowerCase().includes(query.trim().toLowerCase()),
119 );
120
121 if (!editor) return <MissingTaskDraft />;
122
123 return (
124 <BranchPickerScreen
125 title="Base branch"
126 project={project}
127 branches={visibleBranches}
128 selectedBranchName={editor.draft.baseRef}
129 query={query}
130 onQueryChange={setQuery}
131 loading={
132 query.trim() !== debouncedQuery.trim() || (branches.isPending && branches.data === null)
133 }
134 error={project ? branches.error : "This project is no longer available."}
135 refreshing={branches.isFetchingNextPage}
136 hasMore={branches.data?.nextCursor != null}
137 onRefresh={branches.refresh}
138 onLoadMore={branches.loadNext}
139 onSelect={(branch) => {
140 void Haptics.selectionAsync();
141 setEditor((current) =>
142 current ? { ...current, draft: { ...current.draft, baseRef: branch.name } } : current,
143 );
144 navigation.goBack();
145 }}
146 worktree={{
147 startFromOrigin: editor.draft.startFromOrigin,
148 onChangeStartFromOrigin: (startFromOrigin) =>
149 setEditor((current) =>
150 current
151 ? {
152 ...current,
153 draft: { ...current.draft, startFromOrigin },
154 }
155 : current,
156 ),
157 }}
158 />
159 );
160}