clawdex-mobile 2.0.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/pages.yml +41 -0
- package/AGENTS.md +263 -110
- package/README.md +1 -1
- package/apps/mobile/.env.example +2 -2
- package/apps/mobile/App.tsx +175 -14
- package/apps/mobile/app.json +27 -9
- package/apps/mobile/eas.json +14 -4
- package/apps/mobile/package.json +13 -13
- package/apps/mobile/src/api/__tests__/chatMapping.test.ts +219 -0
- package/apps/mobile/src/api/__tests__/client.test.ts +579 -6
- package/apps/mobile/src/api/__tests__/ws.test.ts +27 -0
- package/apps/mobile/src/api/account.ts +47 -0
- package/apps/mobile/src/api/chatMapping.ts +435 -18
- package/apps/mobile/src/api/client.ts +296 -36
- package/apps/mobile/src/api/rateLimits.ts +143 -0
- package/apps/mobile/src/api/types.ts +106 -0
- package/apps/mobile/src/api/ws.ts +10 -1
- package/apps/mobile/src/components/ChatHeader.tsx +12 -12
- package/apps/mobile/src/components/ChatInput.tsx +154 -88
- package/apps/mobile/src/components/ChatMessage.tsx +548 -93
- package/apps/mobile/src/components/ComposerUsageLimits.tsx +167 -0
- package/apps/mobile/src/components/SelectionSheet.tsx +466 -0
- package/apps/mobile/src/components/ToolBlock.tsx +17 -15
- package/apps/mobile/src/components/VoiceRecordingWaveform.tsx +181 -0
- package/apps/mobile/src/components/WorkspacePickerModal.tsx +572 -0
- package/apps/mobile/src/components/__tests__/chat-input-layout.test.ts +35 -0
- package/apps/mobile/src/components/__tests__/chatImageSource.test.ts +44 -0
- package/apps/mobile/src/components/__tests__/composerUsageLimits.test.ts +138 -0
- package/apps/mobile/src/components/__tests__/voiceWaveform.test.ts +31 -0
- package/apps/mobile/src/components/chat-input-layout.ts +59 -0
- package/apps/mobile/src/components/chatImageSource.ts +86 -0
- package/apps/mobile/src/components/usageLimitBadges.ts +109 -0
- package/apps/mobile/src/components/voiceWaveform.ts +46 -0
- package/apps/mobile/src/config.ts +9 -2
- package/apps/mobile/src/hooks/useVoiceRecorder.ts +8 -1
- package/apps/mobile/src/navigation/DrawerContent.tsx +607 -457
- package/apps/mobile/src/navigation/__tests__/chatThreadTree.test.ts +89 -0
- package/apps/mobile/src/navigation/__tests__/drawerChats.test.ts +65 -0
- package/apps/mobile/src/navigation/chatThreadTree.ts +191 -0
- package/apps/mobile/src/navigation/drawerChats.ts +9 -0
- package/apps/mobile/src/screens/GitScreen.tsx +2 -0
- package/apps/mobile/src/screens/MainScreen.tsx +4244 -1237
- package/apps/mobile/src/screens/OnboardingScreen.tsx +2 -0
- package/apps/mobile/src/screens/SettingsScreen.tsx +256 -226
- package/apps/mobile/src/screens/TerminalScreen.tsx +2 -5
- package/apps/mobile/src/screens/__tests__/agentThreadDisplay.test.ts +80 -0
- package/apps/mobile/src/screens/__tests__/agentThreads.test.ts +170 -0
- package/apps/mobile/src/screens/__tests__/planCardState.test.ts +88 -0
- package/apps/mobile/src/screens/__tests__/subAgentTranscript.test.ts +102 -0
- package/apps/mobile/src/screens/__tests__/transcriptMessages.test.ts +97 -0
- package/apps/mobile/src/screens/agentThreadDisplay.ts +261 -0
- package/apps/mobile/src/screens/agentThreads.ts +167 -0
- package/apps/mobile/src/screens/planCardState.ts +40 -0
- package/apps/mobile/src/screens/subAgentTranscript.ts +149 -0
- package/apps/mobile/src/screens/transcriptMessages.ts +102 -0
- package/apps/mobile/src/theme.ts +6 -12
- package/docs/codex-app-server-cli-gap-tracker.md +14 -5
- package/docs/privacy-policy.md +54 -0
- package/docs/setup-and-operations.md +4 -3
- package/docs/terms-of-service.md +33 -0
- package/package.json +3 -3
- package/services/mac-bridge/package.json +6 -6
- package/services/rust-bridge/Cargo.lock +56 -47
- package/services/rust-bridge/Cargo.toml +1 -1
- package/services/rust-bridge/package.json +1 -1
- package/services/rust-bridge/src/main.rs +507 -9
- package/site/index.html +54 -0
- package/site/privacy/index.html +80 -0
- package/site/styles.css +135 -0
- package/site/support/index.html +51 -0
- package/site/terms/index.html +68 -0
|
@@ -419,6 +419,7 @@ export function OnboardingScreen({
|
|
|
419
419
|
setFormError(null);
|
|
420
420
|
setConnectionCheck({ kind: 'idle' });
|
|
421
421
|
}}
|
|
422
|
+
keyboardAppearance="dark"
|
|
422
423
|
autoCapitalize="none"
|
|
423
424
|
autoCorrect={false}
|
|
424
425
|
keyboardType="url"
|
|
@@ -441,6 +442,7 @@ export function OnboardingScreen({
|
|
|
441
442
|
setTokenInput(value);
|
|
442
443
|
setConnectionCheck({ kind: 'idle' });
|
|
443
444
|
}}
|
|
445
|
+
keyboardAppearance="dark"
|
|
444
446
|
autoCapitalize="none"
|
|
445
447
|
autoCorrect={false}
|
|
446
448
|
keyboardType="default"
|
|
@@ -4,18 +4,25 @@ import { LinearGradient } from 'expo-linear-gradient';
|
|
|
4
4
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
5
5
|
import {
|
|
6
6
|
ActivityIndicator,
|
|
7
|
-
Modal,
|
|
8
7
|
Pressable,
|
|
9
8
|
ScrollView,
|
|
10
9
|
StyleSheet,
|
|
10
|
+
Switch,
|
|
11
11
|
Text,
|
|
12
12
|
View,
|
|
13
13
|
} from 'react-native';
|
|
14
14
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
15
15
|
|
|
16
16
|
import type { HostBridgeApiClient } from '../api/client';
|
|
17
|
-
import type {
|
|
17
|
+
import type {
|
|
18
|
+
AccountSnapshot,
|
|
19
|
+
ApprovalMode,
|
|
20
|
+
ModelOption,
|
|
21
|
+
PlanType,
|
|
22
|
+
ReasoningEffort,
|
|
23
|
+
} from '../api/types';
|
|
18
24
|
import type { HostBridgeWsClient } from '../api/ws';
|
|
25
|
+
import { SelectionSheet, type SelectionSheetOption } from '../components/SelectionSheet';
|
|
19
26
|
import { colors, radius, spacing, typography } from '../theme';
|
|
20
27
|
|
|
21
28
|
interface SettingsScreenProps {
|
|
@@ -25,11 +32,13 @@ interface SettingsScreenProps {
|
|
|
25
32
|
defaultModelId?: string | null;
|
|
26
33
|
defaultReasoningEffort?: ReasoningEffort | null;
|
|
27
34
|
approvalMode?: ApprovalMode;
|
|
35
|
+
showToolCalls?: boolean;
|
|
28
36
|
onDefaultModelSettingsChange?: (
|
|
29
37
|
modelId: string | null,
|
|
30
38
|
effort: ReasoningEffort | null
|
|
31
39
|
) => void;
|
|
32
40
|
onApprovalModeChange?: (mode: ApprovalMode) => void;
|
|
41
|
+
onShowToolCallsChange?: (value: boolean) => void;
|
|
33
42
|
onEditBridgeUrl?: () => void;
|
|
34
43
|
onResetOnboarding?: () => void;
|
|
35
44
|
onOpenDrawer: () => void;
|
|
@@ -44,8 +53,10 @@ export function SettingsScreen({
|
|
|
44
53
|
defaultModelId,
|
|
45
54
|
defaultReasoningEffort,
|
|
46
55
|
approvalMode,
|
|
56
|
+
showToolCalls = false,
|
|
47
57
|
onDefaultModelSettingsChange,
|
|
48
58
|
onApprovalModeChange,
|
|
59
|
+
onShowToolCallsChange,
|
|
49
60
|
onEditBridgeUrl,
|
|
50
61
|
onResetOnboarding,
|
|
51
62
|
onOpenDrawer,
|
|
@@ -61,6 +72,9 @@ export function SettingsScreen({
|
|
|
61
72
|
const [modelModalVisible, setModelModalVisible] = useState(false);
|
|
62
73
|
const [effortModalVisible, setEffortModalVisible] = useState(false);
|
|
63
74
|
const [approvalModeModalVisible, setApprovalModeModalVisible] = useState(false);
|
|
75
|
+
const [account, setAccount] = useState<AccountSnapshot | null>(null);
|
|
76
|
+
const [accountLoading, setAccountLoading] = useState(false);
|
|
77
|
+
const [accountError, setAccountError] = useState<string | null>(null);
|
|
64
78
|
|
|
65
79
|
const normalizedDefaultModelId = normalizeModelId(defaultModelId);
|
|
66
80
|
const normalizedDefaultEffort = normalizeReasoningEffort(defaultReasoningEffort);
|
|
@@ -115,15 +129,48 @@ export function SettingsScreen({
|
|
|
115
129
|
}
|
|
116
130
|
}, [api]);
|
|
117
131
|
|
|
132
|
+
const loadAccount = useCallback(async () => {
|
|
133
|
+
setAccountLoading(true);
|
|
134
|
+
try {
|
|
135
|
+
const snapshot = await api.readAccount();
|
|
136
|
+
setAccount(snapshot);
|
|
137
|
+
setAccountError(null);
|
|
138
|
+
} catch (err) {
|
|
139
|
+
setAccountError((err as Error).message);
|
|
140
|
+
} finally {
|
|
141
|
+
setAccountLoading(false);
|
|
142
|
+
}
|
|
143
|
+
}, [api]);
|
|
144
|
+
|
|
118
145
|
useEffect(() => {
|
|
119
146
|
const t = setTimeout(() => {
|
|
120
147
|
void checkHealth();
|
|
121
148
|
void refreshModelOptions();
|
|
149
|
+
void loadAccount();
|
|
122
150
|
}, 0);
|
|
123
151
|
return () => clearTimeout(t);
|
|
124
|
-
}, [checkHealth, refreshModelOptions]);
|
|
152
|
+
}, [checkHealth, loadAccount, refreshModelOptions]);
|
|
125
153
|
|
|
126
|
-
useEffect(
|
|
154
|
+
useEffect(
|
|
155
|
+
() =>
|
|
156
|
+
ws.onStatus((connected) => {
|
|
157
|
+
setWsConnected(connected);
|
|
158
|
+
if (connected) {
|
|
159
|
+
void loadAccount();
|
|
160
|
+
}
|
|
161
|
+
}),
|
|
162
|
+
[loadAccount, ws]
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
useEffect(
|
|
166
|
+
() =>
|
|
167
|
+
ws.onEvent((event) => {
|
|
168
|
+
if (event.method === 'account/updated') {
|
|
169
|
+
void loadAccount();
|
|
170
|
+
}
|
|
171
|
+
}),
|
|
172
|
+
[loadAccount, ws]
|
|
173
|
+
);
|
|
127
174
|
|
|
128
175
|
const openModelModal = useCallback(() => {
|
|
129
176
|
setModelModalVisible(true);
|
|
@@ -223,6 +270,88 @@ export function SettingsScreen({
|
|
|
223
270
|
[onApprovalModeChange]
|
|
224
271
|
);
|
|
225
272
|
|
|
273
|
+
const approvalModeOptions = useMemo<SelectionSheetOption[]>(
|
|
274
|
+
() => [
|
|
275
|
+
{
|
|
276
|
+
key: 'normal',
|
|
277
|
+
title: 'Normal approvals',
|
|
278
|
+
description: 'Ask before commands and file-changing actions run.',
|
|
279
|
+
icon: 'shield-checkmark-outline',
|
|
280
|
+
selected: normalizedApprovalMode === 'normal',
|
|
281
|
+
onPress: () => selectApprovalMode('normal'),
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
key: 'yolo',
|
|
285
|
+
title: 'YOLO approvals',
|
|
286
|
+
description: 'Run commands without prompting for approval.',
|
|
287
|
+
icon: 'flash-outline',
|
|
288
|
+
meta: 'Unsafe',
|
|
289
|
+
selected: normalizedApprovalMode === 'yolo',
|
|
290
|
+
onPress: () => selectApprovalMode('yolo'),
|
|
291
|
+
},
|
|
292
|
+
],
|
|
293
|
+
[normalizedApprovalMode, selectApprovalMode]
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
const modelPickerOptions = useMemo<SelectionSheetOption[]>(
|
|
297
|
+
() => [
|
|
298
|
+
{
|
|
299
|
+
key: 'server-default',
|
|
300
|
+
title: 'Use server default',
|
|
301
|
+
description: 'Follow the bridge default model for new chats.',
|
|
302
|
+
icon: 'sparkles-outline',
|
|
303
|
+
badge: 'Auto',
|
|
304
|
+
selected: normalizedDefaultModelId === null,
|
|
305
|
+
onPress: () => selectDefaultModel(null),
|
|
306
|
+
},
|
|
307
|
+
...modelOptions.map((model) => ({
|
|
308
|
+
key: model.id,
|
|
309
|
+
title: model.displayName,
|
|
310
|
+
description: model.description?.trim() || model.id,
|
|
311
|
+
icon: 'hardware-chip-outline' as const,
|
|
312
|
+
badge: model.isDefault ? 'Default' : undefined,
|
|
313
|
+
meta: model.defaultReasoningEffort
|
|
314
|
+
? formatReasoningEffort(model.defaultReasoningEffort)
|
|
315
|
+
: undefined,
|
|
316
|
+
selected: model.id === normalizedDefaultModelId,
|
|
317
|
+
onPress: () => selectDefaultModel(model.id),
|
|
318
|
+
})),
|
|
319
|
+
],
|
|
320
|
+
[modelOptions, normalizedDefaultModelId, selectDefaultModel]
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
const effortPickerOptions = useMemo<SelectionSheetOption[]>(
|
|
324
|
+
() => [
|
|
325
|
+
{
|
|
326
|
+
key: 'model-default',
|
|
327
|
+
title: 'Use model default',
|
|
328
|
+
description: selectedDefaultModel
|
|
329
|
+
? `Follow ${selectedDefaultModel.displayName}'s default reasoning.`
|
|
330
|
+
: 'Follow the model default reasoning level.',
|
|
331
|
+
icon: 'sparkles-outline',
|
|
332
|
+
badge: 'Auto',
|
|
333
|
+
selected: normalizedDefaultEffort === null,
|
|
334
|
+
onPress: () => selectDefaultEffort(null),
|
|
335
|
+
},
|
|
336
|
+
...selectedDefaultModelEfforts.map((option) => ({
|
|
337
|
+
key: option.effort,
|
|
338
|
+
title: formatReasoningEffort(option.effort),
|
|
339
|
+
description:
|
|
340
|
+
option.description?.trim() ||
|
|
341
|
+
'Override the default reasoning depth for new chats.',
|
|
342
|
+
icon: 'pulse-outline' as const,
|
|
343
|
+
selected: option.effort === normalizedDefaultEffort,
|
|
344
|
+
onPress: () => selectDefaultEffort(option.effort),
|
|
345
|
+
})),
|
|
346
|
+
],
|
|
347
|
+
[
|
|
348
|
+
normalizedDefaultEffort,
|
|
349
|
+
selectDefaultEffort,
|
|
350
|
+
selectedDefaultModel,
|
|
351
|
+
selectedDefaultModelEfforts,
|
|
352
|
+
]
|
|
353
|
+
);
|
|
354
|
+
|
|
226
355
|
return (
|
|
227
356
|
<View style={styles.container}>
|
|
228
357
|
<LinearGradient
|
|
@@ -297,9 +426,68 @@ export function SettingsScreen({
|
|
|
297
426
|
</BlurView>
|
|
298
427
|
<Text style={styles.subtleHintText}>
|
|
299
428
|
This controls command/file-change approvals only. It does not affect
|
|
300
|
-
request_user_input questions.
|
|
429
|
+
request_user_input questions. Mobile chats request full Codex sandbox
|
|
430
|
+
access by default.
|
|
301
431
|
</Text>
|
|
302
432
|
|
|
433
|
+
<Text style={[styles.sectionLabel, styles.sectionLabelGap]}>Transcript</Text>
|
|
434
|
+
<BlurView intensity={50} tint="dark" style={styles.card}>
|
|
435
|
+
<View style={[styles.settingRow, styles.settingRowLast]}>
|
|
436
|
+
<View style={styles.settingRowLeft}>
|
|
437
|
+
<Text style={styles.rowLabel}>Show tool calls</Text>
|
|
438
|
+
<Text style={styles.settingValue} numberOfLines={2}>
|
|
439
|
+
Show web searches, MCP/OpenAI docs calls, commands, and file changes.
|
|
440
|
+
</Text>
|
|
441
|
+
</View>
|
|
442
|
+
<Switch
|
|
443
|
+
value={showToolCalls}
|
|
444
|
+
onValueChange={(value) => onShowToolCallsChange?.(value)}
|
|
445
|
+
trackColor={{ false: colors.borderLight, true: colors.accent }}
|
|
446
|
+
thumbColor={colors.textPrimary}
|
|
447
|
+
ios_backgroundColor={colors.borderLight}
|
|
448
|
+
/>
|
|
449
|
+
</View>
|
|
450
|
+
</BlurView>
|
|
451
|
+
<Text style={styles.subtleHintText}>
|
|
452
|
+
Live tool activity stays in a capped panel so the chat list does not
|
|
453
|
+
start jumping while a turn is running.
|
|
454
|
+
</Text>
|
|
455
|
+
|
|
456
|
+
<Text style={[styles.sectionLabel, styles.sectionLabelGap]}>Account & Auth</Text>
|
|
457
|
+
<BlurView intensity={50} tint="dark" style={styles.card}>
|
|
458
|
+
{accountLoading ? (
|
|
459
|
+
<View style={styles.accountLoadingState}>
|
|
460
|
+
<ActivityIndicator color={colors.textPrimary} />
|
|
461
|
+
<Text style={styles.settingValue}>Loading account details…</Text>
|
|
462
|
+
</View>
|
|
463
|
+
) : (
|
|
464
|
+
<>
|
|
465
|
+
<Row
|
|
466
|
+
label="Status"
|
|
467
|
+
value={formatAccountType(account)}
|
|
468
|
+
valueColor={account?.type ? colors.statusComplete : colors.textMuted}
|
|
469
|
+
/>
|
|
470
|
+
{account?.email ? (
|
|
471
|
+
<Row label="Email" value={account.email} />
|
|
472
|
+
) : null}
|
|
473
|
+
{account?.planType ? (
|
|
474
|
+
<Row label="Plan" value={formatPlanType(account.planType)} />
|
|
475
|
+
) : null}
|
|
476
|
+
<Row
|
|
477
|
+
label="Bridge auth"
|
|
478
|
+
value={account?.requiresOpenaiAuth ? 'Required' : 'Optional'}
|
|
479
|
+
isLast
|
|
480
|
+
/>
|
|
481
|
+
</>
|
|
482
|
+
)}
|
|
483
|
+
</BlurView>
|
|
484
|
+
{account?.type === null && account?.requiresOpenaiAuth ? (
|
|
485
|
+
<Text style={styles.subtleHintText}>
|
|
486
|
+
The bridge expects OpenAI auth, but mobile does not expose a login flow yet.
|
|
487
|
+
</Text>
|
|
488
|
+
) : null}
|
|
489
|
+
{accountError ? <Text style={styles.errorText}>{accountError}</Text> : null}
|
|
490
|
+
|
|
303
491
|
<Text style={[styles.sectionLabel, styles.sectionLabelGap]}>Bridge</Text>
|
|
304
492
|
<BlurView intensity={50} tint="dark" style={styles.card}>
|
|
305
493
|
<Text selectable style={styles.valueText}>
|
|
@@ -348,11 +536,12 @@ export function SettingsScreen({
|
|
|
348
536
|
onPress={() => {
|
|
349
537
|
void checkHealth();
|
|
350
538
|
void refreshModelOptions();
|
|
539
|
+
void loadAccount();
|
|
351
540
|
}}
|
|
352
541
|
style={({ pressed }) => [styles.refreshBtn, pressed && styles.refreshBtnPressed]}
|
|
353
542
|
>
|
|
354
543
|
<Ionicons name="refresh" size={16} color={colors.white} />
|
|
355
|
-
<Text style={styles.refreshBtnText}>Refresh
|
|
544
|
+
<Text style={styles.refreshBtnText}>Refresh settings</Text>
|
|
356
545
|
</Pressable>
|
|
357
546
|
|
|
358
547
|
<Text style={[styles.sectionLabel, styles.sectionLabelGap]}>Legal</Text>
|
|
@@ -383,127 +572,40 @@ export function SettingsScreen({
|
|
|
383
572
|
</ScrollView>
|
|
384
573
|
</SafeAreaView>
|
|
385
574
|
|
|
386
|
-
<
|
|
575
|
+
<SelectionSheet
|
|
387
576
|
visible={approvalModeModalVisible}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
<Text style={styles.modalTitle}>Execution approval mode</Text>
|
|
395
|
-
<ScrollView style={styles.modalList} contentContainerStyle={styles.modalListContent}>
|
|
396
|
-
<OptionRow
|
|
397
|
-
label="Normal — Ask for approvals"
|
|
398
|
-
selected={normalizedApprovalMode === 'normal'}
|
|
399
|
-
onPress={() => selectApprovalMode('normal')}
|
|
400
|
-
/>
|
|
401
|
-
<OptionRow
|
|
402
|
-
label="YOLO — Do not ask approvals"
|
|
403
|
-
selected={normalizedApprovalMode === 'yolo'}
|
|
404
|
-
onPress={() => selectApprovalMode('yolo')}
|
|
405
|
-
/>
|
|
406
|
-
</ScrollView>
|
|
407
|
-
<View style={styles.modalActions}>
|
|
408
|
-
<Pressable
|
|
409
|
-
onPress={() => setApprovalModeModalVisible(false)}
|
|
410
|
-
style={({ pressed }) => [
|
|
411
|
-
styles.modalCloseBtn,
|
|
412
|
-
pressed && styles.workspaceModalCloseBtnPressed,
|
|
413
|
-
]}
|
|
414
|
-
>
|
|
415
|
-
<Text style={styles.modalCloseText}>Close</Text>
|
|
416
|
-
</Pressable>
|
|
417
|
-
</View>
|
|
418
|
-
</View>
|
|
419
|
-
</View>
|
|
420
|
-
</Modal>
|
|
577
|
+
eyebrow="Approvals"
|
|
578
|
+
title="Execution approval mode"
|
|
579
|
+
subtitle="This only affects command and file-change approvals."
|
|
580
|
+
options={approvalModeOptions}
|
|
581
|
+
onClose={() => setApprovalModeModalVisible(false)}
|
|
582
|
+
/>
|
|
421
583
|
|
|
422
|
-
<
|
|
584
|
+
<SelectionSheet
|
|
423
585
|
visible={modelModalVisible}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
) : (
|
|
434
|
-
<ScrollView style={styles.modalList} contentContainerStyle={styles.modalListContent}>
|
|
435
|
-
<OptionRow
|
|
436
|
-
label="Server default"
|
|
437
|
-
selected={normalizedDefaultModelId === null}
|
|
438
|
-
onPress={() => selectDefaultModel(null)}
|
|
439
|
-
/>
|
|
440
|
-
{modelOptions.map((model) => (
|
|
441
|
-
<OptionRow
|
|
442
|
-
key={model.id}
|
|
443
|
-
label={`${model.displayName} (${model.id})`}
|
|
444
|
-
selected={model.id === normalizedDefaultModelId}
|
|
445
|
-
onPress={() => selectDefaultModel(model.id)}
|
|
446
|
-
/>
|
|
447
|
-
))}
|
|
448
|
-
</ScrollView>
|
|
449
|
-
)}
|
|
450
|
-
<View style={styles.modalActions}>
|
|
451
|
-
<Pressable
|
|
452
|
-
onPress={closeModelModal}
|
|
453
|
-
style={({ pressed }) => [
|
|
454
|
-
styles.modalCloseBtn,
|
|
455
|
-
pressed && styles.workspaceModalCloseBtnPressed,
|
|
456
|
-
]}
|
|
457
|
-
>
|
|
458
|
-
<Text style={styles.modalCloseText}>Close</Text>
|
|
459
|
-
</Pressable>
|
|
460
|
-
</View>
|
|
461
|
-
</View>
|
|
462
|
-
</View>
|
|
463
|
-
</Modal>
|
|
586
|
+
eyebrow="Defaults"
|
|
587
|
+
title="Default model"
|
|
588
|
+
subtitle="Pick the model new chats should start with."
|
|
589
|
+
options={modelPickerOptions}
|
|
590
|
+
loading={loadingModels}
|
|
591
|
+
loadingLabel="Refreshing available models…"
|
|
592
|
+
presentation="expanded"
|
|
593
|
+
onClose={closeModelModal}
|
|
594
|
+
/>
|
|
464
595
|
|
|
465
|
-
<
|
|
596
|
+
<SelectionSheet
|
|
466
597
|
visible={effortModalVisible}
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
onPress={() => selectDefaultEffort(null)}
|
|
479
|
-
/>
|
|
480
|
-
{selectedDefaultModelEfforts.map((option) => (
|
|
481
|
-
<OptionRow
|
|
482
|
-
key={option.effort}
|
|
483
|
-
label={
|
|
484
|
-
option.description
|
|
485
|
-
? `${formatReasoningEffort(option.effort)} — ${option.description}`
|
|
486
|
-
: formatReasoningEffort(option.effort)
|
|
487
|
-
}
|
|
488
|
-
selected={option.effort === normalizedDefaultEffort}
|
|
489
|
-
onPress={() => selectDefaultEffort(option.effort)}
|
|
490
|
-
/>
|
|
491
|
-
))}
|
|
492
|
-
</ScrollView>
|
|
493
|
-
<View style={styles.modalActions}>
|
|
494
|
-
<Pressable
|
|
495
|
-
onPress={() => setEffortModalVisible(false)}
|
|
496
|
-
style={({ pressed }) => [
|
|
497
|
-
styles.modalCloseBtn,
|
|
498
|
-
pressed && styles.workspaceModalCloseBtnPressed,
|
|
499
|
-
]}
|
|
500
|
-
>
|
|
501
|
-
<Text style={styles.modalCloseText}>Close</Text>
|
|
502
|
-
</Pressable>
|
|
503
|
-
</View>
|
|
504
|
-
</View>
|
|
505
|
-
</View>
|
|
506
|
-
</Modal>
|
|
598
|
+
eyebrow="Defaults"
|
|
599
|
+
title="Default reasoning"
|
|
600
|
+
subtitle={
|
|
601
|
+
selectedDefaultModel
|
|
602
|
+
? `Current model: ${selectedDefaultModel.displayName}`
|
|
603
|
+
: 'Choose the default reasoning depth for new chats.'
|
|
604
|
+
}
|
|
605
|
+
options={effortPickerOptions}
|
|
606
|
+
presentation="expanded"
|
|
607
|
+
onClose={() => setEffortModalVisible(false)}
|
|
608
|
+
/>
|
|
507
609
|
</View>
|
|
508
610
|
);
|
|
509
611
|
}
|
|
@@ -529,30 +631,6 @@ function Row({
|
|
|
529
631
|
);
|
|
530
632
|
}
|
|
531
633
|
|
|
532
|
-
function OptionRow({
|
|
533
|
-
label,
|
|
534
|
-
selected,
|
|
535
|
-
onPress,
|
|
536
|
-
}: {
|
|
537
|
-
label: string;
|
|
538
|
-
selected: boolean;
|
|
539
|
-
onPress: () => void;
|
|
540
|
-
}) {
|
|
541
|
-
return (
|
|
542
|
-
<Pressable
|
|
543
|
-
onPress={onPress}
|
|
544
|
-
style={({ pressed }) => [
|
|
545
|
-
styles.optionRow,
|
|
546
|
-
selected && styles.optionRowSelected,
|
|
547
|
-
pressed && styles.optionRowPressed,
|
|
548
|
-
]}
|
|
549
|
-
>
|
|
550
|
-
<Text style={[styles.optionRowText, selected && styles.optionRowTextSelected]}>{label}</Text>
|
|
551
|
-
{selected ? <Ionicons name="checkmark" size={16} color={colors.textPrimary} /> : null}
|
|
552
|
-
</Pressable>
|
|
553
|
-
);
|
|
554
|
-
}
|
|
555
|
-
|
|
556
634
|
const styles = StyleSheet.create({
|
|
557
635
|
container: { flex: 1, backgroundColor: colors.bgMain },
|
|
558
636
|
safeArea: { flex: 1 },
|
|
@@ -674,6 +752,12 @@ const styles = StyleSheet.create({
|
|
|
674
752
|
marginTop: spacing.xs,
|
|
675
753
|
marginHorizontal: spacing.xs,
|
|
676
754
|
},
|
|
755
|
+
accountLoadingState: {
|
|
756
|
+
minHeight: 88,
|
|
757
|
+
alignItems: 'center',
|
|
758
|
+
justifyContent: 'center',
|
|
759
|
+
gap: spacing.sm,
|
|
760
|
+
},
|
|
677
761
|
refreshBtn: {
|
|
678
762
|
flexDirection: 'row',
|
|
679
763
|
alignItems: 'center',
|
|
@@ -684,11 +768,7 @@ const styles = StyleSheet.create({
|
|
|
684
768
|
paddingHorizontal: spacing.md,
|
|
685
769
|
backgroundColor: colors.accent,
|
|
686
770
|
borderRadius: radius.md,
|
|
687
|
-
|
|
688
|
-
shadowOffset: { width: 0, height: 4 },
|
|
689
|
-
shadowOpacity: 0.3,
|
|
690
|
-
shadowRadius: 8,
|
|
691
|
-
elevation: 4,
|
|
771
|
+
boxShadow: `0px 4px 8px ${colors.accent}4D`,
|
|
692
772
|
},
|
|
693
773
|
refreshBtnPressed: { backgroundColor: colors.accentPressed },
|
|
694
774
|
refreshBtnText: { ...typography.headline, color: colors.white, fontSize: 15 },
|
|
@@ -711,81 +791,6 @@ const styles = StyleSheet.create({
|
|
|
711
791
|
color: colors.textPrimary,
|
|
712
792
|
fontWeight: '600',
|
|
713
793
|
},
|
|
714
|
-
modalBackdrop: {
|
|
715
|
-
flex: 1,
|
|
716
|
-
backgroundColor: 'rgba(0, 0, 0, 0.55)',
|
|
717
|
-
justifyContent: 'center',
|
|
718
|
-
paddingHorizontal: spacing.lg,
|
|
719
|
-
},
|
|
720
|
-
modalCard: {
|
|
721
|
-
backgroundColor: colors.bgItem,
|
|
722
|
-
borderRadius: 14,
|
|
723
|
-
borderWidth: 1,
|
|
724
|
-
borderColor: colors.border,
|
|
725
|
-
padding: spacing.lg,
|
|
726
|
-
gap: spacing.md,
|
|
727
|
-
maxHeight: '74%',
|
|
728
|
-
},
|
|
729
|
-
modalTitle: {
|
|
730
|
-
...typography.headline,
|
|
731
|
-
color: colors.textPrimary,
|
|
732
|
-
},
|
|
733
|
-
modalLoader: {
|
|
734
|
-
marginVertical: spacing.lg,
|
|
735
|
-
},
|
|
736
|
-
modalList: {
|
|
737
|
-
maxHeight: 320,
|
|
738
|
-
},
|
|
739
|
-
modalListContent: {
|
|
740
|
-
gap: spacing.xs,
|
|
741
|
-
},
|
|
742
|
-
optionRow: {
|
|
743
|
-
flexDirection: 'row',
|
|
744
|
-
alignItems: 'center',
|
|
745
|
-
justifyContent: 'space-between',
|
|
746
|
-
gap: spacing.sm,
|
|
747
|
-
borderRadius: 10,
|
|
748
|
-
borderWidth: StyleSheet.hairlineWidth,
|
|
749
|
-
borderColor: colors.borderLight,
|
|
750
|
-
backgroundColor: colors.bgMain,
|
|
751
|
-
paddingHorizontal: spacing.md,
|
|
752
|
-
paddingVertical: spacing.sm,
|
|
753
|
-
},
|
|
754
|
-
optionRowSelected: {
|
|
755
|
-
borderColor: colors.borderHighlight,
|
|
756
|
-
backgroundColor: 'rgba(255, 255, 255, 0.08)',
|
|
757
|
-
},
|
|
758
|
-
optionRowPressed: {
|
|
759
|
-
opacity: 0.86,
|
|
760
|
-
},
|
|
761
|
-
optionRowText: {
|
|
762
|
-
...typography.caption,
|
|
763
|
-
color: colors.textSecondary,
|
|
764
|
-
flex: 1,
|
|
765
|
-
},
|
|
766
|
-
optionRowTextSelected: {
|
|
767
|
-
color: colors.textPrimary,
|
|
768
|
-
fontWeight: '600',
|
|
769
|
-
},
|
|
770
|
-
modalActions: {
|
|
771
|
-
flexDirection: 'row',
|
|
772
|
-
justifyContent: 'flex-end',
|
|
773
|
-
},
|
|
774
|
-
modalCloseBtn: {
|
|
775
|
-
borderRadius: 10,
|
|
776
|
-
paddingHorizontal: spacing.lg,
|
|
777
|
-
paddingVertical: spacing.sm,
|
|
778
|
-
borderWidth: 1,
|
|
779
|
-
borderColor: colors.border,
|
|
780
|
-
backgroundColor: colors.bgMain,
|
|
781
|
-
},
|
|
782
|
-
workspaceModalCloseBtnPressed: {
|
|
783
|
-
opacity: 0.85,
|
|
784
|
-
},
|
|
785
|
-
modalCloseText: {
|
|
786
|
-
...typography.body,
|
|
787
|
-
color: colors.textPrimary,
|
|
788
|
-
},
|
|
789
794
|
errorText: {
|
|
790
795
|
...typography.caption,
|
|
791
796
|
color: colors.error,
|
|
@@ -838,3 +843,28 @@ function formatReasoningEffort(effort: ReasoningEffort): string {
|
|
|
838
843
|
|
|
839
844
|
return effort.charAt(0).toUpperCase() + effort.slice(1);
|
|
840
845
|
}
|
|
846
|
+
|
|
847
|
+
function formatAccountType(account: AccountSnapshot | null): string {
|
|
848
|
+
if (!account?.type) {
|
|
849
|
+
return 'Signed out';
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
return account.type === 'chatgpt' ? 'ChatGPT' : 'API key';
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
function formatPlanType(planType: PlanType): string {
|
|
856
|
+
if (planType === 'pro') {
|
|
857
|
+
return 'Pro';
|
|
858
|
+
}
|
|
859
|
+
if (planType === 'plus') {
|
|
860
|
+
return 'Plus';
|
|
861
|
+
}
|
|
862
|
+
if (planType === 'go') {
|
|
863
|
+
return 'Go';
|
|
864
|
+
}
|
|
865
|
+
if (planType === 'edu') {
|
|
866
|
+
return 'Edu';
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
return planType.charAt(0).toUpperCase() + planType.slice(1);
|
|
870
|
+
}
|
|
@@ -127,6 +127,7 @@ export function TerminalScreen({ api, ws, onOpenDrawer }: TerminalScreenProps) {
|
|
|
127
127
|
style={styles.input}
|
|
128
128
|
value={command}
|
|
129
129
|
onChangeText={setCommand}
|
|
130
|
+
keyboardAppearance="dark"
|
|
130
131
|
autoCapitalize="none"
|
|
131
132
|
autoCorrect={false}
|
|
132
133
|
returnKeyType="send"
|
|
@@ -171,11 +172,7 @@ const styles = StyleSheet.create({
|
|
|
171
172
|
borderWidth: StyleSheet.hairlineWidth,
|
|
172
173
|
borderColor: 'rgba(255,255,255,0.1)',
|
|
173
174
|
overflow: 'hidden',
|
|
174
|
-
|
|
175
|
-
shadowOffset: { width: 0, height: 10 },
|
|
176
|
-
shadowOpacity: 0.5,
|
|
177
|
-
shadowRadius: 20,
|
|
178
|
-
elevation: 8,
|
|
175
|
+
boxShadow: '0px 10px 20px rgba(0, 0, 0, 0.5)',
|
|
179
176
|
},
|
|
180
177
|
windowHeader: {
|
|
181
178
|
flexDirection: 'row',
|