Changeset 0.17.0 (#75)
This commit is contained in:
118
src/js/chat.js
118
src/js/chat.js
@@ -38,7 +38,7 @@ async function summarizeAndContinue() {
|
||||
siblingIndex: m.sibling_index || 0,
|
||||
}));
|
||||
UI.renderMessages(chat.messages);
|
||||
updateContextWarning();
|
||||
updateContextWarning(); updateChatTokenCount();
|
||||
updateInputTokens();
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -96,6 +96,7 @@ async function loadChats() {
|
||||
async function selectChat(chatId) {
|
||||
clearStaged(); // Discard any staged attachments from previous chat
|
||||
App.currentChatId = chatId;
|
||||
try { sessionStorage.setItem('cs-active-chat', chatId); } catch (_) {}
|
||||
UI.renderChatList();
|
||||
if (window.innerWidth <= 768) {
|
||||
document.getElementById('sidebar').classList.add('collapsed');
|
||||
@@ -137,11 +138,32 @@ async function selectChat(chatId) {
|
||||
UI.renderMessages(chat.messages);
|
||||
UI.showRegenerate(chat.messages.some(m => m.role === 'assistant'));
|
||||
Tokens._warningDismissed = false;
|
||||
updateContextWarning();
|
||||
updateContextWarning(); updateChatTokenCount();
|
||||
updateInputTokens();
|
||||
|
||||
// Refresh KB toggle state for the new channel
|
||||
if (typeof KnowledgeUI !== 'undefined') KnowledgeUI.onChatChanged();
|
||||
updateChatTokenCount();
|
||||
}
|
||||
|
||||
// ── Chat Header Token Count ──────────────────
|
||||
|
||||
function updateChatTokenCount() {
|
||||
const el = document.getElementById('chatTokenCount');
|
||||
if (!el) return;
|
||||
const chat = App.chats.find(c => c.id === App.currentChatId);
|
||||
if (!chat || !chat.messages.length) { el.textContent = ''; return; }
|
||||
|
||||
const tokens = Tokens.estimateConversation(chat.messages, App.settings?.systemPrompt);
|
||||
const budget = Tokens.getContextBudget();
|
||||
if (budget.maxContext > 0) {
|
||||
const pct = tokens / budget.maxContext;
|
||||
el.textContent = `${Tokens.format(tokens)} / ${Tokens.format(budget.maxContext)}`;
|
||||
el.className = 'chat-token-count' + (pct > 0.9 ? ' danger' : pct > 0.75 ? ' warning' : '');
|
||||
} else {
|
||||
el.textContent = `~${Tokens.format(tokens)}`;
|
||||
el.className = 'chat-token-count';
|
||||
}
|
||||
}
|
||||
|
||||
// ── Per-Chat Model/Preset Persistence ────────
|
||||
@@ -242,7 +264,7 @@ async function newChat() {
|
||||
UI.showEmptyState();
|
||||
UI.showRegenerate(false);
|
||||
Tokens._warningDismissed = false;
|
||||
updateContextWarning();
|
||||
updateContextWarning(); updateChatTokenCount();
|
||||
updateInputTokens();
|
||||
document.getElementById('messageInput').focus();
|
||||
if (window.innerWidth <= 768) {
|
||||
@@ -269,6 +291,89 @@ async function deleteChat(chatId) {
|
||||
} catch (e) { UI.toast('Failed to delete: ' + e.message, 'error'); }
|
||||
}
|
||||
|
||||
// ── Auto-Name Chat (utility model) ───────────
|
||||
|
||||
let _autoNamePending = new Set(); // debounce: one inflight per chat
|
||||
|
||||
async function autoNameChat(chatId, chat) {
|
||||
if (!chatId || !chat) return;
|
||||
if (_autoNamePending.has(chatId)) return;
|
||||
|
||||
// Only auto-name if title looks auto-generated (first 50 chars of user msg or "New Chat")
|
||||
const firstUserMsg = chat.messages.find(m => m.role === 'user');
|
||||
if (!firstUserMsg) return;
|
||||
const truncated = firstUserMsg.content.slice(0, 50).trim();
|
||||
const currentTitle = (chat.title || '').trim();
|
||||
const isAutoGenerated = !currentTitle ||
|
||||
currentTitle === 'New Chat' ||
|
||||
currentTitle === truncated ||
|
||||
truncated.startsWith(currentTitle);
|
||||
if (!isAutoGenerated) return;
|
||||
|
||||
_autoNamePending.add(chatId);
|
||||
try {
|
||||
const resp = await API.generateTitle(chatId);
|
||||
if (resp?.title && resp.title !== chat.title) {
|
||||
chat.title = resp.title;
|
||||
UI.renderChatList();
|
||||
}
|
||||
} catch (e) {
|
||||
// Silently fail — keep truncated title
|
||||
console.debug('Auto-name failed:', e.message);
|
||||
} finally {
|
||||
_autoNamePending.delete(chatId);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Chat Rename (inline edit) ────────────────
|
||||
|
||||
function startRenameChat(chatId) {
|
||||
const chat = App.chats.find(c => c.id === chatId);
|
||||
if (!chat) return;
|
||||
|
||||
// Find the title span for this chat
|
||||
const items = document.querySelectorAll('.chat-item');
|
||||
let titleSpan = null;
|
||||
for (const item of items) {
|
||||
if (item.getAttribute('onclick')?.includes(chatId)) {
|
||||
titleSpan = item.querySelector('.chat-item-title');
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!titleSpan) return;
|
||||
|
||||
// Replace span with input
|
||||
const input = document.createElement('input');
|
||||
input.type = 'text';
|
||||
input.className = 'chat-rename-input';
|
||||
input.value = chat.title || '';
|
||||
input.onclick = (e) => e.stopPropagation();
|
||||
input.ondblclick = (e) => e.stopPropagation();
|
||||
|
||||
const commit = async () => {
|
||||
const newTitle = input.value.trim();
|
||||
if (newTitle && newTitle !== chat.title) {
|
||||
try {
|
||||
await API.updateChannel(chatId, { title: newTitle });
|
||||
chat.title = newTitle;
|
||||
} catch (e) {
|
||||
UI.toast('Rename failed: ' + e.message, 'error');
|
||||
}
|
||||
}
|
||||
UI.renderChatList();
|
||||
};
|
||||
|
||||
input.addEventListener('blur', commit);
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') { e.preventDefault(); input.blur(); }
|
||||
if (e.key === 'Escape') { input.value = chat.title || ''; input.blur(); }
|
||||
});
|
||||
|
||||
titleSpan.replaceWith(input);
|
||||
input.focus();
|
||||
input.select();
|
||||
}
|
||||
|
||||
// ── Send Message ─────────────────────────────
|
||||
|
||||
async function sendMessage() {
|
||||
@@ -328,6 +433,11 @@ async function sendMessage() {
|
||||
|
||||
// Persist the model/preset selection for this chat
|
||||
_saveChatModel(App.currentChatId);
|
||||
|
||||
// Auto-name: title the chat after first assistant response (v0.17.0)
|
||||
if (chat.messages.length <= 3) {
|
||||
autoNameChat(App.currentChatId, chat);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.name === 'AbortError') {
|
||||
UI.toast('Generation stopped', 'warning');
|
||||
@@ -381,7 +491,7 @@ async function reloadActivePath() {
|
||||
chat.messageCount = chat.messages.length;
|
||||
await loadChannelAttachments(App.currentChatId);
|
||||
UI.renderMessages(chat.messages);
|
||||
updateContextWarning();
|
||||
updateContextWarning(); updateChatTokenCount();
|
||||
} catch (e) {
|
||||
console.error('Failed to reload path:', e.message);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user