Mode switching (#10) (#26)

This commit is contained in:
2026-02-16 11:30:46 +00:00
parent 4e5b873087
commit b7731bd83f
8 changed files with 771 additions and 83 deletions

View File

@@ -1,5 +1,8 @@
// ==========================================
// State Management
// State Management (Mode-Aware)
// ==========================================
// In unmanaged mode: localStorage (same as before)
// In managed mode: backend API with local cache
// ==========================================
const State = {
@@ -23,6 +26,8 @@ const State = {
abortController: null
};
// ── Settings (always localStorage) ──────────
function loadSettings() {
const saved = Storage.get('chatSwitchboard_settings');
if (saved) {
@@ -34,6 +39,8 @@ function saveSettings() {
Storage.set('chatSwitchboard_settings', State.settings);
}
// ── Models (always localStorage) ────────────
function loadModels() {
State.models = Storage.get('chatSwitchboard_models', []);
}
@@ -42,42 +49,130 @@ function saveModels() {
Storage.set('chatSwitchboard_models', State.models);
}
function loadChats() {
State.chats = Storage.get('chatSwitchboard_chats', []);
// ── Chats ───────────────────────────────────
async function loadChats() {
if (Backend.isManaged) {
try {
const resp = await Backend.listChats(1, 100);
State.chats = (resp.data || []).map(c => ({
id: c.id,
title: c.title,
model: c.model || '',
systemPrompt: c.system_prompt || '',
isArchived: c.is_archived,
isPinned: c.is_pinned,
messageCount: c.message_count || 0,
messages: [], // loaded on demand
createdAt: c.created_at,
updatedAt: c.updated_at
}));
} catch (e) {
console.error('Failed to load chats from backend:', e);
showToast('⚠️ Failed to load chats', 'error');
}
} else {
State.chats = Storage.get('chatSwitchboard_chats', []);
}
}
function saveChats() {
if (State.settings.saveHistory) {
// In managed mode, individual operations handle persistence.
// In unmanaged mode, write to localStorage.
if (!Backend.isManaged && State.settings.saveHistory) {
Storage.set('chatSwitchboard_chats', State.chats);
}
}
function createChat(title, messages) {
const chat = {
id: Date.now().toString(),
title,
messages,
model: State.settings.model,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
State.chats.unshift(chat);
if (State.chats.length > 100) {
State.chats = State.chats.slice(0, 100);
async function createChat(title, messages) {
if (Backend.isManaged) {
try {
const resp = await Backend.createChat(
title,
State.settings.model,
State.settings.systemPrompt
);
const chat = {
id: resp.id,
title: resp.title,
model: resp.model || '',
systemPrompt: resp.system_prompt || '',
messages: messages || [],
messageCount: 0,
createdAt: resp.created_at,
updatedAt: resp.updated_at
};
State.chats.unshift(chat);
// Persist any initial messages (system prompt, first user msg)
for (const msg of (messages || [])) {
await Backend.createMessage(chat.id, msg.role, msg.content, msg.model);
}
chat.messageCount = (messages || []).length;
return chat;
} catch (e) {
console.error('Failed to create chat:', e);
showToast('⚠️ Failed to create chat', 'error');
return null;
}
} else {
const chat = {
id: Date.now().toString(),
title,
messages: messages || [],
model: State.settings.model,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
State.chats.unshift(chat);
if (State.chats.length > 100) {
State.chats = State.chats.slice(0, 100);
}
saveChats();
return chat;
}
saveChats();
return chat;
}
function updateChat(chatId, updates) {
async function updateChat(chatId, updates) {
const chat = State.chats.find(c => c.id === chatId);
if (chat) {
if (!chat) return;
if (Backend.isManaged) {
// Separate backend-updateable fields from local-only fields
const backendUpdates = {};
if (updates.title !== undefined) backendUpdates.title = updates.title;
if (updates.model !== undefined) backendUpdates.model = updates.model;
if (updates.is_archived !== undefined) backendUpdates.is_archived = updates.is_archived;
if (updates.is_pinned !== undefined) backendUpdates.is_pinned = updates.is_pinned;
if (Object.keys(backendUpdates).length > 0) {
try {
await Backend.updateChat(chatId, backendUpdates);
} catch (e) {
console.error('Failed to update chat:', e);
}
}
// Update local cache
Object.assign(chat, updates, { updatedAt: new Date().toISOString() });
} else {
Object.assign(chat, updates, { updatedAt: new Date().toISOString() });
saveChats();
}
}
function deleteChat(chatId) {
async function deleteChat(chatId) {
if (Backend.isManaged) {
try {
await Backend.deleteChat(chatId);
} catch (e) {
console.error('Failed to delete chat:', e);
showToast('⚠️ Failed to delete chat', 'error');
return;
}
}
State.chats = State.chats.filter(c => c.id !== chatId);
saveChats();
}
@@ -89,3 +184,39 @@ function getChat(chatId) {
function getCurrentChat() {
return State.currentChatId ? getChat(State.currentChatId) : null;
}
// ── Messages (managed mode) ─────────────────
async function loadMessages(chatId) {
if (!Backend.isManaged) return;
const chat = getChat(chatId);
if (!chat) return;
// Skip if already loaded
if (chat.messages && chat.messages.length > 0) return;
try {
const resp = await Backend.listMessages(chatId, 1, 200);
chat.messages = (resp.data || []).map(m => ({
role: m.role,
content: m.content,
model: m.model || '',
timestamp: m.created_at,
_backendId: m.id
}));
} catch (e) {
console.error('Failed to load messages:', e);
showToast('⚠️ Failed to load messages', 'error');
}
}
async function persistMessage(chatId, role, content, model) {
if (!Backend.isManaged) return;
try {
await Backend.createMessage(chatId, role, content, model);
} catch (e) {
console.error('Failed to persist message:', e);
}
}