import { createContext } from "preact"; import { useEffect, useMemo, useState } from "preact/hooks"; import { MessageTools, type IMessage } from "../messages"; interface IContext { connectionUrl: string; input: string; name: string; messages: IMessage[]; triggerNext: boolean; } interface IActions { setConnectionUrl: (url: string) => void; setInput: (url: string) => void; setName: (name: string) => void; setTriggerNext: (triggerNext: boolean) => void; setMessages: (messages: IMessage[]) => void; addMessage: (content: string, role: IMessage['role'], triggerNext?: boolean) => void; editMessage: (index: number, content: string, triggerNext?: boolean) => void; deleteMessage: (index: number) => void; setCurrentSwipe: (index: number, swipe: number) => void; addSwipe: (index: number, content: string) => void; continueMessage: () => void; } const SAVE_KEY = 'ai_game_save_state'; export const saveContext = (context: IContext) => { localStorage.setItem(SAVE_KEY, JSON.stringify({ ...context, triggerNext: false, })); } export const loadContext = (): IContext => { const defaultContext: IContext = { connectionUrl: 'http://192.168.10.102:5001', input: '', name: 'Maya', messages: [], triggerNext: false, }; let loadedContext: Partial = {}; try { const json = localStorage.getItem(SAVE_KEY); if (json) { loadedContext = JSON.parse(json); } } catch { } return { ...defaultContext, ...loadedContext }; } export type IStateContext = IContext & IActions; export const StateContext = createContext({} as IStateContext); export const StateContextProvider = ({ children }: { children?: any }) => { const loadedContext = useMemo(() => loadContext(), []); const [connectionUrl, setConnectionUrl] = useState(loadedContext.connectionUrl); const [input, setInput] = useState(loadedContext.input); const [name, setName] = useState(loadedContext.name); const [messages, setMessages] = useState(loadedContext.messages); const [triggerNext, setTriggerNext] = useState(false); const actions: IActions = useMemo(() => ({ setConnectionUrl, setInput, setName, setTriggerNext, setMessages: (newMessages) => setMessages(newMessages.slice()), addMessage: (content, role, triggerNext = false) => { setMessages(messages => [ ...messages, MessageTools.create(content, role), ]); setTriggerNext(triggerNext); }, editMessage: (index, content, triggerNext = false) => { setMessages(messages => MessageTools.updateContent(messages, index, content)); setTriggerNext(triggerNext); }, deleteMessage: (index) => setMessages(messages => messages.filter((_, i) => i !== index) ), setCurrentSwipe: (index, currentSwipe) => { let shouldTrigger = false; setMessages(messages => messages.map( (message, i) => { if (i === index) { const swipes = message.swipes.slice(); const latestSwipe = swipes.at(-1); if (currentSwipe >= swipes.length) { if (latestSwipe.content.length > 0) { currentSwipe = swipes.length; swipes.push({ content: '' }); } else { currentSwipe = swipes.length - 1; } shouldTrigger = true; } else while (currentSwipe < 0) { currentSwipe += swipes.length; } return { ...message, swipes, currentSwipe }; } else { return message; } } ) ); setTriggerNext(shouldTrigger); }, addSwipe: (index, content) => setMessages(messages => messages.map( (message, i) => { if (i === index) { const swipes = [...message.swipes, { content }]; return { ...message, swipes, currentSwipe: swipes.length - 1, }; } else { return message; } } ) ), continueMessage: () => setTriggerNext(true), }), []); const rawContext: IContext = { connectionUrl, input, name, messages, triggerNext, }; const context = useMemo(() => rawContext, Object.values(rawContext)); useEffect(() => { saveContext(context); }, [context]); const value = useMemo(() => ({ ...context, ...actions }), [context, actions]) return ( {children} ); }