1
0
Fork 0

Add story cloning, fix storage caching issue

This commit is contained in:
Pabloader 2026-02-20 13:57:10 +00:00
parent a660ab8161
commit 3f41cea5eb
5 changed files with 36 additions and 57 deletions

View File

@ -55,18 +55,14 @@ export const Header = () => {
assistantOpen.setFalse(); assistantOpen.setFalse();
}, [addSwipe, messages]); }, [addSwipe, messages]);
const handleSetBannedWords = useCallback((e: Event) => { const handleSetBannedWords = useInputCallback((text) => {
if (e.target instanceof HTMLTextAreaElement) { const words = text.split('\n');
const words = e.target.value.split('\n'); setBannedWords(words);
setBannedWords(words);
}
}, [setBannedWords]); }, [setBannedWords]);
const handleBlurBannedWords = useCallback((e: Event) => { const handleBlurBannedWords = useInputCallback((text) => {
if (e.target instanceof HTMLTextAreaElement) { const words = text.toLowerCase().split('\n').sort();
const words = e.target.value.toLowerCase().split('\n').sort(); setBannedWords(words);
setBannedWords(words);
}
}, [setBannedWords]); }, [setBannedWords]);
const handleSetSummaryEnabled = useCallback((e: Event) => { const handleSetSummaryEnabled = useCallback((e: Event) => {
@ -76,15 +72,7 @@ export const Header = () => {
}, [setSummaryEnabled]); }, [setSummaryEnabled]);
const handleChangeStory = useInputCallback((story) => { const handleChangeStory = useInputCallback((story) => {
if (story === '@new') { setCurrentStory(story);
const id = prompt('Story id');
if (id) {
createStory(id);
setCurrentStory(id);
}
} else {
setCurrentStory(story);
}
}, []); }, []);
const handleDeleteStory = useCallback(() => { const handleDeleteStory = useCallback(() => {
@ -93,6 +81,14 @@ export const Header = () => {
} }
}, [currentStory]); }, [currentStory]);
const handleDuplicateStory = useCallback(() => {
const id = prompt('Story id');
if (id) {
createStory(id, currentStory);
setCurrentStory(id);
}
}, [currentStory]);
return ( return (
<div class={styles.header}> <div class={styles.header}>
<div class={styles.inputs}> <div class={styles.inputs}>
@ -137,8 +133,10 @@ export const Header = () => {
{Object.keys(stories).map((story) => ( {Object.keys(stories).map((story) => (
<option key={story} value={story}>{story}</option> <option key={story} value={story}>{story}</option>
))} ))}
<option value='@new'>New Story...</option>
</select> </select>
<button class='icon' onClick={handleDuplicateStory}>
</button>
{currentStory !== DEFAULT_STORY {currentStory !== DEFAULT_STORY
? <button class='icon' onClick={handleDeleteStory}> ? <button class='icon' onClick={handleDeleteStory}>
🗑 🗑

View File

@ -101,9 +101,9 @@ export const LLMContextProvider = ({ children }: { children?: any }) => {
let content = swipe?.content ?? ''; let content = swipe?.content ?? '';
if (role === 'user' && usersRemaining > keepUsers) { if (role === 'user' && usersRemaining > keepUsers) {
usersRemaining--; usersRemaining--;
} else if (role === 'assistant' && templateMessages.at(-1).role === 'assistant') { } else if (role === 'assistant' && templateMessages.at(-1)!.role === 'assistant') {
wasStory = true; wasStory = true;
templateMessages.at(-1).content += '\n\n' + content; templateMessages.at(-1)!.content += '\n\n' + content;
} else if (role === 'user' && !message.technical) { } else if (role === 'user' && !message.technical) {
templateMessages.push({ templateMessages.push({
role: message.role, role: message.role,

View File

@ -35,13 +35,13 @@ interface StoryActionSetLore {
interface StoryActionWithId { interface StoryActionWithId {
action: 'create' | 'delete'; action: 'create' | 'delete';
id: string; id: string;
fromId?: string;
} }
type StoryAction = StoryActionSetCurrent | StoryActionSetLore | StoryActionSetMessages | StoryActionWithId; type StoryAction = StoryActionSetCurrent | StoryActionSetLore | StoryActionSetMessages | StoryActionWithId;
interface IContext { interface IContext {
currentConnection: number; connection: IConnection;
availableConnections: IConnection[];
input: string; input: string;
systemPrompt: string; systemPrompt: string;
userPrompt: string; userPrompt: string;
@ -57,15 +57,12 @@ interface IContext {
} }
interface IComputableContext { interface IComputableContext {
connection: IConnection;
lore: string; lore: string;
messages: IMessage[]; messages: IMessage[];
} }
interface IActions { interface IActions {
setConnection: (connection: IConnection) => void; setConnection: (connection: IConnection) => void;
setAvailableConnections: (connections: IConnection[]) => void;
setCurrentConnection: (connection: number) => void;
setInput: (url: string | Event) => void; setInput: (url: string | Event) => void;
setInstruct: (template: string | Event) => void; setInstruct: (template: string | Event) => void;
setLore: (lore: string | Event) => void; setLore: (lore: string | Event) => void;
@ -90,7 +87,7 @@ interface IActions {
continueMessage: (continueLast?: boolean) => void; continueMessage: (continueLast?: boolean) => void;
setCurrentStory: (id: string) => void; setCurrentStory: (id: string) => void;
createStory: (id: string) => void; createStory: (id: string, fromId?: string) => void;
deleteStory: (id: string) => void; deleteStory: (id: string) => void;
} }
@ -119,12 +116,11 @@ export const INSTRUCTS = {
} }
const DEFAULT_CONTEXT: IContext = { const DEFAULT_CONTEXT: IContext = {
currentConnection: 0, connection: {
availableConnections: [{
type: 'kobold', type: 'kobold',
url: 'http://localhost:5001', url: 'http://localhost:5001',
instruct: INSTRUCT_MISTRAL, instruct: INSTRUCT_MISTRAL,
}], },
input: '', input: '',
systemPrompt: 'You are a creative writer. Write a story based on the world description below. Story should be adult and mature; and could include swearing, violence and unfairness. Portray characters realistically and stay in the lore.', systemPrompt: 'You are a creative writer. Write a story based on the world description below. Story should be adult and mature; and could include swearing, violence and unfairness. Portray characters realistically and stay in the lore.',
stories: {}, stories: {},
@ -155,7 +151,6 @@ const EMPTY_STORY: IStory = {
const saveContext = throttle(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.triggerNext; delete contextToSave.triggerNext;
delete contextToSave.continueLast; delete contextToSave.continueLast;
delete contextToSave.lore; delete contextToSave.lore;
@ -176,7 +171,10 @@ const storyReducer = (state: StoriesState, action: StoryAction): StoriesState =>
if ('id' in action) { if ('id' in action) {
switch (action.action) { switch (action.action) {
case 'create': case 'create':
stories[action.id] = EMPTY_STORY; stories[action.id] = {
...EMPTY_STORY,
lore: (action.fromId && stories[action.fromId]?.lore) ?? EMPTY_STORY.lore,
};
break; break;
case 'delete': case 'delete':
if (action.id !== DEFAULT_STORY) { if (action.id !== DEFAULT_STORY) {
@ -204,8 +202,7 @@ const storyReducer = (state: StoriesState, action: StoryAction): StoriesState =>
}; };
export const StateContextProvider = ({ children }: { children?: any }) => { export const StateContextProvider = ({ children }: { children?: any }) => {
const [currentConnection, setCurrentConnection] = useState<number>(loadedContext.currentConnection); const [connection, setConnection] = useState<IConnection>(loadedContext.connection);
const [availableConnections, setAvailableConnections] = useState<IConnection[]>(loadedContext.availableConnections);
const [input, setInput] = useInputState(loadedContext.input); const [input, setInput] = useInputState(loadedContext.input);
const [systemPrompt, setSystemPrompt] = useInputState(loadedContext.systemPrompt); const [systemPrompt, setSystemPrompt] = useInputState(loadedContext.systemPrompt);
const [userPrompt, setUserPrompt] = useInputState(loadedContext.userPrompt); const [userPrompt, setUserPrompt] = useInputState(loadedContext.userPrompt);
@ -221,22 +218,10 @@ export const StateContextProvider = ({ children }: { children?: any }) => {
messages: loadedContext.stories[loadedContext.currentStory]?.messages ?? [], messages: loadedContext.stories[loadedContext.currentStory]?.messages ?? [],
}); });
const connection = availableConnections[currentConnection] ?? DEFAULT_CONTEXT.availableConnections[0];
const [triggerNext, setTriggerNext] = useState(false); const [triggerNext, setTriggerNext] = useState(false);
const [continueLast, setContinueLast] = useState(false); const [continueLast, setContinueLast] = useState(false);
const [instruct, setInstruct] = useInputState(connection.instruct); const [instruct, setInstruct] = useInputState(connection.instruct);
const setConnection = useCallback((c: IConnection) => {
setAvailableConnections(availableConnections.map((ac, ai) => {
if (ai === currentConnection) {
return c;
} else {
return ac;
}
}));
}, [availableConnections, currentConnection]);
useEffect(() => setConnection({ ...connection, instruct }), [instruct]); useEffect(() => setConnection({ ...connection, instruct }), [instruct]);
const setLore = useInputCallback((lore) => { const setLore = useInputCallback((lore) => {
@ -253,7 +238,6 @@ export const StateContextProvider = ({ children }: { children?: any }) => {
const actions: IActions = useMemo(() => ({ const actions: IActions = useMemo(() => ({
setConnection, setConnection,
setCurrentConnection,
setInput, setInput,
setInstruct, setInstruct,
setSystemPrompt, setSystemPrompt,
@ -268,7 +252,6 @@ export const StateContextProvider = ({ children }: { children?: any }) => {
setCurrentStory, setCurrentStory,
setBannedWords: (words) => setBannedWords(words.slice()), setBannedWords: (words) => setBannedWords(words.slice()),
setAvailableConnections: (connections) => setAvailableConnections(connections.slice()),
setMessages: (newMessages) => setMessages(newMessages.slice()), setMessages: (newMessages) => setMessages(newMessages.slice()),
addMessage: (content, role, triggerNext = false) => { addMessage: (content, role, triggerNext = false) => {
@ -297,7 +280,7 @@ export const StateContextProvider = ({ children }: { children?: any }) => {
const swipes = message.swipes.slice(); const swipes = message.swipes.slice();
const latestSwipe = swipes.at(-1); const latestSwipe = swipes.at(-1);
if (currentSwipe >= swipes.length) { if (currentSwipe >= swipes.length) {
if (latestSwipe.content.length > 0) { if (latestSwipe && latestSwipe.content.length > 0) {
currentSwipe = swipes.length; currentSwipe = swipes.length;
swipes.push({ content: '', cost: 0 }); swipes.push({ content: '', cost: 0 });
} else { } else {
@ -340,8 +323,8 @@ export const StateContextProvider = ({ children }: { children?: any }) => {
setTriggerNext(true); setTriggerNext(true);
setContinueLast(c); setContinueLast(c);
}, },
createStory: (id: string) => { createStory: (id: string, fromId?: string) => {
storyDispatch({ id, action: 'create' }); storyDispatch({ id, action: 'create', fromId });
}, },
deleteStory: (id: string) => { deleteStory: (id: string) => {
storyDispatch({ id, action: 'delete' }); storyDispatch({ id, action: 'delete' });
@ -350,8 +333,6 @@ export const StateContextProvider = ({ children }: { children?: any }) => {
const rawContext: IContext & IComputableContext = { const rawContext: IContext & IComputableContext = {
connection, connection,
currentConnection,
availableConnections,
input, input,
systemPrompt, systemPrompt,
userPrompt, userPrompt,

View File

@ -12,7 +12,7 @@ export const loadObject = async <T>(key: string, defaultObject: T): Promise<T> =
let remoteObject: Partial<T> = {}; let remoteObject: Partial<T> = {};
try { try {
const response = await fetch(`https://demo.pabloader.ru/storage/${key}`); const response = await fetch(`https://demo.pabloader.ru/storage/${key}?_=${Math.random()}`);
if (response.ok) { if (response.ok) {
const compressedData = await response.blob(); const compressedData = await response.blob();
const decompressedData = await decompressBlob(compressedData); const decompressedData = await decompressBlob(compressedData);

View File

@ -28,6 +28,6 @@
"@common/*": ["./src/common/*"] "@common/*": ["./src/common/*"]
} }
}, },
"include": ["./**/*.ts", "./**/*.tsx", "./**/*.js", "./**/*.jsx", "./node_modules/assemblyscript/std/portable/index.d.ts"], "include": ["./**/*.ts", "./**/*.tsx", "./**/*.js", "./**/*.jsx"],
"exclude": ["./dist/**/*"] "exclude": ["./dist/**/*"]
} }