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 nextFrame = async (): Promise<number> => new Promise((resolve) => requestAnimationFrame(resolve));
|
||||||
export const delay = async (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
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;
|
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 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 isThrottled = false;
|
||||||
let savedResult: R;
|
let savedResult: R;
|
||||||
let savedThis: T;
|
let savedThis: T;
|
||||||
let savedArgs: A | undefined;
|
let savedArgs: A | undefined;
|
||||||
|
|
||||||
const wrapper: F = function (...args: A) {
|
const wrapper = function (this: T, ...args: A): R {
|
||||||
if (isThrottled) {
|
if (isThrottled) {
|
||||||
savedThis = this;
|
savedThis = this;
|
||||||
savedArgs = args;
|
savedArgs = args;
|
||||||
|
|
@ -99,7 +103,10 @@ export const throttle = function <T, A extends unknown[], R, F extends (this: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
return savedResult;
|
return savedResult;
|
||||||
} as F;
|
};
|
||||||
|
|
||||||
return wrapper;
|
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 { 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 { MessageTools, type IMessage } from "../tools/messages";
|
||||||
import { useInputState } from "@common/hooks/useInputState";
|
import { useInputState } from "@common/hooks/useInputState";
|
||||||
import { type IConnection } from "../tools/connection";
|
import { type IConnection } from "../tools/connection";
|
||||||
import { loadObject, saveObject } from "../tools/storage";
|
import { loadObject, saveObject } from "../tools/storage";
|
||||||
import { useInputCallback } from "@common/hooks/useInputCallback";
|
import { useInputCallback } from "@common/hooks/useInputCallback";
|
||||||
|
import { callUpdater, throttle } from "@common/utils";
|
||||||
|
|
||||||
interface IStory {
|
interface IStory {
|
||||||
lore: string;
|
lore: string;
|
||||||
messages: IMessage[];
|
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 {
|
interface IContext {
|
||||||
currentConnection: number;
|
currentConnection: number;
|
||||||
availableConnections: IConnection[];
|
availableConnections: IConnection[];
|
||||||
|
|
@ -118,7 +145,7 @@ const EMPTY_STORY: IStory = {
|
||||||
messages: [],
|
messages: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveContext = async (context: IContext & IComputableContext) => {
|
const saveContext = throttle(async (context: IContext & IComputableContext) => {
|
||||||
const contextToSave: Partial<IContext & IComputableContext> = { ...context };
|
const contextToSave: Partial<IContext & IComputableContext> = { ...context };
|
||||||
delete contextToSave.connection;
|
delete contextToSave.connection;
|
||||||
delete contextToSave.triggerNext;
|
delete contextToSave.triggerNext;
|
||||||
|
|
@ -127,7 +154,7 @@ const saveContext = async (context: IContext & IComputableContext) => {
|
||||||
delete contextToSave.messages;
|
delete contextToSave.messages;
|
||||||
|
|
||||||
return saveObject(SAVE_KEY, contextToSave);
|
return saveObject(SAVE_KEY, contextToSave);
|
||||||
}
|
}, 1000, true);
|
||||||
|
|
||||||
export type IStateContext = IContext & IActions & IComputableContext;
|
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 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 }) => {
|
export const StateContextProvider = ({ children }: { children?: any }) => {
|
||||||
const [currentConnection, setCurrentConnection] = useState<number>(loadedContext.currentConnection);
|
const [currentConnection, setCurrentConnection] = useState<number>(loadedContext.currentConnection);
|
||||||
const [availableConnections, setAvailableConnections] = useState<IConnection[]>(loadedContext.availableConnections);
|
const [availableConnections, setAvailableConnections] = useState<IConnection[]>(loadedContext.availableConnections);
|
||||||
const [input, setInput] = useInputState(loadedContext.input);
|
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 [systemPrompt, setSystemPrompt] = useInputState(loadedContext.systemPrompt);
|
||||||
const [userPrompt, setUserPrompt] = useInputState(loadedContext.userPrompt);
|
const [userPrompt, setUserPrompt] = useInputState(loadedContext.userPrompt);
|
||||||
const [summarizePrompt, setSummarizePrompt] = useInputState(loadedContext.summarizePrompt);
|
const [summarizePrompt, setSummarizePrompt] = useInputState(loadedContext.summarizePrompt);
|
||||||
|
|
@ -148,6 +206,13 @@ export const StateContextProvider = ({ children }: { children?: any }) => {
|
||||||
const [summaryEnabled, setSummaryEnabled] = useState(loadedContext.summaryEnabled);
|
const [summaryEnabled, setSummaryEnabled] = useState(loadedContext.summaryEnabled);
|
||||||
const [totalSpentKudos, setTotalSpentKudos] = useState(loadedContext.totalSpentKudos);
|
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 connection = availableConnections[currentConnection] ?? DEFAULT_CONTEXT.availableConnections[0];
|
||||||
|
|
||||||
const [triggerNext, setTriggerNext] = useState(false);
|
const [triggerNext, setTriggerNext] = useState(false);
|
||||||
|
|
@ -167,33 +232,16 @@ export const StateContextProvider = ({ children }: { children?: any }) => {
|
||||||
useEffect(() => setConnection({ ...connection, instruct }), [instruct]);
|
useEffect(() => setConnection({ ...connection, instruct }), [instruct]);
|
||||||
|
|
||||||
const setLore = useInputCallback((lore) => {
|
const setLore = useInputCallback((lore) => {
|
||||||
if (!currentStory) return;
|
storyDispatch({ lore });
|
||||||
setStories(ss => ({
|
}, []);
|
||||||
...ss,
|
|
||||||
[currentStory]: {
|
|
||||||
...EMPTY_STORY,
|
|
||||||
...stories[currentStory],
|
|
||||||
lore,
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}, [currentStory]);
|
|
||||||
|
|
||||||
const setMessages = useCallback((msg: StateUpdater<IMessage[]>) => {
|
const setMessages = useCallback((messages: StateUpdater<IMessage[]>) => {
|
||||||
if (!currentStory) return;
|
storyDispatch({ messages });
|
||||||
|
}, []);
|
||||||
|
|
||||||
let messages = (typeof msg === 'function')
|
const setCurrentStory = useCallback((currentStory: StateUpdater<string>) => {
|
||||||
? msg(stories[currentStory]?.messages ?? EMPTY_STORY.messages)
|
storyDispatch({ currentStory });
|
||||||
: msg;
|
}, []);
|
||||||
|
|
||||||
setStories(ss => ({
|
|
||||||
...ss,
|
|
||||||
[currentStory]: {
|
|
||||||
...EMPTY_STORY,
|
|
||||||
...stories[currentStory],
|
|
||||||
messages,
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}, [currentStory]);
|
|
||||||
|
|
||||||
const actions: IActions = useMemo(() => ({
|
const actions: IActions = useMemo(() => ({
|
||||||
setConnection,
|
setConnection,
|
||||||
|
|
@ -285,18 +333,12 @@ export const StateContextProvider = ({ children }: { children?: any }) => {
|
||||||
setContinueLast(c);
|
setContinueLast(c);
|
||||||
},
|
},
|
||||||
createStory: (id: string) => {
|
createStory: (id: string) => {
|
||||||
setStories(ss => ({
|
storyDispatch({ id, action: 'create' });
|
||||||
...ss,
|
|
||||||
[id]: { ...EMPTY_STORY }
|
|
||||||
}))
|
|
||||||
},
|
},
|
||||||
deleteStory: (id: string) => {
|
deleteStory: (id: string) => {
|
||||||
if (id === DEFAULT_STORY) return;
|
storyDispatch({ id, action: 'delete' });
|
||||||
|
|
||||||
setStories(ss => Object.fromEntries(Object.entries(ss).filter(([k]) => k !== id)));
|
|
||||||
setCurrentStory(cs => cs === id ? DEFAULT_STORY : cs);
|
|
||||||
}
|
}
|
||||||
}), [setLore, setMessages]);
|
}), []);
|
||||||
|
|
||||||
const rawContext: IContext & IComputableContext = {
|
const rawContext: IContext & IComputableContext = {
|
||||||
connection,
|
connection,
|
||||||
|
|
@ -309,13 +351,10 @@ export const StateContextProvider = ({ children }: { children?: any }) => {
|
||||||
summaryEnabled,
|
summaryEnabled,
|
||||||
bannedWords,
|
bannedWords,
|
||||||
totalSpentKudos,
|
totalSpentKudos,
|
||||||
stories,
|
...storiesState,
|
||||||
currentStory,
|
|
||||||
//
|
//
|
||||||
triggerNext,
|
triggerNext,
|
||||||
continueLast,
|
continueLast,
|
||||||
lore: stories[currentStory]?.lore ?? '',
|
|
||||||
messages: stories[currentStory]?.messages ?? [],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const context = useMemo(() => rawContext, Object.values(rawContext));
|
const context = useMemo(() => rawContext, Object.values(rawContext));
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue