import { useRef, useState } from "react"; import { View, Text, TextInput, Pressable, ScrollView, StyleSheet } from "react-native"; import { Ionicons } from "@expo/vector-icons"; import * as Speech from "expo-speech"; import { useAuth } from "@/auth"; import { api, ApiError } from "@/api"; import { C } from "@/theme"; interface Msg { role: "user" | "assistant"; text: string; } export default function Chat() { const { token, logout } = useAuth(); const [messages, setMessages] = useState([]); const [input, setInput] = useState(""); const [busy, setBusy] = useState(false); const [speak, setSpeak] = useState(false); const [error, setError] = useState(null); const scroll = useRef(null); const send = async (): Promise => { const t = input.trim(); if (!t || busy || !token) return; setInput(""); setError(null); setMessages((m) => [...m, { role: "user", text: t }]); setBusy(true); try { const { reply } = await api.chat(token, t); setMessages((m) => [...m, { role: "assistant", text: reply }]); if (speak) Speech.speak(reply, { language: "fr-FR" }); } catch (err) { if (err instanceof ApiError && err.status === 401) { await logout(); return; } setError(err instanceof Error ? err.message : "Erreur"); } finally { setBusy(false); } }; return ( scroll.current?.scrollToEnd({ animated: true })} > {messages.length === 0 && Pose une question à CHLOVA…} {messages.map((m, i) => ( {m.text} ))} {busy && CHLOVA réfléchit…} {error && {error}} setSpeak((v) => !v)} style={s.iconBtn} accessibilityLabel="Voix"> ); } const s = StyleSheet.create({ wrap: { flex: 1, backgroundColor: C.bg }, list: { flex: 1 }, muted: { color: C.muted, fontSize: 13 }, error: { color: C.danger, fontSize: 13 }, bubble: { maxWidth: "85%", borderRadius: 10, padding: 10, borderWidth: 1 }, user: { alignSelf: "flex-end", backgroundColor: C.surface2, borderColor: C.accent }, assistant: { alignSelf: "flex-start", backgroundColor: C.surface, borderColor: C.border }, bubbleText: { color: C.fg, fontSize: 14 }, bar: { flexDirection: "row", alignItems: "center", gap: 8, padding: 10, borderTopWidth: 1, borderTopColor: C.border, backgroundColor: C.surface }, iconBtn: { padding: 8, borderRadius: 8, borderWidth: 1, borderColor: C.border }, input: { flex: 1, backgroundColor: C.surface2, borderColor: C.border, borderWidth: 1, borderRadius: 8, color: C.fg, paddingHorizontal: 12, paddingVertical: 8 }, send: { backgroundColor: C.accent, borderRadius: 8, padding: 10 }, disabled: { opacity: 0.5 }, });