Matthias Nott
2026-03-02 a0f39302919fbacf7a0d407f01b1a50413ea6f70
app/chat.tsx
....@@ -1,4 +1,4 @@
1
-import React, { useCallback } from "react";
1
+import React, { useCallback, useState } from "react";
22 import { Pressable, Text, View } from "react-native";
33 import { SafeAreaView } from "react-native-safe-area-context";
44 import { router } from "expo-router";
....@@ -6,69 +6,129 @@
66 import { useConnection } from "../contexts/ConnectionContext";
77 import { MessageList } from "../components/chat/MessageList";
88 import { InputBar } from "../components/chat/InputBar";
9
-import { CommandBar } from "../components/chat/CommandBar";
9
+import { CommandBar, TextModeCommandBar } from "../components/chat/CommandBar";
1010 import { StatusDot } from "../components/ui/StatusDot";
11
+import { SessionPicker } from "../components/SessionPicker";
12
+import { playAudio } from "../services/audio";
1113
1214 export default function ChatScreen() {
13
- const { messages, sendTextMessage, sendVoiceMessage, clearMessages } =
15
+ const { messages, sendTextMessage, clearMessages, requestScreenshot } =
1416 useChat();
1517 const { status } = useConnection();
18
+ const [isTextMode, setIsTextMode] = useState(false);
19
+ const [showSessions, setShowSessions] = useState(false);
1620
17
- const handleCommand = useCallback(
18
- (command: string) => {
19
- if (command === "/clear") {
20
- clearMessages();
21
+ const handleSessions = useCallback(() => {
22
+ setShowSessions(true);
23
+ }, []);
24
+
25
+ const handleScreenshot = useCallback(() => {
26
+ requestScreenshot();
27
+ router.push("/navigate");
28
+ }, [requestScreenshot]);
29
+
30
+ const handleHelp = useCallback(() => {
31
+ sendTextMessage("/h");
32
+ }, [sendTextMessage]);
33
+
34
+ const handleNavigate = useCallback(() => {
35
+ router.push("/navigate");
36
+ }, []);
37
+
38
+ const handleClear = useCallback(() => {
39
+ clearMessages();
40
+ }, [clearMessages]);
41
+
42
+ const handleReplay = useCallback(() => {
43
+ for (let i = messages.length - 1; i >= 0; i--) {
44
+ const msg = messages[i];
45
+ if (msg.role === "assistant") {
46
+ if (msg.audioUri) {
47
+ playAudio(msg.audioUri).catch(() => {});
48
+ }
2149 return;
2250 }
23
- sendTextMessage(command);
24
- },
25
- [sendTextMessage, clearMessages]
26
- );
27
-
28
- const handleSendVoice = useCallback(
29
- (audioUri: string, durationMs: number) => {
30
- sendVoiceMessage(audioUri, durationMs);
31
- },
32
- [sendVoiceMessage]
33
- );
51
+ }
52
+ }, [messages]);
3453
3554 return (
36
- <SafeAreaView className="flex-1 bg-pai-bg" edges={["top", "bottom"]}>
55
+ <SafeAreaView style={{ flex: 1, backgroundColor: "#0A0A0F" }} edges={["top", "bottom"]}>
3756 {/* Header */}
38
- <View className="flex-row items-center justify-between px-4 py-3 border-b border-pai-border">
39
- <Text className="text-pai-text text-xl font-bold tracking-tight">
40
- PAILot
41
- </Text>
42
- <View className="flex-row items-center gap-3">
43
- <StatusDot status={status} size={10} />
44
- <Text className="text-pai-text-secondary text-xs">
45
- {status === "connected"
46
- ? "Connected"
47
- : status === "connecting"
48
- ? "Connecting..."
49
- : "Offline"}
50
- </Text>
51
- <Pressable
52
- onPress={() => router.push("/settings")}
53
- className="w-9 h-9 items-center justify-center rounded-full bg-pai-bg-tertiary"
54
- hitSlop={{ top: 4, bottom: 4, left: 4, right: 4 }}
57
+ <View
58
+ style={{
59
+ flexDirection: "row",
60
+ alignItems: "center",
61
+ justifyContent: "space-between",
62
+ paddingHorizontal: 16,
63
+ paddingVertical: 12,
64
+ borderBottomWidth: 1,
65
+ borderBottomColor: "#2E2E45",
66
+ }}
67
+ >
68
+ <View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
69
+ <Text
70
+ style={{
71
+ color: "#E8E8F0",
72
+ fontSize: 22,
73
+ fontWeight: "800",
74
+ letterSpacing: -0.5,
75
+ }}
5576 >
56
- <Text className="text-base">⚙️</Text>
57
- </Pressable>
77
+ PAILot
78
+ </Text>
79
+ <StatusDot status={status} size={8} />
5880 </View>
81
+
82
+ <Pressable
83
+ onPress={() => router.push("/settings")}
84
+ hitSlop={{ top: 6, bottom: 6, left: 6, right: 6 }}
85
+ style={{
86
+ width: 36,
87
+ height: 36,
88
+ alignItems: "center",
89
+ justifyContent: "center",
90
+ borderRadius: 18,
91
+ backgroundColor: "#1E1E2E",
92
+ }}
93
+ >
94
+ <Text style={{ fontSize: 15 }}>⚙️</Text>
95
+ </Pressable>
5996 </View>
6097
6198 {/* Message list */}
62
- <View className="flex-1">
99
+ <View style={{ flex: 1 }}>
63100 {messages.length === 0 ? (
64
- <View className="flex-1 items-center justify-center gap-3">
65
- <Text className="text-5xl">🛩</Text>
66
- <Text className="text-pai-text text-xl font-semibold">
67
- PAILot
68
- </Text>
69
- <Text className="text-pai-text-muted text-sm text-center px-8">
70
- Voice-first AI communicator.{"\n"}Hold the mic button to talk.
71
- </Text>
101
+ <View style={{ flex: 1, alignItems: "center", justifyContent: "center", gap: 16 }}>
102
+ <View
103
+ style={{
104
+ width: 80,
105
+ height: 80,
106
+ borderRadius: 40,
107
+ backgroundColor: "#1E1E2E",
108
+ alignItems: "center",
109
+ justifyContent: "center",
110
+ borderWidth: 1,
111
+ borderColor: "#2E2E45",
112
+ }}
113
+ >
114
+ <Text style={{ fontSize: 36 }}>🛩</Text>
115
+ </View>
116
+ <View style={{ alignItems: "center", gap: 6 }}>
117
+ <Text style={{ color: "#E8E8F0", fontSize: 20, fontWeight: "700" }}>
118
+ PAILot
119
+ </Text>
120
+ <Text
121
+ style={{
122
+ color: "#5A5A78",
123
+ fontSize: 14,
124
+ textAlign: "center",
125
+ paddingHorizontal: 40,
126
+ lineHeight: 20,
127
+ }}
128
+ >
129
+ Voice-first AI communicator.{"\n"}Tap the mic to start talking.
130
+ </Text>
131
+ </View>
72132 </View>
73133 ) : (
74134 <MessageList messages={messages} />
....@@ -76,10 +136,34 @@
76136 </View>
77137
78138 {/* Command bar */}
79
- <CommandBar onCommand={handleCommand} />
139
+ {isTextMode ? (
140
+ <TextModeCommandBar
141
+ onSessions={handleSessions}
142
+ onScreenshot={handleScreenshot}
143
+ onNavigate={handleNavigate}
144
+ onClear={handleClear}
145
+ />
146
+ ) : (
147
+ <CommandBar
148
+ onSessions={handleSessions}
149
+ onScreenshot={handleScreenshot}
150
+ onHelp={handleHelp}
151
+ />
152
+ )}
80153
81154 {/* Input bar */}
82
- <InputBar onSendText={sendTextMessage} onSendVoice={handleSendVoice} />
155
+ <InputBar
156
+ onSendText={sendTextMessage}
157
+ onReplay={handleReplay}
158
+ isTextMode={isTextMode}
159
+ onToggleMode={() => setIsTextMode((v) => !v)}
160
+ />
161
+
162
+ {/* Session picker modal */}
163
+ <SessionPicker
164
+ visible={showSessions}
165
+ onClose={() => setShowSessions(false)}
166
+ />
83167 </SafeAreaView>
84168 );
85169 }