App.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. import React, { useState, useEffect } from 'react'
  2. import {
  3. View, Text, TextInput, TouchableOpacity, FlatList,
  4. StyleSheet, SafeAreaView, StatusBar, ActivityIndicator
  5. } from 'react-native'
  6. import { client, Session, Message } from './src/services/api'
  7. const MODES = ['build', 'plan', 'debug', 'solo'] as const
  8. type Mode = typeof MODES[number]
  9. export default function App() {
  10. const [sessions, setSessions] = useState<Session[]>([])
  11. const [currentSession, setCurrentSession] = useState<Session | null>(null)
  12. const [messages, setMessages] = useState<Message[]>([])
  13. const [inputText, setInputText] = useState('')
  14. const [mode, setMode] = useState<Mode>('build')
  15. const [connected, setConnected] = useState(false)
  16. const [loading, setLoading] = useState(true)
  17. useEffect(() => {
  18. checkConnection()
  19. }, [])
  20. async function checkConnection() {
  21. try {
  22. if (typeof window !== 'undefined') {
  23. await fetch('http://localhost:4096/global/health')
  24. }
  25. setConnected(true)
  26. } catch { }
  27. setLoading(false)
  28. }
  29. async function createSession() {
  30. try {
  31. const s = await client.createSession(`Chat ${new Date().toLocaleTimeString()}`)
  32. setCurrentSession(s)
  33. setMessages([])
  34. } catch (e) {
  35. console.error(e)
  36. }
  37. }
  38. async function sendMessage() {
  39. if (!inputText.trim() || !currentSession) return
  40. const userMsg: Message = {
  41. id: `u_${Date.now()}`, role: 'user',
  42. content: inputText, createdAt: new Date().toISOString()
  43. }
  44. setMessages(prev => [...prev, userMsg])
  45. setInputText('')
  46. try {
  47. await client.sendPrompt(currentSession.id, inputText, mode)
  48. const msgs = await client.getMessages(currentSession.id)
  49. setMessages(msgs)
  50. } catch (e) {
  51. console.error(e)
  52. }
  53. }
  54. if (loading) {
  55. return (
  56. <View style={styles.center}>
  57. <ActivityIndicator size="large" color="#e74c3c" />
  58. <Text style={styles.brand}>K I R I N</Text>
  59. </View>
  60. )
  61. }
  62. return (
  63. <SafeAreaView style={styles.container}>
  64. <StatusBar barStyle="light-content" backgroundColor="#1a1a2e" />
  65. {/* Mode Tabs */}
  66. <View style={styles.modeBar}>
  67. {MODES.map(m => (
  68. <TouchableOpacity key={m} style={[styles.modeTab, mode === m && styles.modeActive]}
  69. onPress={() => setMode(m)}>
  70. <Text style={[styles.modeText, mode === m && styles.modeActiveText]}>{m}</Text>
  71. </TouchableOpacity>
  72. ))}
  73. <Text style={styles.status}>{connected ? '🟢' : '🔴'}</Text>
  74. </View>
  75. {/* Chat Area */}
  76. {!currentSession ? (
  77. <View style={styles.center}>
  78. <Text style={styles.logo}>🦄</Text>
  79. <Text style={styles.brand}>K I R I N</Text>
  80. <TouchableOpacity style={styles.newChatBtn} onPress={createSession}>
  81. <Text style={styles.newChatText}>New Chat</Text>
  82. </TouchableOpacity>
  83. </View>
  84. ) : (
  85. <FlatList
  86. data={messages}
  87. keyExtractor={item => item.id}
  88. style={styles.messageList}
  89. renderItem={({ item }) => (
  90. <View style={[styles.bubble, item.role === 'user' ? styles.userBubble : styles.aiBubble]}>
  91. <Text style={[styles.bubbleText, item.role === 'user' ? styles.userText : styles.aiText]}>
  92. {item.content}
  93. </Text>
  94. </View>
  95. )}
  96. />
  97. )}
  98. {/* Input Bar */}
  99. <View style={styles.inputBar}>
  100. <TextInput
  101. style={styles.input}
  102. placeholder="Message..."
  103. placeholderTextColor="#7f8c8d"
  104. value={inputText}
  105. onChangeText={setInputText}
  106. onSubmitEditing={sendMessage}
  107. />
  108. <TouchableOpacity style={styles.sendBtn} onPress={sendMessage}>
  109. <Text style={styles.sendText}>→</Text>
  110. </TouchableOpacity>
  111. </View>
  112. </SafeAreaView>
  113. )
  114. }
  115. const styles = StyleSheet.create({
  116. container: { flex: 1, backgroundColor: '#0f172a' },
  117. center: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#0f172a' },
  118. logo: { fontSize: 48, opacity: 0.3 },
  119. brand: { fontSize: 20, fontWeight: '700', color: '#e74c3c', marginTop: 8, letterSpacing: 4 },
  120. modeBar: { flexDirection: 'row', padding: 8, backgroundColor: '#16213e', alignItems: 'center' },
  121. modeTab: { paddingHorizontal: 12, paddingVertical: 4, borderRadius: 6, marginRight: 4 },
  122. modeActive: { backgroundColor: '#e74c3c' },
  123. modeText: { fontSize: 12, color: '#7f8c8d' },
  124. modeActiveText: { color: '#fff' },
  125. status: { fontSize: 10, marginLeft: 'auto' },
  126. newChatBtn: { marginTop: 16, backgroundColor: '#e74c3c', paddingHorizontal: 24, paddingVertical: 10, borderRadius: 8 },
  127. newChatText: { color: '#fff', fontWeight: '600' },
  128. messageList: { flex: 1, padding: 8 },
  129. bubble: { padding: 10, borderRadius: 8, marginBottom: 6, maxWidth: '85%' },
  130. userBubble: { alignSelf: 'flex-end', backgroundColor: '#2c3e5033' },
  131. aiBubble: { alignSelf: 'flex-start', backgroundColor: '#16213e' },
  132. bubbleText: { fontSize: 14 },
  133. userText: { color: '#ecf0f1' },
  134. aiText: { color: '#bdc3c7' },
  135. inputBar: { flexDirection: 'row', padding: 8, backgroundColor: '#1a1a2e', alignItems: 'center' },
  136. input: { flex: 1, height: 40, backgroundColor: '#1a1a2e', borderRadius: 8, borderWidth: 1, borderColor: '#2c3e50', paddingHorizontal: 12, color: '#ecf0f1', fontSize: 14 },
  137. sendBtn: { width: 40, height: 40, backgroundColor: '#e74c3c', borderRadius: 8, justifyContent: 'center', alignItems: 'center', marginLeft: 6 },
  138. sendText: { color: '#fff', fontSize: 18 },
  139. })