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
|
@@ -3,15 +3,11 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
|
3
3
|
import {
|
|
4
4
|
ActivityIndicator,
|
|
5
5
|
AppState,
|
|
6
|
-
Modal,
|
|
7
6
|
Pressable,
|
|
8
7
|
RefreshControl,
|
|
9
8
|
SectionList,
|
|
10
|
-
ScrollView,
|
|
11
|
-
type StyleProp,
|
|
12
9
|
StyleSheet,
|
|
13
10
|
Text,
|
|
14
|
-
type ViewStyle,
|
|
15
11
|
View,
|
|
16
12
|
} from 'react-native';
|
|
17
13
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
@@ -19,6 +15,12 @@ import type { HostBridgeApiClient } from '../api/client';
|
|
|
19
15
|
import type { ChatSummary, RpcNotification } from '../api/types';
|
|
20
16
|
import type { HostBridgeWsClient } from '../api/ws';
|
|
21
17
|
import { BrandMark } from '../components/BrandMark';
|
|
18
|
+
import { filterDrawerChats } from './drawerChats';
|
|
19
|
+
import { describeAgentThreadSource } from '../screens/agentThreads';
|
|
20
|
+
import {
|
|
21
|
+
buildChatWorkspaceSections,
|
|
22
|
+
type ChatWorkspaceSection,
|
|
23
|
+
} from './chatThreadTree';
|
|
22
24
|
import { colors, spacing, typography } from '../theme';
|
|
23
25
|
|
|
24
26
|
type Screen = 'Main' | 'Settings' | 'Privacy' | 'Terms';
|
|
@@ -27,20 +29,11 @@ interface DrawerContentProps {
|
|
|
27
29
|
api: HostBridgeApiClient;
|
|
28
30
|
ws: HostBridgeWsClient;
|
|
29
31
|
selectedChatId: string | null;
|
|
30
|
-
selectedDefaultCwd: string | null;
|
|
31
|
-
onSelectDefaultCwd: (cwd: string | null) => void;
|
|
32
32
|
onSelectChat: (id: string) => void;
|
|
33
33
|
onNewChat: () => void;
|
|
34
34
|
onNavigate: (screen: Screen) => void;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
interface ChatWorkspaceSection {
|
|
38
|
-
key: string;
|
|
39
|
-
title: string;
|
|
40
|
-
subtitle?: string;
|
|
41
|
-
data: ChatSummary[];
|
|
42
|
-
}
|
|
43
|
-
|
|
44
37
|
const RUN_HEARTBEAT_STALE_MS = 20_000;
|
|
45
38
|
const DRAWER_REFRESH_CONNECTED_MS = 10_000;
|
|
46
39
|
const DRAWER_REFRESH_DISCONNECTED_MS = 5_000;
|
|
@@ -65,8 +58,6 @@ export function DrawerContent({
|
|
|
65
58
|
api,
|
|
66
59
|
ws,
|
|
67
60
|
selectedChatId,
|
|
68
|
-
selectedDefaultCwd,
|
|
69
|
-
onSelectDefaultCwd,
|
|
70
61
|
onSelectChat,
|
|
71
62
|
onNewChat,
|
|
72
63
|
onNavigate,
|
|
@@ -74,14 +65,12 @@ export function DrawerContent({
|
|
|
74
65
|
const [chats, setChats] = useState<ChatSummary[]>([]);
|
|
75
66
|
const [loading, setLoading] = useState(true);
|
|
76
67
|
const [refreshing, setRefreshing] = useState(false);
|
|
77
|
-
const [workspacePickerOpen, setWorkspacePickerOpen] = useState(false);
|
|
78
68
|
const [collapsedWorkspaceKeys, setCollapsedWorkspaceKeys] = useState<Set<string>>(new Set());
|
|
79
69
|
const [runHeartbeatAtByThread, setRunHeartbeatAtByThread] = useState<Record<string, number>>({});
|
|
80
70
|
const [wsConnected, setWsConnected] = useState(ws.isConnected);
|
|
81
71
|
const hasAppliedInitialCollapseRef = useRef(false);
|
|
82
72
|
const chatSectionsRef = useRef<ChatWorkspaceSection[]>([]);
|
|
83
|
-
const
|
|
84
|
-
const chatSections = useMemo(() => buildWorkspaceSections(chats), [chats]);
|
|
73
|
+
const chatSections = useMemo(() => buildChatWorkspaceSections(chats), [chats]);
|
|
85
74
|
const visibleChatSections = useMemo(
|
|
86
75
|
() =>
|
|
87
76
|
chatSections.map((section) =>
|
|
@@ -94,8 +83,14 @@ export function DrawerContent({
|
|
|
94
83
|
),
|
|
95
84
|
[chatSections, collapsedWorkspaceKeys]
|
|
96
85
|
);
|
|
97
|
-
const
|
|
98
|
-
|
|
86
|
+
const runningChatCount = useMemo(() => {
|
|
87
|
+
const now = Date.now();
|
|
88
|
+
return chats.reduce((count, chat) => {
|
|
89
|
+
const runningFromHeartbeat =
|
|
90
|
+
(runHeartbeatAtByThread[chat.id] ?? 0) > now - RUN_HEARTBEAT_STALE_MS;
|
|
91
|
+
return count + (chat.status === 'running' || runningFromHeartbeat ? 1 : 0);
|
|
92
|
+
}, 0);
|
|
93
|
+
}, [chats, runHeartbeatAtByThread]);
|
|
99
94
|
|
|
100
95
|
const loadChats = useCallback(async (showRefresh = false) => {
|
|
101
96
|
if (showRefresh) {
|
|
@@ -103,8 +98,26 @@ export function DrawerContent({
|
|
|
103
98
|
}
|
|
104
99
|
|
|
105
100
|
try {
|
|
106
|
-
const
|
|
107
|
-
const
|
|
101
|
+
const listedChats = await api.listChats();
|
|
102
|
+
const listedChatIds = new Set(listedChats.map((chat) => chat.id));
|
|
103
|
+
let loadedChats: ChatSummary[] = [];
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
const loadedIds = await api.listLoadedChatIds();
|
|
107
|
+
const missingIds = loadedIds.filter((threadId) => !listedChatIds.has(threadId));
|
|
108
|
+
if (missingIds.length > 0) {
|
|
109
|
+
const loadedResults = await Promise.allSettled(
|
|
110
|
+
missingIds.map((threadId) => api.getChatSummary(threadId))
|
|
111
|
+
);
|
|
112
|
+
loadedChats = loadedResults.flatMap((result) =>
|
|
113
|
+
result.status === 'fulfilled' ? [result.value] : []
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
} catch {
|
|
117
|
+
// Keep the drawer usable if loaded-thread hydration fails.
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const dedupedChats = dedupeChatsById(filterDrawerChats([...listedChats, ...loadedChats]));
|
|
108
121
|
setChats(sortChats(dedupedChats));
|
|
109
122
|
const activeChatIds = new Set(dedupedChats.map((chat) => chat.id));
|
|
110
123
|
setRunHeartbeatAtByThread((prev) => {
|
|
@@ -284,56 +297,112 @@ export function DrawerContent({
|
|
|
284
297
|
<View style={styles.container}>
|
|
285
298
|
<SafeAreaView style={styles.safeArea}>
|
|
286
299
|
<View style={styles.mainContent}>
|
|
287
|
-
<View style={styles.
|
|
288
|
-
<
|
|
289
|
-
|
|
290
|
-
|
|
300
|
+
<View style={styles.topDeck}>
|
|
301
|
+
<View style={styles.heroCard}>
|
|
302
|
+
<View style={styles.heroHeaderRow}>
|
|
303
|
+
<View style={styles.brandBadge}>
|
|
304
|
+
<BrandMark size={18} />
|
|
305
|
+
</View>
|
|
306
|
+
<View style={styles.heroCopy}>
|
|
307
|
+
<Text style={styles.heroTitle}>Clawdex</Text>
|
|
308
|
+
<Text style={styles.heroSubtitle} numberOfLines={1}>
|
|
309
|
+
Threads, terminal, and git in a focused mobile control deck.
|
|
310
|
+
</Text>
|
|
311
|
+
</View>
|
|
312
|
+
<View
|
|
313
|
+
style={[
|
|
314
|
+
styles.connectionBadge,
|
|
315
|
+
wsConnected
|
|
316
|
+
? styles.connectionBadgeConnected
|
|
317
|
+
: styles.connectionBadgeDisconnected,
|
|
318
|
+
]}
|
|
319
|
+
>
|
|
320
|
+
<View
|
|
321
|
+
style={[
|
|
322
|
+
styles.connectionDot,
|
|
323
|
+
wsConnected
|
|
324
|
+
? styles.connectionDotConnected
|
|
325
|
+
: styles.connectionDotDisconnected,
|
|
326
|
+
]}
|
|
327
|
+
/>
|
|
328
|
+
<Text
|
|
329
|
+
style={[
|
|
330
|
+
styles.connectionText,
|
|
331
|
+
wsConnected
|
|
332
|
+
? styles.connectionTextConnected
|
|
333
|
+
: styles.connectionTextDisconnected,
|
|
334
|
+
]}
|
|
335
|
+
>
|
|
336
|
+
{wsConnected ? 'Live' : 'Offline'}
|
|
337
|
+
</Text>
|
|
338
|
+
</View>
|
|
339
|
+
</View>
|
|
291
340
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
styles.
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
341
|
+
<View style={styles.heroStatsRow}>
|
|
342
|
+
<View style={styles.heroStat}>
|
|
343
|
+
<Text style={styles.heroStatValue}>{formatCompactCount(chats.length)}</Text>
|
|
344
|
+
<Text style={styles.heroStatLabel}>Chats</Text>
|
|
345
|
+
</View>
|
|
346
|
+
<View style={styles.heroStatsDivider} />
|
|
347
|
+
<View style={styles.heroStat}>
|
|
348
|
+
<Text style={styles.heroStatValue}>
|
|
349
|
+
{formatCompactCount(runningChatCount)}
|
|
350
|
+
</Text>
|
|
351
|
+
<Text style={styles.heroStatLabel}>Running</Text>
|
|
352
|
+
</View>
|
|
353
|
+
<View style={styles.heroStatsDivider} />
|
|
354
|
+
<View style={styles.heroStat}>
|
|
355
|
+
<Text style={styles.heroStatValue}>
|
|
356
|
+
{formatCompactCount(chatSections.length)}
|
|
357
|
+
</Text>
|
|
358
|
+
<Text style={styles.heroStatLabel}>Spaces</Text>
|
|
359
|
+
</View>
|
|
360
|
+
</View>
|
|
361
|
+
</View>
|
|
306
362
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
</Text>
|
|
320
|
-
<Ionicons name="chevron-down" size={14} color={colors.textMuted} />
|
|
321
|
-
</Pressable>
|
|
363
|
+
<View style={styles.actionRow}>
|
|
364
|
+
<Pressable
|
|
365
|
+
style={({ pressed }) => [
|
|
366
|
+
styles.primaryActionButton,
|
|
367
|
+
pressed && styles.primaryActionButtonPressed,
|
|
368
|
+
]}
|
|
369
|
+
onPress={onNewChat}
|
|
370
|
+
>
|
|
371
|
+
<Ionicons name="add" size={18} color={colors.black} />
|
|
372
|
+
<Text style={styles.primaryActionText}>New chat</Text>
|
|
373
|
+
</Pressable>
|
|
374
|
+
</View>
|
|
322
375
|
</View>
|
|
323
376
|
|
|
324
|
-
{/* Chats section */}
|
|
325
377
|
<View style={styles.sectionHeader}>
|
|
326
378
|
<Text style={styles.sectionTitle}>Chats</Text>
|
|
379
|
+
<View style={styles.sectionCountBadge}>
|
|
380
|
+
<Text style={styles.sectionCountText}>
|
|
381
|
+
{formatCompactCount(chats.length)}
|
|
382
|
+
</Text>
|
|
383
|
+
</View>
|
|
327
384
|
</View>
|
|
328
385
|
|
|
329
386
|
{loading ? (
|
|
330
|
-
<
|
|
387
|
+
<View style={styles.emptyStateCard}>
|
|
388
|
+
<ActivityIndicator color={colors.textMuted} style={styles.loader} />
|
|
389
|
+
<Text style={styles.emptyTitle}>Loading chats</Text>
|
|
390
|
+
<Text style={styles.emptyHint}>Syncing recent threads from your bridge.</Text>
|
|
391
|
+
</View>
|
|
331
392
|
) : chatSections.length === 0 ? (
|
|
332
|
-
<
|
|
393
|
+
<View style={styles.emptyStateCard}>
|
|
394
|
+
<View style={styles.emptyStateIconWrap}>
|
|
395
|
+
<Ionicons name="sparkles-outline" size={18} color={colors.textPrimary} />
|
|
396
|
+
</View>
|
|
397
|
+
<Text style={styles.emptyTitle}>No chats yet</Text>
|
|
398
|
+
<Text style={styles.emptyHint}>
|
|
399
|
+
Start a new chat and it will show up here with live activity.
|
|
400
|
+
</Text>
|
|
401
|
+
</View>
|
|
333
402
|
) : (
|
|
334
403
|
<SectionList
|
|
335
404
|
sections={visibleChatSections}
|
|
336
|
-
keyExtractor={(item) => item.id}
|
|
405
|
+
keyExtractor={(item) => item.chat.id}
|
|
337
406
|
style={styles.list}
|
|
338
407
|
contentContainerStyle={styles.listContent}
|
|
339
408
|
showsVerticalScrollIndicator={false}
|
|
@@ -353,7 +422,9 @@ export function DrawerContent({
|
|
|
353
422
|
<Pressable
|
|
354
423
|
style={({ pressed }) => [
|
|
355
424
|
styles.workspaceGroupHeader,
|
|
356
|
-
collapsed
|
|
425
|
+
collapsed
|
|
426
|
+
? styles.workspaceGroupHeaderCollapsed
|
|
427
|
+
: styles.workspaceGroupHeaderExpanded,
|
|
357
428
|
pressed && styles.workspaceGroupHeaderPressed,
|
|
358
429
|
]}
|
|
359
430
|
onPress={() => toggleWorkspaceSection(section.key)}
|
|
@@ -369,6 +440,11 @@ export function DrawerContent({
|
|
|
369
440
|
</Text>
|
|
370
441
|
) : null}
|
|
371
442
|
</View>
|
|
443
|
+
<View style={styles.workspaceGroupCountBadge}>
|
|
444
|
+
<Text style={styles.workspaceGroupCountText}>
|
|
445
|
+
{formatCompactCount(section.itemCount)}
|
|
446
|
+
</Text>
|
|
447
|
+
</View>
|
|
372
448
|
<View style={styles.workspaceGroupHeaderMeta}>
|
|
373
449
|
<Ionicons
|
|
374
450
|
name={collapsed ? 'chevron-forward' : 'chevron-down'}
|
|
@@ -381,33 +457,99 @@ export function DrawerContent({
|
|
|
381
457
|
);
|
|
382
458
|
}}
|
|
383
459
|
renderItem={({ item, index, section }) => {
|
|
384
|
-
const
|
|
460
|
+
const chat = item.chat;
|
|
461
|
+
const isSelected = chat.id === selectedChatId;
|
|
385
462
|
const isLast = index === section.data.length - 1;
|
|
386
463
|
const isRunningFromHeartbeat =
|
|
387
|
-
(runHeartbeatAtByThread[
|
|
388
|
-
const isRunning =
|
|
464
|
+
(runHeartbeatAtByThread[chat.id] ?? 0) > Date.now() - RUN_HEARTBEAT_STALE_MS;
|
|
465
|
+
const isRunning = chat.status === 'running' || isRunningFromHeartbeat;
|
|
466
|
+
const isSubAgent = item.indentLevel > 0 || Boolean(chat.parentThreadId);
|
|
467
|
+
const previewText = isSubAgent
|
|
468
|
+
? `${describeAgentThreadSource(chat, item.rootThreadId)} • ${formatChatPreview(chat)}`
|
|
469
|
+
: formatChatPreview(chat);
|
|
389
470
|
return (
|
|
390
471
|
<Pressable
|
|
391
472
|
style={({ pressed }) => [
|
|
392
473
|
styles.chatItem,
|
|
393
|
-
|
|
474
|
+
isSubAgent && styles.chatItemSubAgent,
|
|
475
|
+
isSubAgent && { marginLeft: Math.min(item.indentLevel, 4) * 18 },
|
|
394
476
|
isSelected && styles.chatItemSelected,
|
|
395
477
|
pressed && styles.chatItemPressed,
|
|
478
|
+
isLast && styles.chatItemLast,
|
|
396
479
|
]}
|
|
397
|
-
onPress={() => onSelectChat(
|
|
480
|
+
onPress={() => onSelectChat(chat.id)}
|
|
398
481
|
>
|
|
399
|
-
<
|
|
400
|
-
{
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
482
|
+
<View
|
|
483
|
+
style={[
|
|
484
|
+
styles.chatItemAccent,
|
|
485
|
+
isSubAgent && styles.chatItemAccentSubAgent,
|
|
486
|
+
isSelected && styles.chatItemAccentSelected,
|
|
487
|
+
isRunning && styles.chatItemAccentRunning,
|
|
488
|
+
chat.status === 'error' && styles.chatItemAccentError,
|
|
489
|
+
]}
|
|
490
|
+
/>
|
|
491
|
+
<View style={styles.chatItemContent}>
|
|
492
|
+
<View style={styles.chatItemTopRow}>
|
|
493
|
+
{isSubAgent ? (
|
|
494
|
+
<Ionicons
|
|
495
|
+
name="git-branch-outline"
|
|
496
|
+
size={12}
|
|
497
|
+
color="#F5A524"
|
|
498
|
+
style={styles.chatSubAgentIcon}
|
|
499
|
+
/>
|
|
500
|
+
) : null}
|
|
501
|
+
<Text
|
|
502
|
+
style={[
|
|
503
|
+
styles.chatTitle,
|
|
504
|
+
isSubAgent && styles.chatTitleSubAgent,
|
|
505
|
+
isSelected && styles.chatTitleSelected,
|
|
506
|
+
]}
|
|
507
|
+
numberOfLines={1}
|
|
508
|
+
>
|
|
509
|
+
{chat.title || 'Untitled'}
|
|
510
|
+
</Text>
|
|
511
|
+
<Text
|
|
512
|
+
style={[styles.chatAge, isSelected && styles.chatAgeSelected]}
|
|
513
|
+
>
|
|
514
|
+
{relativeTime(chat.updatedAt)}
|
|
515
|
+
</Text>
|
|
516
|
+
</View>
|
|
517
|
+
<View style={styles.chatItemBottomRow}>
|
|
518
|
+
<Text
|
|
519
|
+
style={[
|
|
520
|
+
styles.chatPreview,
|
|
521
|
+
isSubAgent && styles.chatPreviewSubAgent,
|
|
522
|
+
isSelected && styles.chatPreviewSelected,
|
|
523
|
+
]}
|
|
524
|
+
numberOfLines={1}
|
|
525
|
+
>
|
|
526
|
+
{previewText}
|
|
527
|
+
</Text>
|
|
528
|
+
{isRunning ? (
|
|
529
|
+
<View style={styles.chatMeta}>
|
|
530
|
+
<View style={[styles.statusPill, styles.statusPillRunning]}>
|
|
531
|
+
<View
|
|
532
|
+
style={[styles.statusPillDot, styles.statusPillDotRunning]}
|
|
533
|
+
/>
|
|
534
|
+
<Text
|
|
535
|
+
style={[styles.statusPillText, styles.statusPillTextRunning]}
|
|
536
|
+
>
|
|
537
|
+
Live
|
|
538
|
+
</Text>
|
|
539
|
+
</View>
|
|
540
|
+
</View>
|
|
541
|
+
) : chat.status === 'error' ? (
|
|
542
|
+
<View style={styles.chatMeta}>
|
|
543
|
+
<View style={[styles.statusPill, styles.statusPillError]}>
|
|
544
|
+
<Text
|
|
545
|
+
style={[styles.statusPillText, styles.statusPillTextError]}
|
|
546
|
+
>
|
|
547
|
+
Error
|
|
548
|
+
</Text>
|
|
549
|
+
</View>
|
|
550
|
+
</View>
|
|
551
|
+
) : null}
|
|
552
|
+
</View>
|
|
411
553
|
</View>
|
|
412
554
|
</Pressable>
|
|
413
555
|
);
|
|
@@ -417,124 +559,24 @@ export function DrawerContent({
|
|
|
417
559
|
</View>
|
|
418
560
|
|
|
419
561
|
<View style={styles.footer}>
|
|
420
|
-
<
|
|
421
|
-
|
|
422
|
-
|
|
562
|
+
<Pressable
|
|
563
|
+
accessibilityLabel="Open settings"
|
|
564
|
+
style={({ pressed }) => [
|
|
565
|
+
styles.footerSettingsButton,
|
|
566
|
+
pressed && styles.footerSettingsButtonPressed,
|
|
567
|
+
]}
|
|
423
568
|
onPress={() => onNavigate('Settings')}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
569
|
+
>
|
|
570
|
+
<Ionicons name="settings-outline" size={16} color={colors.textPrimary} />
|
|
571
|
+
<Text style={styles.footerSettingsText}>Settings</Text>
|
|
572
|
+
</Pressable>
|
|
427
573
|
</View>
|
|
428
|
-
</SafeAreaView>
|
|
429
574
|
|
|
430
|
-
|
|
431
|
-
visible={workspacePickerOpen}
|
|
432
|
-
transparent
|
|
433
|
-
animationType="fade"
|
|
434
|
-
onRequestClose={() => setWorkspacePickerOpen(false)}
|
|
435
|
-
>
|
|
436
|
-
<View style={styles.workspaceModalBackdrop}>
|
|
437
|
-
<View style={styles.workspaceModalCard}>
|
|
438
|
-
<Text style={styles.workspaceModalTitle}>Start directory for new chats</Text>
|
|
439
|
-
<ScrollView
|
|
440
|
-
style={styles.workspaceModalList}
|
|
441
|
-
contentContainerStyle={styles.workspaceModalListContent}
|
|
442
|
-
showsVerticalScrollIndicator={false}
|
|
443
|
-
>
|
|
444
|
-
<WorkspaceOption
|
|
445
|
-
label="Bridge default workspace"
|
|
446
|
-
selected={normalizeCwd(selectedDefaultCwd) === null}
|
|
447
|
-
onPress={() => {
|
|
448
|
-
onSelectDefaultCwd(null);
|
|
449
|
-
setWorkspacePickerOpen(false);
|
|
450
|
-
}}
|
|
451
|
-
/>
|
|
452
|
-
{workspaceOptions.map((cwd) => (
|
|
453
|
-
<WorkspaceOption
|
|
454
|
-
key={cwd}
|
|
455
|
-
label={cwd}
|
|
456
|
-
selected={cwd === normalizeCwd(selectedDefaultCwd)}
|
|
457
|
-
onPress={() => {
|
|
458
|
-
onSelectDefaultCwd(cwd);
|
|
459
|
-
setWorkspacePickerOpen(false);
|
|
460
|
-
}}
|
|
461
|
-
/>
|
|
462
|
-
))}
|
|
463
|
-
</ScrollView>
|
|
464
|
-
<Pressable
|
|
465
|
-
style={({ pressed }) => [
|
|
466
|
-
styles.workspaceModalCloseBtn,
|
|
467
|
-
pressed && styles.workspaceModalCloseBtnPressed,
|
|
468
|
-
]}
|
|
469
|
-
onPress={() => setWorkspacePickerOpen(false)}
|
|
470
|
-
>
|
|
471
|
-
<Text style={styles.workspaceModalCloseText}>Close</Text>
|
|
472
|
-
</Pressable>
|
|
473
|
-
</View>
|
|
474
|
-
</View>
|
|
475
|
-
</Modal>
|
|
476
|
-
</View>
|
|
477
|
-
);
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
function NavItem({
|
|
481
|
-
icon,
|
|
482
|
-
label,
|
|
483
|
-
onPress,
|
|
484
|
-
style,
|
|
485
|
-
pressableStyle,
|
|
486
|
-
}: {
|
|
487
|
-
icon: keyof typeof Ionicons.glyphMap;
|
|
488
|
-
label: string;
|
|
489
|
-
onPress: () => void;
|
|
490
|
-
style?: StyleProp<ViewStyle>;
|
|
491
|
-
pressableStyle?: StyleProp<ViewStyle>;
|
|
492
|
-
}) {
|
|
493
|
-
return (
|
|
494
|
-
<View style={style}>
|
|
495
|
-
<Pressable
|
|
496
|
-
style={({ pressed }) => [
|
|
497
|
-
styles.navItem,
|
|
498
|
-
pressableStyle,
|
|
499
|
-
pressed && styles.navItemPressed,
|
|
500
|
-
]}
|
|
501
|
-
onPress={onPress}
|
|
502
|
-
>
|
|
503
|
-
<Ionicons name={icon} size={18} color={colors.textPrimary} />
|
|
504
|
-
<Text style={styles.navLabel}>{label}</Text>
|
|
505
|
-
</Pressable>
|
|
575
|
+
</SafeAreaView>
|
|
506
576
|
</View>
|
|
507
577
|
);
|
|
508
578
|
}
|
|
509
579
|
|
|
510
|
-
function WorkspaceOption({
|
|
511
|
-
label,
|
|
512
|
-
selected,
|
|
513
|
-
onPress,
|
|
514
|
-
}: {
|
|
515
|
-
label: string;
|
|
516
|
-
selected: boolean;
|
|
517
|
-
onPress: () => void;
|
|
518
|
-
}) {
|
|
519
|
-
return (
|
|
520
|
-
<Pressable
|
|
521
|
-
style={({ pressed }) => [
|
|
522
|
-
styles.workspaceOption,
|
|
523
|
-
selected && styles.workspaceOptionSelected,
|
|
524
|
-
pressed && styles.workspaceOptionPressed,
|
|
525
|
-
]}
|
|
526
|
-
onPress={onPress}
|
|
527
|
-
>
|
|
528
|
-
<Text style={[styles.workspaceOptionText, selected && styles.workspaceOptionTextSelected]} numberOfLines={2}>
|
|
529
|
-
{label}
|
|
530
|
-
</Text>
|
|
531
|
-
{selected ? (
|
|
532
|
-
<Ionicons name="checkmark-circle" size={16} color={colors.textPrimary} />
|
|
533
|
-
) : null}
|
|
534
|
-
</Pressable>
|
|
535
|
-
);
|
|
536
|
-
}
|
|
537
|
-
|
|
538
580
|
function sortChats(chats: ChatSummary[]): ChatSummary[] {
|
|
539
581
|
return [...chats].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
540
582
|
}
|
|
@@ -552,121 +594,53 @@ function dedupeChatsById(chats: ChatSummary[]): ChatSummary[] {
|
|
|
552
594
|
return Array.from(byId.values());
|
|
553
595
|
}
|
|
554
596
|
|
|
555
|
-
function
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
const trimmed = value.trim();
|
|
561
|
-
return trimmed.length > 0 ? trimmed : null;
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
function listWorkspaces(chats: ChatSummary[]): string[] {
|
|
565
|
-
const sorted = [...chats].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
566
|
-
const seen = new Set<string>();
|
|
567
|
-
const result: string[] = [];
|
|
568
|
-
|
|
569
|
-
for (const chat of sorted) {
|
|
570
|
-
const cwd = normalizeCwd(chat.cwd);
|
|
571
|
-
if (!cwd || seen.has(cwd)) {
|
|
572
|
-
continue;
|
|
573
|
-
}
|
|
574
|
-
seen.add(cwd);
|
|
575
|
-
result.push(cwd);
|
|
597
|
+
function getDefaultCollapsedWorkspaceKeys(sections: ChatWorkspaceSection[]): Set<string> {
|
|
598
|
+
const collapsed = new Set<string>();
|
|
599
|
+
for (let i = 1; i < sections.length; i += 1) {
|
|
600
|
+
collapsed.add(sections[i].key);
|
|
576
601
|
}
|
|
577
|
-
|
|
578
|
-
return result;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
const DEFAULT_WORKSPACE_KEY = '__bridge_default_workspace__';
|
|
582
|
-
|
|
583
|
-
function workspaceKey(cwd: string | null): string {
|
|
584
|
-
return cwd ?? DEFAULT_WORKSPACE_KEY;
|
|
602
|
+
return collapsed;
|
|
585
603
|
}
|
|
586
604
|
|
|
587
|
-
function
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
const
|
|
593
|
-
if (!normalized) {
|
|
594
|
-
return cwd;
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
const lastSlash = normalized.lastIndexOf('/');
|
|
598
|
-
if (lastSlash === -1) {
|
|
599
|
-
return normalized;
|
|
600
|
-
}
|
|
605
|
+
function relativeTime(iso: string): string {
|
|
606
|
+
const diff = Math.max(0, Date.now() - new Date(iso).getTime());
|
|
607
|
+
const minutes = Math.floor(diff / 60000);
|
|
608
|
+
const hours = Math.floor(diff / 3600000);
|
|
609
|
+
const days = Math.floor(diff / 86400000);
|
|
610
|
+
const weeks = Math.floor(days / 7);
|
|
601
611
|
|
|
602
|
-
|
|
612
|
+
if (minutes < 1) return 'now';
|
|
613
|
+
if (minutes < 60) return `${minutes}m`;
|
|
614
|
+
if (hours < 24) return `${hours}h`;
|
|
615
|
+
if (days < 7) return `${days}d`;
|
|
616
|
+
if (weeks < 5) return `${weeks}w`;
|
|
617
|
+
return `${Math.floor(days / 30)}mo`;
|
|
603
618
|
}
|
|
604
619
|
|
|
605
|
-
function
|
|
606
|
-
if (
|
|
607
|
-
return
|
|
620
|
+
function formatCompactCount(value: number): string {
|
|
621
|
+
if (value >= 1000) {
|
|
622
|
+
return `${(value / 1000).toFixed(value >= 10000 ? 0 : 1).replace(/\.0$/, '')}k`;
|
|
608
623
|
}
|
|
609
624
|
|
|
610
|
-
return
|
|
625
|
+
return String(value);
|
|
611
626
|
}
|
|
612
627
|
|
|
613
|
-
function
|
|
614
|
-
|
|
615
|
-
|
|
628
|
+
function formatChatPreview(chat: ChatSummary): string {
|
|
629
|
+
const preview = chat.lastMessagePreview.trim();
|
|
630
|
+
if (preview.length > 0) {
|
|
631
|
+
return preview;
|
|
616
632
|
}
|
|
617
633
|
|
|
618
|
-
const
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
{
|
|
622
|
-
cwd: string | null;
|
|
623
|
-
chats: ChatSummary[];
|
|
624
|
-
}
|
|
625
|
-
>();
|
|
626
|
-
|
|
627
|
-
for (const chat of sorted) {
|
|
628
|
-
const cwd = normalizeCwd(chat.cwd);
|
|
629
|
-
const key = workspaceKey(cwd);
|
|
630
|
-
const bucket = byWorkspace.get(key);
|
|
631
|
-
if (bucket) {
|
|
632
|
-
bucket.chats.push(chat);
|
|
633
|
-
continue;
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
byWorkspace.set(key, {
|
|
637
|
-
cwd,
|
|
638
|
-
chats: [chat],
|
|
639
|
-
});
|
|
634
|
+
const errorPreview = chat.lastError?.trim();
|
|
635
|
+
if (errorPreview) {
|
|
636
|
+
return errorPreview;
|
|
640
637
|
}
|
|
641
638
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
const aUpdatedAt = a.chats[0]?.updatedAt ?? '';
|
|
645
|
-
const bUpdatedAt = b.chats[0]?.updatedAt ?? '';
|
|
646
|
-
return bUpdatedAt.localeCompare(aUpdatedAt);
|
|
647
|
-
})
|
|
648
|
-
.map(([key, bucket]) => ({
|
|
649
|
-
key,
|
|
650
|
-
title: workspaceTitle(bucket.cwd),
|
|
651
|
-
subtitle: workspaceSubtitle(bucket.cwd),
|
|
652
|
-
data: bucket.chats,
|
|
653
|
-
}));
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
function getDefaultCollapsedWorkspaceKeys(sections: ChatWorkspaceSection[]): Set<string> {
|
|
657
|
-
const collapsed = new Set<string>();
|
|
658
|
-
for (let i = 1; i < sections.length; i += 1) {
|
|
659
|
-
collapsed.add(sections[i].key);
|
|
639
|
+
if (chat.status === 'running') {
|
|
640
|
+
return 'Run in progress';
|
|
660
641
|
}
|
|
661
|
-
return collapsed;
|
|
662
|
-
}
|
|
663
642
|
|
|
664
|
-
|
|
665
|
-
const diff = Date.now() - new Date(iso).getTime();
|
|
666
|
-
const days = Math.floor(diff / 86400000);
|
|
667
|
-
if (days === 0) return 'today';
|
|
668
|
-
if (days === 1) return '1d';
|
|
669
|
-
return `${days}d`;
|
|
643
|
+
return 'No messages yet';
|
|
670
644
|
}
|
|
671
645
|
|
|
672
646
|
function toRecord(value: unknown): Record<string, unknown> | null {
|
|
@@ -695,7 +669,7 @@ function extractThreadId(event: RpcNotification): string | null {
|
|
|
695
669
|
const styles = StyleSheet.create({
|
|
696
670
|
container: {
|
|
697
671
|
flex: 1,
|
|
698
|
-
backgroundColor: colors.
|
|
672
|
+
backgroundColor: colors.bgMain,
|
|
699
673
|
},
|
|
700
674
|
safeArea: {
|
|
701
675
|
flex: 1,
|
|
@@ -704,175 +678,249 @@ const styles = StyleSheet.create({
|
|
|
704
678
|
flex: 1,
|
|
705
679
|
minHeight: 0,
|
|
706
680
|
},
|
|
707
|
-
|
|
681
|
+
topDeck: {
|
|
682
|
+
paddingHorizontal: spacing.lg,
|
|
683
|
+
paddingTop: spacing.sm,
|
|
684
|
+
paddingBottom: spacing.md,
|
|
685
|
+
gap: spacing.xs + 2,
|
|
686
|
+
},
|
|
687
|
+
heroCard: {
|
|
688
|
+
borderRadius: 16,
|
|
689
|
+
borderWidth: 1,
|
|
690
|
+
borderColor: 'rgba(255, 255, 255, 0.09)',
|
|
691
|
+
backgroundColor: '#090C10',
|
|
692
|
+
padding: spacing.sm + 2,
|
|
693
|
+
gap: spacing.sm,
|
|
694
|
+
},
|
|
695
|
+
heroHeaderRow: {
|
|
708
696
|
flexDirection: 'row',
|
|
697
|
+
alignItems: 'flex-start',
|
|
698
|
+
gap: spacing.xs + 2,
|
|
699
|
+
},
|
|
700
|
+
brandBadge: {
|
|
701
|
+
width: 34,
|
|
702
|
+
height: 34,
|
|
703
|
+
borderRadius: 12,
|
|
709
704
|
alignItems: 'center',
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
705
|
+
justifyContent: 'center',
|
|
706
|
+
backgroundColor: '#14181D',
|
|
707
|
+
borderWidth: 1,
|
|
708
|
+
borderColor: 'rgba(255, 255, 255, 0.08)',
|
|
714
709
|
},
|
|
715
|
-
|
|
710
|
+
heroCopy: {
|
|
711
|
+
flex: 1,
|
|
712
|
+
gap: 2,
|
|
713
|
+
},
|
|
714
|
+
heroTitle: {
|
|
716
715
|
...typography.body,
|
|
717
716
|
color: colors.textPrimary,
|
|
718
|
-
|
|
717
|
+
fontSize: 16,
|
|
718
|
+
fontWeight: '700',
|
|
719
719
|
},
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
workspaceSection: {
|
|
726
|
-
paddingHorizontal: spacing.lg,
|
|
727
|
-
paddingBottom: spacing.md,
|
|
728
|
-
gap: spacing.xs,
|
|
720
|
+
heroSubtitle: {
|
|
721
|
+
...typography.caption,
|
|
722
|
+
color: colors.textMuted,
|
|
723
|
+
fontSize: 11,
|
|
724
|
+
lineHeight: 14,
|
|
729
725
|
},
|
|
730
|
-
|
|
726
|
+
connectionBadge: {
|
|
731
727
|
flexDirection: 'row',
|
|
732
728
|
alignItems: 'center',
|
|
733
|
-
gap:
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
729
|
+
gap: 5,
|
|
730
|
+
borderRadius: 999,
|
|
731
|
+
paddingHorizontal: spacing.sm,
|
|
732
|
+
paddingVertical: spacing.xs,
|
|
733
|
+
borderWidth: 1,
|
|
734
|
+
},
|
|
735
|
+
connectionBadgeConnected: {
|
|
736
|
+
backgroundColor: 'rgba(52, 199, 89, 0.12)',
|
|
737
|
+
borderColor: 'rgba(52, 199, 89, 0.32)',
|
|
738
|
+
},
|
|
739
|
+
connectionBadgeDisconnected: {
|
|
740
|
+
backgroundColor: 'rgba(245, 158, 11, 0.12)',
|
|
741
|
+
borderColor: 'rgba(245, 158, 11, 0.28)',
|
|
740
742
|
},
|
|
741
|
-
|
|
742
|
-
|
|
743
|
+
connectionDot: {
|
|
744
|
+
width: 6,
|
|
745
|
+
height: 6,
|
|
746
|
+
borderRadius: 3,
|
|
743
747
|
},
|
|
744
|
-
|
|
748
|
+
connectionDotConnected: {
|
|
749
|
+
backgroundColor: '#34C759',
|
|
750
|
+
},
|
|
751
|
+
connectionDotDisconnected: {
|
|
752
|
+
backgroundColor: '#F59E0B',
|
|
753
|
+
},
|
|
754
|
+
connectionText: {
|
|
745
755
|
...typography.caption,
|
|
746
|
-
|
|
747
|
-
|
|
756
|
+
fontSize: 10,
|
|
757
|
+
lineHeight: 12,
|
|
758
|
+
fontWeight: '700',
|
|
759
|
+
textTransform: 'uppercase',
|
|
760
|
+
letterSpacing: 0.5,
|
|
761
|
+
},
|
|
762
|
+
connectionTextConnected: {
|
|
763
|
+
color: '#8EE6AD',
|
|
764
|
+
},
|
|
765
|
+
connectionTextDisconnected: {
|
|
766
|
+
color: '#F6C875',
|
|
767
|
+
},
|
|
768
|
+
heroStatsRow: {
|
|
769
|
+
flexDirection: 'row',
|
|
770
|
+
alignItems: 'stretch',
|
|
771
|
+
borderRadius: 14,
|
|
772
|
+
backgroundColor: '#050608',
|
|
773
|
+
borderWidth: 1,
|
|
774
|
+
borderColor: 'rgba(255, 255, 255, 0.06)',
|
|
775
|
+
overflow: 'hidden',
|
|
748
776
|
},
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
backgroundColor: colors.bgItem,
|
|
777
|
+
heroStat: {
|
|
778
|
+
flex: 1,
|
|
779
|
+
alignItems: 'center',
|
|
780
|
+
gap: 2,
|
|
781
|
+
paddingVertical: spacing.sm,
|
|
755
782
|
},
|
|
756
|
-
|
|
783
|
+
heroStatValue: {
|
|
757
784
|
...typography.body,
|
|
758
|
-
fontWeight: '500',
|
|
759
785
|
color: colors.textPrimary,
|
|
786
|
+
fontSize: 15,
|
|
787
|
+
fontWeight: '700',
|
|
788
|
+
fontVariant: ['tabular-nums'],
|
|
760
789
|
},
|
|
761
|
-
|
|
790
|
+
heroStatLabel: {
|
|
791
|
+
...typography.caption,
|
|
792
|
+
color: colors.textMuted,
|
|
793
|
+
fontSize: 10,
|
|
794
|
+
lineHeight: 12,
|
|
795
|
+
textTransform: 'uppercase',
|
|
796
|
+
letterSpacing: 0.7,
|
|
797
|
+
},
|
|
798
|
+
heroStatsDivider: {
|
|
799
|
+
width: StyleSheet.hairlineWidth,
|
|
800
|
+
backgroundColor: 'rgba(255, 255, 255, 0.08)',
|
|
801
|
+
},
|
|
802
|
+
actionRow: {
|
|
803
|
+
flexDirection: 'row',
|
|
804
|
+
gap: spacing.xs + 2,
|
|
805
|
+
},
|
|
806
|
+
primaryActionButton: {
|
|
807
|
+
flex: 1,
|
|
808
|
+
height: 42,
|
|
809
|
+
borderRadius: 14,
|
|
810
|
+
backgroundColor: '#F2F4F8',
|
|
762
811
|
flexDirection: 'row',
|
|
763
812
|
alignItems: 'center',
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
paddingVertical: spacing.md,
|
|
767
|
-
marginHorizontal: spacing.md,
|
|
768
|
-
borderRadius: 10,
|
|
769
|
-
marginBottom: spacing.xs,
|
|
813
|
+
justifyContent: 'center',
|
|
814
|
+
gap: spacing.xs,
|
|
770
815
|
},
|
|
771
|
-
|
|
772
|
-
|
|
816
|
+
primaryActionButtonPressed: {
|
|
817
|
+
opacity: 0.9,
|
|
773
818
|
},
|
|
774
|
-
|
|
819
|
+
primaryActionText: {
|
|
775
820
|
...typography.body,
|
|
776
|
-
|
|
777
|
-
|
|
821
|
+
color: colors.black,
|
|
822
|
+
fontWeight: '700',
|
|
823
|
+
fontSize: 14,
|
|
778
824
|
},
|
|
779
825
|
sectionHeader: {
|
|
826
|
+
flexDirection: 'row',
|
|
827
|
+
alignItems: 'center',
|
|
828
|
+
justifyContent: 'space-between',
|
|
780
829
|
paddingHorizontal: spacing.lg,
|
|
781
|
-
paddingTop: spacing.md,
|
|
782
830
|
paddingBottom: spacing.sm,
|
|
783
831
|
},
|
|
784
832
|
sectionTitle: {
|
|
785
833
|
...typography.caption,
|
|
834
|
+
color: colors.textMuted,
|
|
786
835
|
textTransform: 'uppercase',
|
|
787
|
-
|
|
836
|
+
fontSize: 10,
|
|
837
|
+
lineHeight: 12,
|
|
838
|
+
letterSpacing: 0.9,
|
|
839
|
+
fontWeight: '700',
|
|
840
|
+
},
|
|
841
|
+
sectionCountBadge: {
|
|
842
|
+
minWidth: 24,
|
|
843
|
+
borderRadius: 999,
|
|
844
|
+
borderWidth: 1,
|
|
845
|
+
borderColor: 'rgba(255, 255, 255, 0.08)',
|
|
846
|
+
backgroundColor: '#101317',
|
|
847
|
+
paddingHorizontal: spacing.sm,
|
|
848
|
+
paddingVertical: 3,
|
|
849
|
+
alignItems: 'center',
|
|
850
|
+
justifyContent: 'center',
|
|
851
|
+
},
|
|
852
|
+
sectionCountText: {
|
|
853
|
+
...typography.caption,
|
|
854
|
+
color: colors.textSecondary,
|
|
855
|
+
fontSize: 10,
|
|
856
|
+
lineHeight: 12,
|
|
857
|
+
fontWeight: '700',
|
|
858
|
+
fontVariant: ['tabular-nums'],
|
|
788
859
|
},
|
|
789
860
|
list: {
|
|
790
861
|
flex: 1,
|
|
791
862
|
},
|
|
792
863
|
listContent: {
|
|
793
|
-
paddingBottom: spacing.
|
|
864
|
+
paddingBottom: spacing.lg,
|
|
794
865
|
},
|
|
795
866
|
loader: {
|
|
796
|
-
|
|
797
|
-
},
|
|
798
|
-
emptyText: {
|
|
799
|
-
...typography.caption,
|
|
800
|
-
textAlign: 'center',
|
|
801
|
-
marginTop: spacing.xl,
|
|
867
|
+
marginBottom: spacing.xs,
|
|
802
868
|
},
|
|
803
|
-
|
|
804
|
-
|
|
869
|
+
emptyStateCard: {
|
|
870
|
+
marginHorizontal: spacing.lg,
|
|
871
|
+
marginTop: spacing.sm,
|
|
872
|
+
borderRadius: 16,
|
|
873
|
+
borderWidth: 1,
|
|
874
|
+
borderColor: 'rgba(255, 255, 255, 0.08)',
|
|
875
|
+
backgroundColor: '#0B0D10',
|
|
876
|
+
padding: spacing.md,
|
|
805
877
|
alignItems: 'center',
|
|
806
|
-
|
|
807
|
-
paddingHorizontal: spacing.md,
|
|
808
|
-
paddingVertical: spacing.md,
|
|
809
|
-
marginHorizontal: spacing.md,
|
|
810
|
-
borderLeftWidth: StyleSheet.hairlineWidth,
|
|
811
|
-
borderRightWidth: StyleSheet.hairlineWidth,
|
|
812
|
-
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
813
|
-
borderColor: colors.borderLight,
|
|
814
|
-
backgroundColor: colors.bgItem,
|
|
878
|
+
gap: spacing.xs + 2,
|
|
815
879
|
},
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
backgroundColor: '
|
|
823
|
-
},
|
|
824
|
-
chatItemPressed: {
|
|
825
|
-
opacity: 0.88,
|
|
880
|
+
emptyStateIconWrap: {
|
|
881
|
+
width: 34,
|
|
882
|
+
height: 34,
|
|
883
|
+
borderRadius: 12,
|
|
884
|
+
alignItems: 'center',
|
|
885
|
+
justifyContent: 'center',
|
|
886
|
+
backgroundColor: '#11151A',
|
|
826
887
|
},
|
|
827
|
-
|
|
888
|
+
emptyTitle: {
|
|
828
889
|
...typography.body,
|
|
829
|
-
color: colors.textMuted,
|
|
830
|
-
flex: 1,
|
|
831
|
-
marginRight: spacing.sm,
|
|
832
|
-
},
|
|
833
|
-
chatTitleSelected: {
|
|
834
890
|
color: colors.textPrimary,
|
|
891
|
+
fontSize: 14,
|
|
835
892
|
fontWeight: '600',
|
|
836
893
|
},
|
|
837
|
-
|
|
894
|
+
emptyHint: {
|
|
838
895
|
...typography.caption,
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
alignItems: 'center',
|
|
844
|
-
gap: spacing.xs,
|
|
845
|
-
flexShrink: 0,
|
|
846
|
-
},
|
|
847
|
-
chatSpinner: {
|
|
848
|
-
marginRight: 2,
|
|
896
|
+
color: colors.textMuted,
|
|
897
|
+
fontSize: 11,
|
|
898
|
+
textAlign: 'center',
|
|
899
|
+
lineHeight: 15,
|
|
849
900
|
},
|
|
850
901
|
workspaceGroupHeader: {
|
|
851
|
-
marginHorizontal: spacing.
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
borderWidth:
|
|
856
|
-
borderColor:
|
|
857
|
-
backgroundColor: '#
|
|
902
|
+
marginHorizontal: spacing.lg,
|
|
903
|
+
paddingHorizontal: spacing.sm + 2,
|
|
904
|
+
paddingVertical: spacing.sm,
|
|
905
|
+
borderRadius: 16,
|
|
906
|
+
borderWidth: 1,
|
|
907
|
+
borderColor: 'rgba(255, 255, 255, 0.08)',
|
|
908
|
+
backgroundColor: '#0C0F13',
|
|
858
909
|
},
|
|
859
910
|
workspaceGroupHeaderExpanded: {
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
borderBottomLeftRadius: 0,
|
|
863
|
-
borderBottomRightRadius: 0,
|
|
911
|
+
marginTop: spacing.xs,
|
|
912
|
+
marginBottom: spacing.xs,
|
|
864
913
|
},
|
|
865
914
|
workspaceGroupHeaderCollapsed: {
|
|
866
|
-
|
|
867
|
-
marginBottom: spacing.
|
|
915
|
+
marginTop: spacing.xs,
|
|
916
|
+
marginBottom: spacing.md,
|
|
868
917
|
},
|
|
869
918
|
workspaceGroupHeaderPressed: {
|
|
870
|
-
|
|
919
|
+
backgroundColor: '#14181D',
|
|
871
920
|
},
|
|
872
921
|
workspaceGroupHeaderRow: {
|
|
873
922
|
flexDirection: 'row',
|
|
874
923
|
alignItems: 'center',
|
|
875
|
-
justifyContent: 'space-between',
|
|
876
924
|
gap: spacing.sm,
|
|
877
925
|
},
|
|
878
926
|
workspaceGroupTitleBlock: {
|
|
@@ -881,99 +929,201 @@ const styles = StyleSheet.create({
|
|
|
881
929
|
workspaceGroupTitle: {
|
|
882
930
|
...typography.body,
|
|
883
931
|
color: colors.textPrimary,
|
|
932
|
+
fontSize: 14,
|
|
884
933
|
fontWeight: '600',
|
|
885
934
|
},
|
|
886
935
|
workspaceGroupSubtitle: {
|
|
887
936
|
...typography.caption,
|
|
888
937
|
color: colors.textMuted,
|
|
889
|
-
|
|
938
|
+
fontSize: 11,
|
|
939
|
+
lineHeight: 14,
|
|
940
|
+
marginTop: 2,
|
|
941
|
+
},
|
|
942
|
+
workspaceGroupCountBadge: {
|
|
943
|
+
minWidth: 24,
|
|
944
|
+
borderRadius: 999,
|
|
945
|
+
backgroundColor: '#161B20',
|
|
946
|
+
paddingHorizontal: spacing.sm,
|
|
947
|
+
paddingVertical: 3,
|
|
948
|
+
alignItems: 'center',
|
|
949
|
+
justifyContent: 'center',
|
|
950
|
+
},
|
|
951
|
+
workspaceGroupCountText: {
|
|
952
|
+
...typography.caption,
|
|
953
|
+
color: colors.textSecondary,
|
|
954
|
+
fontSize: 10,
|
|
955
|
+
lineHeight: 12,
|
|
956
|
+
fontWeight: '700',
|
|
957
|
+
fontVariant: ['tabular-nums'],
|
|
890
958
|
},
|
|
891
959
|
workspaceGroupHeaderMeta: {
|
|
892
960
|
alignItems: 'center',
|
|
893
961
|
justifyContent: 'center',
|
|
894
962
|
flexShrink: 0,
|
|
895
963
|
},
|
|
896
|
-
|
|
897
|
-
|
|
964
|
+
chatItem: {
|
|
965
|
+
marginHorizontal: spacing.lg,
|
|
966
|
+
marginBottom: spacing.xs,
|
|
967
|
+
borderRadius: 16,
|
|
968
|
+
borderWidth: 1,
|
|
969
|
+
borderColor: 'rgba(255, 255, 255, 0.06)',
|
|
970
|
+
backgroundColor: '#080A0D',
|
|
971
|
+
padding: spacing.sm,
|
|
972
|
+
flexDirection: 'row',
|
|
973
|
+
gap: spacing.xs + 2,
|
|
974
|
+
alignItems: 'stretch',
|
|
898
975
|
},
|
|
899
|
-
|
|
900
|
-
|
|
976
|
+
chatItemSubAgent: {
|
|
977
|
+
backgroundColor: 'rgba(255, 255, 255, 0.025)',
|
|
901
978
|
},
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
979
|
+
chatItemLast: {
|
|
980
|
+
marginBottom: spacing.md,
|
|
981
|
+
},
|
|
982
|
+
chatItemSelected: {
|
|
983
|
+
backgroundColor: '#11151A',
|
|
984
|
+
borderColor: 'rgba(255, 255, 255, 0.18)',
|
|
908
985
|
},
|
|
909
|
-
|
|
986
|
+
chatItemPressed: {
|
|
987
|
+
backgroundColor: '#0E1216',
|
|
988
|
+
},
|
|
989
|
+
chatItemAccent: {
|
|
990
|
+
width: 4,
|
|
991
|
+
borderRadius: 999,
|
|
992
|
+
backgroundColor: 'rgba(255, 255, 255, 0.08)',
|
|
993
|
+
},
|
|
994
|
+
chatItemAccentSubAgent: {
|
|
995
|
+
backgroundColor: 'rgba(245, 165, 36, 0.35)',
|
|
996
|
+
},
|
|
997
|
+
chatItemAccentSelected: {
|
|
998
|
+
backgroundColor: colors.textPrimary,
|
|
999
|
+
},
|
|
1000
|
+
chatItemAccentRunning: {
|
|
1001
|
+
backgroundColor: colors.statusRunning,
|
|
1002
|
+
},
|
|
1003
|
+
chatItemAccentError: {
|
|
1004
|
+
backgroundColor: colors.statusError,
|
|
1005
|
+
},
|
|
1006
|
+
chatItemContent: {
|
|
910
1007
|
flex: 1,
|
|
911
|
-
|
|
912
|
-
justifyContent: 'center',
|
|
913
|
-
paddingHorizontal: spacing.lg,
|
|
1008
|
+
gap: 4,
|
|
914
1009
|
},
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
borderColor: colors.border,
|
|
920
|
-
maxHeight: '70%',
|
|
921
|
-
padding: spacing.md,
|
|
922
|
-
gap: spacing.sm,
|
|
1010
|
+
chatItemTopRow: {
|
|
1011
|
+
flexDirection: 'row',
|
|
1012
|
+
alignItems: 'center',
|
|
1013
|
+
gap: spacing.xs + 2,
|
|
923
1014
|
},
|
|
924
|
-
|
|
1015
|
+
chatSubAgentIcon: {
|
|
1016
|
+
marginRight: -2,
|
|
1017
|
+
},
|
|
1018
|
+
chatTitle: {
|
|
925
1019
|
...typography.body,
|
|
926
|
-
|
|
1020
|
+
flex: 1,
|
|
1021
|
+
color: colors.textSecondary,
|
|
1022
|
+
fontSize: 14,
|
|
927
1023
|
fontWeight: '600',
|
|
928
1024
|
},
|
|
929
|
-
|
|
930
|
-
|
|
1025
|
+
chatTitleSubAgent: {
|
|
1026
|
+
color: '#F5C06A',
|
|
1027
|
+
},
|
|
1028
|
+
chatTitleSelected: {
|
|
1029
|
+
color: colors.textPrimary,
|
|
1030
|
+
},
|
|
1031
|
+
chatAge: {
|
|
1032
|
+
...typography.caption,
|
|
1033
|
+
color: colors.textMuted,
|
|
1034
|
+
fontSize: 10,
|
|
1035
|
+
lineHeight: 12,
|
|
1036
|
+
fontVariant: ['tabular-nums'],
|
|
1037
|
+
flexShrink: 0,
|
|
1038
|
+
},
|
|
1039
|
+
chatAgeSelected: {
|
|
1040
|
+
color: colors.textSecondary,
|
|
1041
|
+
},
|
|
1042
|
+
chatItemBottomRow: {
|
|
1043
|
+
flexDirection: 'row',
|
|
1044
|
+
alignItems: 'center',
|
|
1045
|
+
gap: spacing.xs + 2,
|
|
1046
|
+
},
|
|
1047
|
+
chatPreview: {
|
|
1048
|
+
...typography.caption,
|
|
1049
|
+
flex: 1,
|
|
1050
|
+
fontSize: 11,
|
|
1051
|
+
lineHeight: 14,
|
|
1052
|
+
color: 'rgba(232, 236, 244, 0.56)',
|
|
1053
|
+
},
|
|
1054
|
+
chatPreviewSubAgent: {
|
|
1055
|
+
color: 'rgba(245, 192, 106, 0.9)',
|
|
1056
|
+
},
|
|
1057
|
+
chatPreviewSelected: {
|
|
1058
|
+
color: colors.textMuted,
|
|
931
1059
|
},
|
|
932
|
-
|
|
1060
|
+
chatMeta: {
|
|
1061
|
+
flexDirection: 'row',
|
|
1062
|
+
alignItems: 'center',
|
|
933
1063
|
gap: spacing.xs,
|
|
1064
|
+
flexShrink: 0,
|
|
934
1065
|
},
|
|
935
|
-
|
|
1066
|
+
statusPill: {
|
|
936
1067
|
flexDirection: 'row',
|
|
937
1068
|
alignItems: 'center',
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
borderColor: colors.borderLight,
|
|
943
|
-
backgroundColor: colors.bgItem,
|
|
944
|
-
paddingHorizontal: spacing.md,
|
|
945
|
-
paddingVertical: spacing.sm,
|
|
1069
|
+
gap: 4,
|
|
1070
|
+
borderRadius: 999,
|
|
1071
|
+
paddingHorizontal: spacing.xs + 6,
|
|
1072
|
+
paddingVertical: 3,
|
|
946
1073
|
},
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
backgroundColor: 'rgba(255, 255, 255, 0.08)',
|
|
1074
|
+
statusPillRunning: {
|
|
1075
|
+
backgroundColor: 'rgba(52, 199, 89, 0.12)',
|
|
950
1076
|
},
|
|
951
|
-
|
|
952
|
-
|
|
1077
|
+
statusPillError: {
|
|
1078
|
+
backgroundColor: 'rgba(239, 68, 68, 0.14)',
|
|
953
1079
|
},
|
|
954
|
-
|
|
1080
|
+
statusPillText: {
|
|
955
1081
|
...typography.caption,
|
|
956
|
-
|
|
957
|
-
|
|
1082
|
+
fontSize: 10,
|
|
1083
|
+
lineHeight: 12,
|
|
1084
|
+
fontWeight: '700',
|
|
958
1085
|
},
|
|
959
|
-
|
|
960
|
-
color:
|
|
961
|
-
fontWeight: '600',
|
|
1086
|
+
statusPillTextRunning: {
|
|
1087
|
+
color: '#8EE6AD',
|
|
962
1088
|
},
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1089
|
+
statusPillTextError: {
|
|
1090
|
+
color: '#FFB4B4',
|
|
1091
|
+
},
|
|
1092
|
+
statusPillDot: {
|
|
1093
|
+
width: 6,
|
|
1094
|
+
height: 6,
|
|
1095
|
+
borderRadius: 3,
|
|
1096
|
+
},
|
|
1097
|
+
statusPillDotRunning: {
|
|
1098
|
+
backgroundColor: '#34C759',
|
|
1099
|
+
},
|
|
1100
|
+
footer: {
|
|
1101
|
+
marginTop: 'auto',
|
|
1102
|
+
paddingHorizontal: spacing.lg,
|
|
1103
|
+
paddingTop: spacing.xs,
|
|
1104
|
+
paddingBottom: spacing.sm,
|
|
1105
|
+
},
|
|
1106
|
+
footerSettingsButton: {
|
|
1107
|
+
height: 42,
|
|
1108
|
+
borderRadius: 14,
|
|
966
1109
|
borderWidth: 1,
|
|
967
|
-
borderColor:
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
1110
|
+
borderColor: 'rgba(255, 255, 255, 0.12)',
|
|
1111
|
+
backgroundColor: '#101317',
|
|
1112
|
+
flexDirection: 'row',
|
|
1113
|
+
alignItems: 'center',
|
|
1114
|
+
justifyContent: 'center',
|
|
1115
|
+
gap: spacing.xs,
|
|
971
1116
|
},
|
|
972
|
-
|
|
973
|
-
|
|
1117
|
+
footerSettingsButtonPressed: {
|
|
1118
|
+
backgroundColor: '#171B20',
|
|
974
1119
|
},
|
|
975
|
-
|
|
1120
|
+
footerSettingsText: {
|
|
976
1121
|
...typography.caption,
|
|
977
1122
|
color: colors.textPrimary,
|
|
1123
|
+
fontSize: 11,
|
|
1124
|
+
lineHeight: 13,
|
|
1125
|
+
fontWeight: '700',
|
|
1126
|
+
textTransform: 'uppercase',
|
|
1127
|
+
letterSpacing: 0.4,
|
|
978
1128
|
},
|
|
979
1129
|
});
|