116 lines
3.3 KiB
TypeScript
116 lines
3.3 KiB
TypeScript
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
import { StatusBar, StyleSheet, View } from 'react-native';
|
|
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context';
|
|
import ChatArea from './components/ChatArea';
|
|
import InputArea from './components/InputArea';
|
|
import { fetchHistory, sendMessage, syncHistory } from './api';
|
|
import { loadMessages, mergeMessages, saveMessages } from './storage';
|
|
|
|
import type { Message } from './types';
|
|
|
|
export default function App() {
|
|
const [messages, setMessages] = useState<Message[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
const [synced, setSynced] = useState(false);
|
|
const [refreshing, setRefreshing] = useState(false);
|
|
const nextId = useRef(1);
|
|
|
|
// 启动:先读本地缓存,再异步拉远端
|
|
useEffect(() => {
|
|
(async () => {
|
|
const local = await loadMessages();
|
|
if (local.length > 0) {
|
|
setMessages(local);
|
|
nextId.current = Math.max(...local.map(m => m.id)) + 1;
|
|
}
|
|
|
|
const remote = await fetchHistory();
|
|
if (remote.length > 0) {
|
|
const merged = mergeMessages(local, remote);
|
|
setMessages(merged);
|
|
nextId.current = Math.max(...merged.map(m => m.id)) + 1;
|
|
await saveMessages(merged);
|
|
} else if (local.length > 0) {
|
|
// 服务端没数据但本地有,把本地推上去
|
|
await syncHistory(local);
|
|
}
|
|
|
|
setSynced(true);
|
|
})();
|
|
}, []);
|
|
|
|
// synced 后,每次 messages 变化同步写本地 + 远端
|
|
useEffect(() => {
|
|
if (!synced || messages.length === 0) return;
|
|
saveMessages(messages);
|
|
syncHistory(messages);
|
|
}, [messages, synced]);
|
|
|
|
const handleSend = useCallback(
|
|
async (text: string) => {
|
|
const userMsg: Message = {
|
|
id: nextId.current++,
|
|
role: 'user',
|
|
text,
|
|
};
|
|
const withUser = [...messages, userMsg];
|
|
setMessages(withUser);
|
|
setLoading(true);
|
|
|
|
try {
|
|
const reply = await sendMessage(withUser);
|
|
const shenyanMsg: Message = {
|
|
id: nextId.current++,
|
|
role: 'shenyan',
|
|
text: reply,
|
|
};
|
|
setMessages(prev => [...prev, shenyanMsg]);
|
|
} catch {
|
|
const errMsg: Message = {
|
|
id: nextId.current++,
|
|
role: 'shenyan',
|
|
text: '连接失败',
|
|
};
|
|
setMessages(prev => [...prev, errMsg]);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
},
|
|
[messages],
|
|
);
|
|
|
|
const handleRefresh = useCallback(async () => {
|
|
setRefreshing(true);
|
|
const remote = await fetchHistory();
|
|
if (remote.length > 0) {
|
|
setMessages(prev => {
|
|
const merged = mergeMessages(prev, remote);
|
|
nextId.current = Math.max(...merged.map(m => m.id)) + 1;
|
|
return merged;
|
|
});
|
|
}
|
|
setRefreshing(false);
|
|
}, []);
|
|
|
|
return (
|
|
<SafeAreaProvider>
|
|
<StatusBar barStyle="light-content" backgroundColor="#000" />
|
|
<SafeAreaView style={styles.container} edges={['top']}>
|
|
<ChatArea
|
|
messages={messages}
|
|
onRefresh={handleRefresh}
|
|
refreshing={refreshing}
|
|
/>
|
|
<InputArea onSend={handleSend} disabled={loading} />
|
|
</SafeAreaView>
|
|
</SafeAreaProvider>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: '#000',
|
|
},
|
|
});
|