Move stores to reducer
This commit is contained in:
parent
77802634ae
commit
22eeae962a
|
|
@ -1,3 +1,5 @@
|
|||
import type { StateUpdater } from "preact/hooks";
|
||||
|
||||
export const nextFrame = async (): Promise<number> => new Promise((resolve) => requestAnimationFrame(resolve));
|
||||
export const delay = async (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
|
|
@ -74,13 +76,15 @@ export const intHash = (seed: number, ...parts: number[]) => {
|
|||
return h1;
|
||||
};
|
||||
export const sinHash = (...data: number[]) => data.reduce((hash, n) => Math.sin((hash * 123.12 + n) * 756.12), 0) / 2 + 0.5;
|
||||
export const throttle = function <T, A extends unknown[], R, F extends (this: T, ...args: A) => R>(func: F, ms: number, trailing = false): F {
|
||||
|
||||
type F<T, A extends unknown[], R> = (this: T, ...args: A) => R;
|
||||
export const throttle = function <T, A extends unknown[], R>(func: F<T, A, R>, ms: number, trailing = false): F<T, A, R> {
|
||||
let isThrottled = false;
|
||||
let savedResult: R;
|
||||
let savedThis: T;
|
||||
let savedArgs: A | undefined;
|
||||
|
||||
const wrapper: F = function (...args: A) {
|
||||
const wrapper = function (this: T, ...args: A): R {
|
||||
if (isThrottled) {
|
||||
savedThis = this;
|
||||
savedArgs = args;
|
||||
|
|
@ -99,7 +103,10 @@ export const throttle = function <T, A extends unknown[], R, F extends (this: T,
|
|||
}
|
||||
|
||||
return savedResult;
|
||||
} as F;
|
||||
};
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
export const callUpdater = <T>(f: StateUpdater<T>, prev: T) =>
|
||||
typeof f === 'function' ? (f as Function)(prev) : f;
|
||||
|
|
|
|||
|
|
@ -1,16 +1,43 @@
|
|||
import { createContext } from "preact";
|
||||
import { useCallback, useEffect, useMemo, useState, type Dispatch, type StateUpdater } from "preact/hooks";
|
||||
import { useCallback, useEffect, useMemo, useReducer, useState, type Dispatch, type StateUpdater } from "preact/hooks";
|
||||
import { MessageTools, type IMessage } from "../tools/messages";
|
||||
import { useInputState } from "@common/hooks/useInputState";
|
||||
import { type IConnection } from "../tools/connection";
|
||||
import { loadObject, saveObject } from "../tools/storage";
|
||||
import { useInputCallback } from "@common/hooks/useInputCallback";
|
||||
import { callUpdater, throttle } from "@common/utils";
|
||||
|
||||
interface IStory {
|
||||
lore: string;
|
||||
messages: IMessage[];
|
||||
}
|
||||
|
||||
interface StoriesState {
|
||||
currentStory: string;
|
||||
stories: Record<string, IStory>;
|
||||
lore: string;
|
||||
messages: IMessage[];
|
||||
}
|
||||
|
||||
interface StoryActionSetCurrent {
|
||||
currentStory: StateUpdater<string>;
|
||||
}
|
||||
|
||||
interface StoryActionSetMessages {
|
||||
messages: StateUpdater<IMessage[]>;
|
||||
}
|
||||
|
||||
interface StoryActionSetLore {
|
||||
lore: StateUpdater<string>;
|
||||
}
|
||||
|
||||
interface StoryActionWithId {
|
||||
action: 'create' | 'delete';
|
||||
id: string;
|
||||
}
|
||||
|
||||
type StoryAction = StoryActionSetCurrent | StoryActionSetLore | StoryActionSetMessages | StoryActionWithId;
|
||||
|
||||
interface IContext {
|
||||
currentConnection: number;
|
||||
availableConnections: IConnection[];
|
||||
|
|
@ -118,7 +145,7 @@ const EMPTY_STORY: IStory = {
|
|||
messages: [],
|
||||
};
|
||||
|
||||
const saveContext = async (context: IContext & IComputableContext) => {
|
||||
const saveContext = throttle(async (context: IContext & IComputableContext) => {
|
||||
const contextToSave: Partial<IContext & IComputableContext> = { ...context };
|
||||
delete contextToSave.connection;
|
||||
delete contextToSave.triggerNext;
|
||||
|
|
@ -127,7 +154,7 @@ const saveContext = async (context: IContext & IComputableContext) => {
|
|||
delete contextToSave.messages;
|
||||
|
||||
return saveObject(SAVE_KEY, contextToSave);
|
||||
}
|
||||
}, 1000, true);
|
||||
|
||||
export type IStateContext = IContext & IActions & IComputableContext;
|
||||
|
||||
|
|
@ -135,12 +162,43 @@ export const StateContext = createContext<IStateContext>({} as IStateContext);
|
|||
|
||||
const loadedContext = await loadObject(SAVE_KEY, DEFAULT_CONTEXT);
|
||||
|
||||
const storyReducer = (state: StoriesState, action: StoryAction): StoriesState => {
|
||||
let { currentStory, stories } = state;
|
||||
|
||||
if ('id' in action) {
|
||||
switch (action.action) {
|
||||
case 'create':
|
||||
stories[action.id] = EMPTY_STORY;
|
||||
break;
|
||||
case 'delete':
|
||||
if (action.id !== DEFAULT_STORY) {
|
||||
delete stories[action.id];
|
||||
if (action.id === currentStory) {
|
||||
currentStory = DEFAULT_STORY;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if ('currentStory' in action) {
|
||||
currentStory = callUpdater(action.currentStory, currentStory);
|
||||
} else if ('messages' in action) {
|
||||
stories[currentStory].messages = callUpdater(action.messages, stories[currentStory]?.messages ?? []);
|
||||
} else if ('lore' in action) {
|
||||
stories[currentStory].lore = callUpdater(action.lore, stories[currentStory]?.lore ?? '');
|
||||
}
|
||||
|
||||
return {
|
||||
currentStory,
|
||||
stories,
|
||||
lore: stories[currentStory].lore ?? '',
|
||||
messages: stories[currentStory].messages ?? [],
|
||||
};
|
||||
};
|
||||
|
||||
export const StateContextProvider = ({ children }: { children?: any }) => {
|
||||
const [currentConnection, setCurrentConnection] = useState<number>(loadedContext.currentConnection);
|
||||
const [availableConnections, setAvailableConnections] = useState<IConnection[]>(loadedContext.availableConnections);
|
||||
const [input, setInput] = useInputState(loadedContext.input);
|
||||
const [stories, setStories] = useState(loadedContext.stories);
|
||||
const [currentStory, setCurrentStory] = useState(loadedContext.currentStory);
|
||||
const [systemPrompt, setSystemPrompt] = useInputState(loadedContext.systemPrompt);
|
||||
const [userPrompt, setUserPrompt] = useInputState(loadedContext.userPrompt);
|
||||
const [summarizePrompt, setSummarizePrompt] = useInputState(loadedContext.summarizePrompt);
|
||||
|
|
@ -148,6 +206,13 @@ export const StateContextProvider = ({ children }: { children?: any }) => {
|
|||
const [summaryEnabled, setSummaryEnabled] = useState(loadedContext.summaryEnabled);
|
||||
const [totalSpentKudos, setTotalSpentKudos] = useState(loadedContext.totalSpentKudos);
|
||||
|
||||
const [storiesState, storyDispatch] = useReducer(storyReducer, {
|
||||
stories: loadedContext.stories,
|
||||
currentStory: loadedContext.currentStory,
|
||||
lore: loadedContext.stories[loadedContext.currentStory]?.lore ?? '',
|
||||
messages: loadedContext.stories[loadedContext.currentStory]?.messages ?? [],
|
||||
});
|
||||
|
||||
const connection = availableConnections[currentConnection] ?? DEFAULT_CONTEXT.availableConnections[0];
|
||||
|
||||
const [triggerNext, setTriggerNext] = useState(false);
|
||||
|
|
@ -167,33 +232,16 @@ export const StateContextProvider = ({ children }: { children?: any }) => {
|
|||
useEffect(() => setConnection({ ...connection, instruct }), [instruct]);
|
||||
|
||||
const setLore = useInputCallback((lore) => {
|
||||
if (!currentStory) return;
|
||||
setStories(ss => ({
|
||||
...ss,
|
||||
[currentStory]: {
|
||||
...EMPTY_STORY,
|
||||
...stories[currentStory],
|
||||
lore,
|
||||
}
|
||||
}));
|
||||
}, [currentStory]);
|
||||
storyDispatch({ lore });
|
||||
}, []);
|
||||
|
||||
const setMessages = useCallback((msg: StateUpdater<IMessage[]>) => {
|
||||
if (!currentStory) return;
|
||||
const setMessages = useCallback((messages: StateUpdater<IMessage[]>) => {
|
||||
storyDispatch({ messages });
|
||||
}, []);
|
||||
|
||||
let messages = (typeof msg === 'function')
|
||||
? msg(stories[currentStory]?.messages ?? EMPTY_STORY.messages)
|
||||
: msg;
|
||||
|
||||
setStories(ss => ({
|
||||
...ss,
|
||||
[currentStory]: {
|
||||
...EMPTY_STORY,
|
||||
...stories[currentStory],
|
||||
messages,
|
||||
}
|
||||
}));
|
||||
}, [currentStory]);
|
||||
const setCurrentStory = useCallback((currentStory: StateUpdater<string>) => {
|
||||
storyDispatch({ currentStory });
|
||||
}, []);
|
||||
|
||||
const actions: IActions = useMemo(() => ({
|
||||
setConnection,
|
||||
|
|
@ -285,18 +333,12 @@ export const StateContextProvider = ({ children }: { children?: any }) => {
|
|||
setContinueLast(c);
|
||||
},
|
||||
createStory: (id: string) => {
|
||||
setStories(ss => ({
|
||||
...ss,
|
||||
[id]: { ...EMPTY_STORY }
|
||||
}))
|
||||
storyDispatch({ id, action: 'create' });
|
||||
},
|
||||
deleteStory: (id: string) => {
|
||||
if (id === DEFAULT_STORY) return;
|
||||
|
||||
setStories(ss => Object.fromEntries(Object.entries(ss).filter(([k]) => k !== id)));
|
||||
setCurrentStory(cs => cs === id ? DEFAULT_STORY : cs);
|
||||
storyDispatch({ id, action: 'delete' });
|
||||
}
|
||||
}), [setLore, setMessages]);
|
||||
}), []);
|
||||
|
||||
const rawContext: IContext & IComputableContext = {
|
||||
connection,
|
||||
|
|
@ -309,13 +351,10 @@ export const StateContextProvider = ({ children }: { children?: any }) => {
|
|||
summaryEnabled,
|
||||
bannedWords,
|
||||
totalSpentKudos,
|
||||
stories,
|
||||
currentStory,
|
||||
...storiesState,
|
||||
//
|
||||
triggerNext,
|
||||
continueLast,
|
||||
lore: stories[currentStory]?.lore ?? '',
|
||||
messages: stories[currentStory]?.messages ?? [],
|
||||
};
|
||||
|
||||
const context = useMemo(() => rawContext, Object.values(rawContext));
|
||||
|
|
|
|||
Loading…
Reference in New Issue