CI / checks (push) Has been cancelled
Cap get_api_docs, break duplicate tool rounds, force a reply after successful writes, and log each tool call so limit hits are diagnosable.
78 lines
2.3 KiB
TypeScript
78 lines
2.3 KiB
TypeScript
import type { ClientChatMessage } from "./messages";
|
|
|
|
export type AssistantChatMessage = {
|
|
role: "user" | "assistant";
|
|
content: string;
|
|
imageUrl?: string;
|
|
toolCalls?: Array<{ name: string; status: number }>;
|
|
};
|
|
|
|
const STORAGE_VERSION = "v2";
|
|
const MAX_MESSAGES = 40;
|
|
|
|
function storageKey(userId: string) {
|
|
return `assistant-chat:${STORAGE_VERSION}:${userId}`;
|
|
}
|
|
|
|
function isValidMessage(value: unknown): value is AssistantChatMessage {
|
|
if (!value || typeof value !== "object") return false;
|
|
const row = value as Record<string, unknown>;
|
|
if (row.role !== "user" && row.role !== "assistant") return false;
|
|
if (typeof row.content !== "string" || row.content.trim().length === 0) return false;
|
|
if (row.imageUrl !== undefined && typeof row.imageUrl !== "string") return false;
|
|
if (row.toolCalls !== undefined) {
|
|
if (!Array.isArray(row.toolCalls)) return false;
|
|
for (const call of row.toolCalls) {
|
|
if (!call || typeof call !== "object") return false;
|
|
const entry = call as Record<string, unknown>;
|
|
if (typeof entry.name !== "string" || typeof entry.status !== "number") return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
export function toClientChatMessage(message: AssistantChatMessage): ClientChatMessage {
|
|
if (!message.imageUrl) {
|
|
return { role: message.role, content: message.content };
|
|
}
|
|
|
|
return {
|
|
role: message.role,
|
|
content: message.content,
|
|
attachments: [{ type: "image", url: message.imageUrl }],
|
|
};
|
|
}
|
|
|
|
export function loadAssistantChat(userId: string): AssistantChatMessage[] {
|
|
try {
|
|
const raw = localStorage.getItem(storageKey(userId));
|
|
if (!raw) return [];
|
|
const parsed: unknown = JSON.parse(raw);
|
|
if (!Array.isArray(parsed)) return [];
|
|
return parsed.filter(isValidMessage).slice(-MAX_MESSAGES);
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export function saveAssistantChat(userId: string, messages: AssistantChatMessage[]): void {
|
|
try {
|
|
const trimmed = messages.slice(-MAX_MESSAGES);
|
|
if (trimmed.length === 0) {
|
|
localStorage.removeItem(storageKey(userId));
|
|
return;
|
|
}
|
|
localStorage.setItem(storageKey(userId), JSON.stringify(trimmed));
|
|
} catch {
|
|
// Private browsing, quota exceeded, or disabled storage.
|
|
}
|
|
}
|
|
|
|
export function clearAssistantChat(userId: string): void {
|
|
try {
|
|
localStorage.removeItem(storageKey(userId));
|
|
} catch {
|
|
// Ignore storage errors.
|
|
}
|
|
}
|