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();
}, [addSwipe, messages]);
const handleSetBannedWords = useCallback((e: Event) => {
if (e.target instanceof HTMLTextAreaElement) {
const words = e.target.value.split('\n');
setBannedWords(words);
}
const handleSetBannedWords = useInputCallback((text) => {
const words = text.split('\n');
setBannedWords(words);
}, [setBannedWords]);
const handleBlurBannedWords = useCallback((e: Event) => {
if (e.target instanceof HTMLTextAreaElement) {
const words = e.target.value.toLowerCase().split('\n').sort();
setBannedWords(words);
}
const handleBlurBannedWords = useInputCallback((text) => {
const words = text.toLowerCase().split('\n').sort();
setBannedWords(words);
}, [setBannedWords]);
const handleSetSummaryEnabled = useCallback((e: Event) => {
@ -76,21 +72,21 @@ export const Header = () => {
}, [setSummaryEnabled]);
const handleChangeStory = useInputCallback((story) => {
if (story === '@new') {
const id = prompt('Story id');
if (id) {
createStory(id);
setCurrentStory(id);
}
} else {
setCurrentStory(story);
}
setCurrentStory(story);
}, []);
const handleDeleteStory = useCallback(() => {
if (confirm(`Delete story "${currentStory}"?`)) {
deleteStory(currentStory);
}
}
}, [currentStory]);
const handleDuplicateStory = useCallback(() => {
const id = prompt('Story id');
if (id) {
createStory(id, currentStory);
setCurrentStory(id);
}
}, [currentStory]);
return (
@ -137,8 +133,10 @@ export const Header = () => {
{Object.keys(stories).map((story) => (
<option key={story} value={story}>{story}</option>
))}
<option value='@new'>New Story...</option>
</select>
<button class='icon' onClick={handleDuplicateStory}>
</button>
{currentStory !== DEFAULT_STORY
? <button class='icon' onClick={handleDeleteStory}>
🗑

View File

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

View File

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

View File

@ -12,7 +12,7 @@ export const loadObject = async <T>(key: string, defaultObject: T): Promise<T> =
let remoteObject: Partial<T> = {};
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) {
const compressedData = await response.blob();
const decompressedData = await decompressBlob(compressedData);

View File

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