diff --git a/Plugin/unraid/include/ai_chat.php b/Plugin/unraid/include/ai_chat.php index 7b58647..8a39801 100644 --- a/Plugin/unraid/include/ai_chat.php +++ b/Plugin/unraid/include/ai_chat.php @@ -2163,9 +2163,60 @@ vv_ai_profiles_script(); if (!box) return null; let rows = [], activeId = ''; + // Optional first section: this card's own profile, collapsed, holding its most recent + // conversations. Underneath it the full list carries on unchanged. + // + // The two answer different questions. "Where was I in General Chat" is the one asked on a + // card pinned to General Chat, and a single flat list buries it under whatever the tabs have + // been doing — the profile with the most threads wins the top of the list regardless of which + // card you are looking at. Collapsed by default because the card resumes the newest of these + // anyway; opening it is for reaching the other nine. + const GROUP = o.groupProfile || ''; + const GROUP_MAX = o.groupMax || 10; + const OPEN_KEY = 'vvAiListOpen:' + (o.into || ''); + let groupOpen = false; + try { groupOpen = localStorage.getItem(OPEN_KEY) === '1'; } catch (_) {} + + function rowHtml(c, P) { + const prof = (c.profile && c.profile !== 'chat' && P[c.profile]) ? P[c.profile].short : ''; + const tag = [prof, c.scope || ''].filter(Boolean).join(' · '); + return `
` + + `` + + (tag ? `${esc(tag)} ` : '') + + `${esc(c.title)}` + + `${esc(ago(c.ts))}` + + `×` + + `
`; + } + + function groupHtml() { + if (!GROUP) return ''; + const P = window.VvAiProfiles || {}; + const name = (P[GROUP] && P[GROUP].short) || (GROUP === 'chat' ? 'General Chat' : GROUP); + const mine = rows.filter(c => (c.profile || 'chat') === GROUP) + .sort((a, b) => (b.updated || b.ts || 0) - (a.updated || a.ts || 0)) + .slice(0, GROUP_MAX); + return `
` + + `
` + + `${groupOpen ? '▾' : '▸'}` + + `${esc(name)}` + + `${mine.length}` + + `
` + + (groupOpen + ? `
` + + (mine.length ? mine.map(c => rowHtml(c, P)).join('') + : '
none yet
') + + `
` + : '') + + `
` + + `
`; + } + function render() { if (!rows.length) { - box.innerHTML = '
no saved conversations yet
'; + box.innerHTML = groupHtml() + '
no saved conversations yet
'; return; } // The scope is shown, not just stored. A Scheduler thread is about one script or log, @@ -2176,18 +2227,8 @@ vv_ai_profiles_script(); // makes the rows that are genuinely different harder to pick out, which is the opposite // of the point. const P = window.VvAiProfiles || {}; - box.innerHTML = '
' + rows.map(c => { - const prof = (c.profile && c.profile !== 'chat' && P[c.profile]) ? P[c.profile].short : ''; - const tag = [prof, c.scope || ''].filter(Boolean).join(' · '); - return `
` - + `` - + (tag ? `${esc(tag)} ` : '') - + `${esc(c.title)}` - + `${esc(ago(c.ts))}` - + `×` - + `
`; - }).join('') + '
'; + box.innerHTML = groupHtml() + + '
' + rows.map(c => rowHtml(c, P)).join('') + '
'; } function load() { @@ -2197,6 +2238,14 @@ vv_ai_profiles_script(); } box.addEventListener('click', e => { + // Before the row handler: the header sits above the rows and must not be read as one. + const grp = e.target.closest('[data-grp]'); + if (grp) { + groupOpen = !groupOpen; + try { localStorage.setItem(OPEN_KEY, groupOpen ? '1' : '0'); } catch (_) {} + render(); + return; + } const del = e.target.closest('[data-del]'); if (del) { e.stopPropagation(); diff --git a/Plugin/unraid/pages/monitor.php b/Plugin/unraid/pages/monitor.php index 60e6e74..8364c15 100644 --- a/Plugin/unraid/pages/monitor.php +++ b/Plugin/unraid/pages/monitor.php @@ -2867,7 +2867,14 @@ if (document.getElementById('vv-mon-ai-chat')) { // for the poll to reflect it reads as the card being broken. onTurn: () => vvLoadTokens(), }); - vvMonChatList = VvAiChatList({ into: 'vv-mon-ai-chats', chat: vvMonChat }); + vvMonChatList = VvAiChatList({ + into: 'vv-mon-ai-chats', + chat: vvMonChat, + // This card is pinned to General Chat, so its own threads get the top section rather than + // competing for position with everything the tabs have been doing. + groupProfile: 'chat', + groupMax: 10, + }); vvMonChatList.reload(); // Re-measured rather than computed once. The Assistant changes height when the transcript is