diff --git a/app/src/screens/chat.tsx b/app/src/screens/chat.tsx index 13deec5b..7d28249a 100644 --- a/app/src/screens/chat.tsx +++ b/app/src/screens/chat.tsx @@ -11,7 +11,8 @@ import { Keyboard } from 'react-native' import 'react-native-get-random-values' -import { useContext, useState, useRef } from 'react' +import { useContext, useState, useRef, useEffect, useCallback } from 'react' +import AsyncStorage from '@react-native-async-storage/async-storage' import { ThemeContext, AppContext } from '../context' import { getEventSource, getFirstNCharsOrLess, getChatType } from '../utils' import { v4 as uuid } from 'uuid' @@ -40,6 +41,31 @@ export function Chat() { // Per-model chat state - each model has its own conversation history const [chatStates, setChatStates] = useState>({}) + const [chatLoaded, setChatLoaded] = useState(false) + + // Load persisted chat history on mount + useEffect(() => { + async function loadChatHistory() { + try { + const stored = await AsyncStorage.getItem('rnai-chatStates') + if (stored) { + setChatStates(JSON.parse(stored)) + } + } catch (err) { + console.log('error loading chat history', err) + } finally { + setChatLoaded(true) + } + } + loadChatHistory() + }, []) + + // Persist chat history when it changes + const saveChatHistory = useCallback((states: Record) => { + AsyncStorage.setItem('rnai-chatStates', JSON.stringify(states)).catch(err => + console.log('error saving chat history', err) + ) + }, []) // Helper to get or create chat state for current model const getChatState = (modelLabel: string): ChatState => { @@ -54,12 +80,20 @@ export function Chat() { })) } + // Persist the current chat states to AsyncStorage + const persistChatStates = () => { + setChatStates(current => { + saveChatHistory(current) + return current + }) + } + const { theme } = useContext(ThemeContext) const { chatType } = useContext(AppContext) const styles = getStyles(theme) async function chat() { - if (!input) return + if (!input || !chatLoaded) return Keyboard.dismiss() if (chatType.label.includes('claude')) { generateClaudeResponse() @@ -137,6 +171,7 @@ export function Chat() { })) } else { setLoading(false) + persistChatStates() es.close() } } else if (event.type === "error") { @@ -215,6 +250,7 @@ export function Chat() { ...prev, apiMessages: `${prev.apiMessages}\n\nPrompt: ${input}\n\nResponse:${localResponse}` })) + persistChatStates() es.close() } } else if (event.type === "error") { @@ -292,6 +328,7 @@ export function Chat() { ...prev, apiMessages: `${prev.apiMessages}\n\nHuman: ${input}\n\nAssistant:${getFirstNCharsOrLess(localResponse, 2000)}` })) + persistChatStates() es.close() } } else if (event.type === "error") { @@ -329,7 +366,12 @@ export function Chat() { async function clearChat() { if (loading) return const modelLabel = chatType.label - updateChatState(modelLabel, () => createEmptyChatState()) + setChatStates(prev => { + const next = { ...prev } + delete next[modelLabel] + saveChatHistory(next) + return next + }) } function renderItem({ diff --git a/app/src/screens/images.tsx b/app/src/screens/images.tsx index cb2abce2..3f5acacc 100644 --- a/app/src/screens/images.tsx +++ b/app/src/screens/images.tsx @@ -11,7 +11,7 @@ import { Keyboard, Image } from 'react-native' -import { useState, useRef, useContext } from 'react' +import { useState, useRef, useContext, useEffect, useCallback } from 'react' import { DOMAIN, IMAGE_MODELS } from '../../constants' import { v4 as uuid } from 'uuid' import { ThemeContext, AppContext } from '../context' @@ -21,6 +21,7 @@ import { useActionSheet } from '@expo/react-native-action-sheet' import * as FileSystem from 'expo-file-system' import * as ImagePicker from 'expo-image-picker' import * as Clipboard from 'expo-clipboard' +import AsyncStorage from '@react-native-async-storage/async-storage' const { width } = Dimensions.get('window') @@ -41,6 +42,35 @@ export function Images() { index: uuid, values: [] }) + const [imagesLoaded, setImagesLoaded] = useState(false) + + // Load persisted image history on mount + useEffect(() => { + async function loadImageHistory() { + try { + const stored = await AsyncStorage.getItem('rnai-imageHistory') + if (stored) { + const parsed = JSON.parse(stored) + setImages(parsed) + if (parsed.values.length > 0) { + setCallMade(true) + } + } + } catch (err) { + console.log('error loading image history', err) + } finally { + setImagesLoaded(true) + } + } + loadImageHistory() + }, []) + + // Persist image history when it changes + const saveImageHistory = useCallback((state: ImagesState) => { + AsyncStorage.setItem('rnai-imageHistory', JSON.stringify(state)).catch(err => + console.log('error saving image history', err) + ) + }, []) const { handlePresentModalPress, closeModal, @@ -56,7 +86,7 @@ export function Images() { const showImagePickerButton = !hideInput async function generate() { - if (loading) return + if (loading || !imagesLoaded) return if (hideInput && !image) { console.log('no image selected') return @@ -128,10 +158,12 @@ export function Images() { imagesArray[imagesArray.length - 1].image = response.image imagesArray[imagesArray.length - 1].model = currentModel imagesArray[imagesArray.length - 1].provider = providerLabel - setImages(i => ({ - index: i.index, + const newState = { + index: images.index, values: imagesArray - })) + } + setImages(newState) + saveImageHistory(newState) setLoading(false) setTimeout(() => { scrollViewRef.current?.scrollToEnd({ @@ -177,10 +209,14 @@ export function Images() { function clearPrompts() { setCallMade(false) - setImages({ + const emptyState = { index: uuid, values: [] - }) + } + setImages(emptyState) + AsyncStorage.removeItem('rnai-imageHistory').catch(err => + console.log('error clearing image history', err) + ) } async function showClipboardActionsheet(d) {