Changeset 0.12.0 (#63)
This commit is contained in:
@@ -64,7 +64,8 @@ async function loadChats() {
|
||||
model: c.model || '',
|
||||
messageCount: c.message_count || 0,
|
||||
messages: [],
|
||||
updatedAt: c.updated_at
|
||||
updatedAt: c.updated_at,
|
||||
settings: c.settings || {},
|
||||
}));
|
||||
} catch (e) {
|
||||
console.error('Failed to load chats:', e.message);
|
||||
@@ -73,6 +74,7 @@ async function loadChats() {
|
||||
}
|
||||
|
||||
async function selectChat(chatId) {
|
||||
clearStaged(); // Discard any staged attachments from previous chat
|
||||
App.currentChatId = chatId;
|
||||
UI.renderChatList();
|
||||
if (window.innerWidth <= 768) {
|
||||
@@ -109,6 +111,9 @@ async function selectChat(chatId) {
|
||||
// Restore the last-used model/preset for this chat
|
||||
_restoreChatModel(chatId, chat);
|
||||
|
||||
// Load attachments for message rendering (non-blocking, best-effort)
|
||||
await loadChannelAttachments(chatId);
|
||||
|
||||
UI.renderMessages(chat.messages);
|
||||
UI.showRegenerate(chat.messages.some(m => m.role === 'assistant'));
|
||||
Tokens._warningDismissed = false;
|
||||
@@ -117,46 +122,63 @@ async function selectChat(chatId) {
|
||||
}
|
||||
|
||||
// ── Per-Chat Model/Preset Persistence ────────
|
||||
// BANDAID: localStorage — doesn't roam across devices.
|
||||
// TODO: move to channel.settings.last_selector_id (server-side). See ROADMAP TBD.
|
||||
// Server-side: saved in channel.settings.last_selector_id (JSONB merge).
|
||||
// localStorage used as write-through cache for instant restore without
|
||||
// waiting for the channel list API call.
|
||||
|
||||
function _saveChatModel(chatId) {
|
||||
if (!chatId) return;
|
||||
const selectorId = UI.getModelValue();
|
||||
if (!selectorId) return;
|
||||
|
||||
// Write-through: localStorage for instant restore on next visit
|
||||
try {
|
||||
const map = JSON.parse(localStorage.getItem('cs-chat-models') || '{}');
|
||||
map[chatId] = selectorId;
|
||||
localStorage.setItem('cs-chat-models', JSON.stringify(map));
|
||||
} catch (e) { /* quota exceeded, etc */ }
|
||||
|
||||
// Update in-memory chat object
|
||||
const chat = App.chats.find(c => c.id === chatId);
|
||||
if (chat) {
|
||||
if (!chat.settings) chat.settings = {};
|
||||
chat.settings.last_selector_id = selectorId;
|
||||
}
|
||||
|
||||
// Persist to server (fire-and-forget — non-critical)
|
||||
API.updateChannel(chatId, { settings: { last_selector_id: selectorId } })
|
||||
.catch(e => console.debug('Failed to persist model selection:', e.message));
|
||||
}
|
||||
|
||||
function _restoreChatModel(chatId, chat) {
|
||||
// Priority: stored selector ID → channel base model → keep current
|
||||
// Priority: server settings → localStorage fallback → channel base model → keep current
|
||||
let targetId = null;
|
||||
|
||||
// 1. Check localStorage for exact selector ID (handles presets)
|
||||
try {
|
||||
const map = JSON.parse(localStorage.getItem('cs-chat-models') || '{}');
|
||||
if (map[chatId]) targetId = map[chatId];
|
||||
} catch (e) { /* */ }
|
||||
// 1. Server-side settings (most authoritative, roams across devices)
|
||||
if (chat?.settings?.last_selector_id) {
|
||||
targetId = chat.settings.last_selector_id;
|
||||
}
|
||||
|
||||
// 2. Verify the stored ID still exists in available models
|
||||
// 2. localStorage fallback (covers race where settings haven't loaded yet)
|
||||
if (!targetId) {
|
||||
try {
|
||||
const map = JSON.parse(localStorage.getItem('cs-chat-models') || '{}');
|
||||
if (map[chatId]) targetId = map[chatId];
|
||||
} catch (e) { /* */ }
|
||||
}
|
||||
|
||||
// 3. Verify the stored ID still exists in available models
|
||||
if (targetId) {
|
||||
const found = App.models.find(m => m.id === targetId);
|
||||
if (found) {
|
||||
UI.setModelValue(found.id, found.name + (found.provider ? ` (${found.provider})` : ''));
|
||||
return;
|
||||
}
|
||||
// Stored model removed — clean up stale entry
|
||||
try {
|
||||
const map = JSON.parse(localStorage.getItem('cs-chat-models') || '{}');
|
||||
delete map[chatId];
|
||||
localStorage.setItem('cs-chat-models', JSON.stringify(map));
|
||||
} catch (e) { /* */ }
|
||||
// Stored model removed — clean up stale entries
|
||||
_cleanStaleChatModel(chatId);
|
||||
}
|
||||
|
||||
// 3. Fall back to channel's base model ID (match by baseModelId)
|
||||
// 4. Fall back to channel's base model ID (match by baseModelId)
|
||||
if (chat?.model) {
|
||||
const match = App.models.find(m => m.baseModelId === chat.model && !m.isPreset && !m.hidden);
|
||||
if (match) {
|
||||
@@ -165,7 +187,7 @@ function _restoreChatModel(chatId, chat) {
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Stored model gone — fall back to admin default → first visible
|
||||
// 5. Stored model gone — fall back to admin default → first visible
|
||||
const visible = App.models.filter(m => !m.hidden);
|
||||
const def = App.defaultModel
|
||||
? visible.find(m => m.id === App.defaultModel || m.baseModelId === App.defaultModel)
|
||||
@@ -176,7 +198,22 @@ function _restoreChatModel(chatId, chat) {
|
||||
}
|
||||
}
|
||||
|
||||
function _cleanStaleChatModel(chatId) {
|
||||
// Remove from localStorage
|
||||
try {
|
||||
const map = JSON.parse(localStorage.getItem('cs-chat-models') || '{}');
|
||||
delete map[chatId];
|
||||
localStorage.setItem('cs-chat-models', JSON.stringify(map));
|
||||
} catch (e) { /* */ }
|
||||
// Clear from in-memory settings (server cleanup is fire-and-forget)
|
||||
const chat = App.chats.find(c => c.id === chatId);
|
||||
if (chat?.settings?.last_selector_id) {
|
||||
delete chat.settings.last_selector_id;
|
||||
}
|
||||
}
|
||||
|
||||
async function newChat() {
|
||||
clearStaged(); // Discard any staged attachments
|
||||
App.currentChatId = null;
|
||||
UI.renderChatList();
|
||||
UI.showEmptyState();
|
||||
@@ -213,11 +250,17 @@ async function deleteChat(chatId) {
|
||||
async function sendMessage() {
|
||||
const input = document.getElementById('messageInput');
|
||||
const text = input.value.trim();
|
||||
if (!text || App.isGenerating) return;
|
||||
const hasAttachments = hasStagedAttachments();
|
||||
|
||||
// Need text or attachments, not generating, not blocked by uploads
|
||||
if ((!text && !hasAttachments) || App.isGenerating || isSendBlocked()) return;
|
||||
|
||||
input.value = '';
|
||||
input.style.height = 'auto';
|
||||
|
||||
// Snapshot attachment IDs before clearing staged state
|
||||
const attachmentIds = getStagedAttachmentIds();
|
||||
|
||||
const selectedId = UI.getModelValue();
|
||||
const modelInfo = App.findModel(selectedId);
|
||||
// For presets, send the base model ID; for regular models, send the model ID
|
||||
@@ -239,15 +282,19 @@ async function sendMessage() {
|
||||
if (!chat) return;
|
||||
|
||||
// Optimistic: show user message immediately
|
||||
chat.messages.push({ role: 'user', content: text, timestamp: new Date().toISOString(), siblingCount: 1, siblingIndex: 0 });
|
||||
const userContent = text || (hasAttachments ? `[${attachmentIds.length} file${attachmentIds.length > 1 ? 's' : ''} attached]` : '');
|
||||
chat.messages.push({ role: 'user', content: userContent, timestamp: new Date().toISOString(), siblingCount: 1, siblingIndex: 0 });
|
||||
UI.renderMessages(chat.messages);
|
||||
|
||||
// Clear staged attachments (they're now part of this message)
|
||||
if (hasAttachments) consumeStaged();
|
||||
|
||||
App.isGenerating = true;
|
||||
App.abortController = new AbortController();
|
||||
UI.setGenerating(true);
|
||||
|
||||
try {
|
||||
const resp = await API.streamCompletion(App.currentChatId, text, model, App.abortController.signal, modelInfo?.configId, presetId);
|
||||
const resp = await API.streamCompletion(App.currentChatId, text, model, App.abortController.signal, modelInfo?.configId, presetId, attachmentIds);
|
||||
await UI.streamResponse(resp, chat.messages, modelInfo?.name);
|
||||
|
||||
// Reload active path from server to get proper IDs and sibling metadata
|
||||
@@ -307,6 +354,7 @@ async function reloadActivePath() {
|
||||
siblingIndex: m.sibling_index || 0,
|
||||
}));
|
||||
chat.messageCount = chat.messages.length;
|
||||
await loadChannelAttachments(App.currentChatId);
|
||||
UI.renderMessages(chat.messages);
|
||||
updateContextWarning();
|
||||
} catch (e) {
|
||||
|
||||
Reference in New Issue
Block a user