223 lines
6.7 KiB
JavaScript
223 lines
6.7 KiB
JavaScript
// ==========================================
|
|
// State Management (Mode-Aware)
|
|
// ==========================================
|
|
// In unmanaged mode: localStorage (same as before)
|
|
// In managed mode: backend API with local cache
|
|
// ==========================================
|
|
|
|
const State = {
|
|
settings: {
|
|
apiEndpoint: '',
|
|
apiKey: '',
|
|
model: 'gpt-3.5-turbo',
|
|
stream: true,
|
|
saveHistory: true,
|
|
showThinking: true,
|
|
systemPrompt: '',
|
|
maxTokens: 4096,
|
|
temperature: 0.7,
|
|
topP: 1,
|
|
presencePenalty: 0
|
|
},
|
|
models: [],
|
|
chats: [],
|
|
currentChatId: null,
|
|
isGenerating: false,
|
|
abortController: null
|
|
};
|
|
|
|
// ── Settings (always localStorage) ──────────
|
|
|
|
function loadSettings() {
|
|
const saved = Storage.get('chatSwitchboard_settings');
|
|
if (saved) {
|
|
State.settings = { ...State.settings, ...saved };
|
|
}
|
|
}
|
|
|
|
function saveSettings() {
|
|
Storage.set('chatSwitchboard_settings', State.settings);
|
|
}
|
|
|
|
// ── Models (always localStorage) ────────────
|
|
|
|
function loadModels() {
|
|
State.models = Storage.get('chatSwitchboard_models', []);
|
|
}
|
|
|
|
function saveModels() {
|
|
Storage.set('chatSwitchboard_models', State.models);
|
|
}
|
|
|
|
// ── 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() {
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
async function updateChat(chatId, updates) {
|
|
const chat = State.chats.find(c => c.id === chatId);
|
|
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();
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
function getChat(chatId) {
|
|
return State.chats.find(c => c.id === 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);
|
|
}
|
|
}
|