|
|
@@ -0,0 +1,151 @@
|
|
|
+import React, { useState, useEffect } from 'react'
|
|
|
+import {
|
|
|
+ View, Text, TextInput, TouchableOpacity, FlatList,
|
|
|
+ StyleSheet, SafeAreaView, StatusBar, ActivityIndicator
|
|
|
+} from 'react-native'
|
|
|
+import { client, Session, Message } from './src/services/api'
|
|
|
+
|
|
|
+const MODES = ['build', 'plan', 'debug', 'solo'] as const
|
|
|
+type Mode = typeof MODES[number]
|
|
|
+
|
|
|
+export default function App() {
|
|
|
+ const [sessions, setSessions] = useState<Session[]>([])
|
|
|
+ const [currentSession, setCurrentSession] = useState<Session | null>(null)
|
|
|
+ const [messages, setMessages] = useState<Message[]>([])
|
|
|
+ const [inputText, setInputText] = useState('')
|
|
|
+ const [mode, setMode] = useState<Mode>('build')
|
|
|
+ const [connected, setConnected] = useState(false)
|
|
|
+ const [loading, setLoading] = useState(true)
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ checkConnection()
|
|
|
+ }, [])
|
|
|
+
|
|
|
+ async function checkConnection() {
|
|
|
+ try {
|
|
|
+ if (typeof window !== 'undefined') {
|
|
|
+ await fetch('http://localhost:4096/global/health')
|
|
|
+ }
|
|
|
+ setConnected(true)
|
|
|
+ } catch { }
|
|
|
+ setLoading(false)
|
|
|
+ }
|
|
|
+
|
|
|
+ async function createSession() {
|
|
|
+ try {
|
|
|
+ const s = await client.createSession(`Chat ${new Date().toLocaleTimeString()}`)
|
|
|
+ setCurrentSession(s)
|
|
|
+ setMessages([])
|
|
|
+ } catch (e) {
|
|
|
+ console.error(e)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ async function sendMessage() {
|
|
|
+ if (!inputText.trim() || !currentSession) return
|
|
|
+ const userMsg: Message = {
|
|
|
+ id: `u_${Date.now()}`, role: 'user',
|
|
|
+ content: inputText, createdAt: new Date().toISOString()
|
|
|
+ }
|
|
|
+ setMessages(prev => [...prev, userMsg])
|
|
|
+ setInputText('')
|
|
|
+ try {
|
|
|
+ await client.sendPrompt(currentSession.id, inputText, mode)
|
|
|
+ const msgs = await client.getMessages(currentSession.id)
|
|
|
+ setMessages(msgs)
|
|
|
+ } catch (e) {
|
|
|
+ console.error(e)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (loading) {
|
|
|
+ return (
|
|
|
+ <View style={styles.center}>
|
|
|
+ <ActivityIndicator size="large" color="#e74c3c" />
|
|
|
+ <Text style={styles.brand}>K I R I N</Text>
|
|
|
+ </View>
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ <SafeAreaView style={styles.container}>
|
|
|
+ <StatusBar barStyle="light-content" backgroundColor="#1a1a2e" />
|
|
|
+
|
|
|
+ {/* Mode Tabs */}
|
|
|
+ <View style={styles.modeBar}>
|
|
|
+ {MODES.map(m => (
|
|
|
+ <TouchableOpacity key={m} style={[styles.modeTab, mode === m && styles.modeActive]}
|
|
|
+ onPress={() => setMode(m)}>
|
|
|
+ <Text style={[styles.modeText, mode === m && styles.modeActiveText]}>{m}</Text>
|
|
|
+ </TouchableOpacity>
|
|
|
+ ))}
|
|
|
+ <Text style={styles.status}>{connected ? '🟢' : '🔴'}</Text>
|
|
|
+ </View>
|
|
|
+
|
|
|
+ {/* Chat Area */}
|
|
|
+ {!currentSession ? (
|
|
|
+ <View style={styles.center}>
|
|
|
+ <Text style={styles.logo}>🦄</Text>
|
|
|
+ <Text style={styles.brand}>K I R I N</Text>
|
|
|
+ <TouchableOpacity style={styles.newChatBtn} onPress={createSession}>
|
|
|
+ <Text style={styles.newChatText}>New Chat</Text>
|
|
|
+ </TouchableOpacity>
|
|
|
+ </View>
|
|
|
+ ) : (
|
|
|
+ <FlatList
|
|
|
+ data={messages}
|
|
|
+ keyExtractor={item => item.id}
|
|
|
+ style={styles.messageList}
|
|
|
+ renderItem={({ item }) => (
|
|
|
+ <View style={[styles.bubble, item.role === 'user' ? styles.userBubble : styles.aiBubble]}>
|
|
|
+ <Text style={[styles.bubbleText, item.role === 'user' ? styles.userText : styles.aiText]}>
|
|
|
+ {item.content}
|
|
|
+ </Text>
|
|
|
+ </View>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+ )}
|
|
|
+
|
|
|
+ {/* Input Bar */}
|
|
|
+ <View style={styles.inputBar}>
|
|
|
+ <TextInput
|
|
|
+ style={styles.input}
|
|
|
+ placeholder="Message..."
|
|
|
+ placeholderTextColor="#7f8c8d"
|
|
|
+ value={inputText}
|
|
|
+ onChangeText={setInputText}
|
|
|
+ onSubmitEditing={sendMessage}
|
|
|
+ />
|
|
|
+ <TouchableOpacity style={styles.sendBtn} onPress={sendMessage}>
|
|
|
+ <Text style={styles.sendText}>→</Text>
|
|
|
+ </TouchableOpacity>
|
|
|
+ </View>
|
|
|
+ </SafeAreaView>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+const styles = StyleSheet.create({
|
|
|
+ container: { flex: 1, backgroundColor: '#0f172a' },
|
|
|
+ center: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#0f172a' },
|
|
|
+ logo: { fontSize: 48, opacity: 0.3 },
|
|
|
+ brand: { fontSize: 20, fontWeight: '700', color: '#e74c3c', marginTop: 8, letterSpacing: 4 },
|
|
|
+ modeBar: { flexDirection: 'row', padding: 8, backgroundColor: '#16213e', alignItems: 'center' },
|
|
|
+ modeTab: { paddingHorizontal: 12, paddingVertical: 4, borderRadius: 6, marginRight: 4 },
|
|
|
+ modeActive: { backgroundColor: '#e74c3c' },
|
|
|
+ modeText: { fontSize: 12, color: '#7f8c8d' },
|
|
|
+ modeActiveText: { color: '#fff' },
|
|
|
+ status: { fontSize: 10, marginLeft: 'auto' },
|
|
|
+ newChatBtn: { marginTop: 16, backgroundColor: '#e74c3c', paddingHorizontal: 24, paddingVertical: 10, borderRadius: 8 },
|
|
|
+ newChatText: { color: '#fff', fontWeight: '600' },
|
|
|
+ messageList: { flex: 1, padding: 8 },
|
|
|
+ bubble: { padding: 10, borderRadius: 8, marginBottom: 6, maxWidth: '85%' },
|
|
|
+ userBubble: { alignSelf: 'flex-end', backgroundColor: '#2c3e5033' },
|
|
|
+ aiBubble: { alignSelf: 'flex-start', backgroundColor: '#16213e' },
|
|
|
+ bubbleText: { fontSize: 14 },
|
|
|
+ userText: { color: '#ecf0f1' },
|
|
|
+ aiText: { color: '#bdc3c7' },
|
|
|
+ inputBar: { flexDirection: 'row', padding: 8, backgroundColor: '#1a1a2e', alignItems: 'center' },
|
|
|
+ input: { flex: 1, height: 40, backgroundColor: '#1a1a2e', borderRadius: 8, borderWidth: 1, borderColor: '#2c3e50', paddingHorizontal: 12, color: '#ecf0f1', fontSize: 14 },
|
|
|
+ sendBtn: { width: 40, height: 40, backgroundColor: '#e74c3c', borderRadius: 8, justifyContent: 'center', alignItems: 'center', marginLeft: 6 },
|
|
|
+ sendText: { color: '#fff', fontSize: 18 },
|
|
|
+})
|