68 lines
1.8 KiB
TypeScript
68 lines
1.8 KiB
TypeScript
|
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||
|
|
import type { Message } from "../types";
|
||
|
|
|
||
|
|
const STORAGE_KEY_PREFIX = "storykit-chat-history:";
|
||
|
|
|
||
|
|
function storageKey(projectPath: string): string {
|
||
|
|
return `${STORAGE_KEY_PREFIX}${projectPath}`;
|
||
|
|
}
|
||
|
|
|
||
|
|
function loadMessages(projectPath: string): Message[] {
|
||
|
|
try {
|
||
|
|
const raw = localStorage.getItem(storageKey(projectPath));
|
||
|
|
if (!raw) return [];
|
||
|
|
const parsed: unknown = JSON.parse(raw);
|
||
|
|
if (!Array.isArray(parsed)) return [];
|
||
|
|
return parsed as Message[];
|
||
|
|
} catch {
|
||
|
|
return [];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function saveMessages(projectPath: string, messages: Message[]): void {
|
||
|
|
try {
|
||
|
|
if (messages.length === 0) {
|
||
|
|
localStorage.removeItem(storageKey(projectPath));
|
||
|
|
} else {
|
||
|
|
localStorage.setItem(storageKey(projectPath), JSON.stringify(messages));
|
||
|
|
}
|
||
|
|
} catch (e) {
|
||
|
|
console.warn("Failed to persist chat history to localStorage:", e);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export function useChatHistory(projectPath: string) {
|
||
|
|
const [messages, setMessagesState] = useState<Message[]>(() =>
|
||
|
|
loadMessages(projectPath),
|
||
|
|
);
|
||
|
|
const projectPathRef = useRef(projectPath);
|
||
|
|
|
||
|
|
// Keep the ref in sync so the effect closure always has the latest path.
|
||
|
|
projectPathRef.current = projectPath;
|
||
|
|
|
||
|
|
// Persist whenever messages change.
|
||
|
|
useEffect(() => {
|
||
|
|
saveMessages(projectPathRef.current, messages);
|
||
|
|
}, [messages]);
|
||
|
|
|
||
|
|
const setMessages = useCallback(
|
||
|
|
(update: Message[] | ((prev: Message[]) => Message[])) => {
|
||
|
|
setMessagesState(update);
|
||
|
|
},
|
||
|
|
[],
|
||
|
|
);
|
||
|
|
|
||
|
|
const clearMessages = useCallback(() => {
|
||
|
|
setMessagesState([]);
|
||
|
|
// Eagerly remove from storage so clearSession doesn't depend on the
|
||
|
|
// effect firing before the component unmounts or re-renders.
|
||
|
|
try {
|
||
|
|
localStorage.removeItem(storageKey(projectPathRef.current));
|
||
|
|
} catch {
|
||
|
|
// Ignore — quota or security errors.
|
||
|
|
}
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
return { messages, setMessages, clearMessages } as const;
|
||
|
|
}
|