Monitor conversation list: collapsible section for the card's own profile above the full list
A flat list buries 'where was I in General Chat' under whatever the tabs have been doing, since the busiest profile takes the top regardless of which card you are looking at.
This commit is contained in:
@@ -2163,20 +2163,21 @@ vv_ai_profiles_script();
|
|||||||
if (!box) return null;
|
if (!box) return null;
|
||||||
let rows = [], activeId = '';
|
let rows = [], activeId = '';
|
||||||
|
|
||||||
function render() {
|
// Optional first section: this card's own profile, collapsed, holding its most recent
|
||||||
if (!rows.length) {
|
// conversations. Underneath it the full list carries on unchanged.
|
||||||
box.innerHTML = '<div class="vv-ai-none">no saved conversations yet</div>';
|
//
|
||||||
return;
|
// 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
|
||||||
// The scope is shown, not just stored. A Scheduler thread is about one script or log,
|
// been doing — the profile with the most threads wins the top of the list regardless of which
|
||||||
// and a list of titles alone makes "why does this one talk about a log I never opened"
|
// card you are looking at. Collapsed by default because the card resumes the newest of these
|
||||||
// an unanswerable question.
|
// anyway; opening it is for reaching the other nine.
|
||||||
// Tagged only when the tag says something. A scope always does; a profile does unless it
|
const GROUP = o.groupProfile || '';
|
||||||
// is the ordinary one — labelling every General Chat row "Chat" is a column of noise that
|
const GROUP_MAX = o.groupMax || 10;
|
||||||
// makes the rows that are genuinely different harder to pick out, which is the opposite
|
const OPEN_KEY = 'vvAiListOpen:' + (o.into || '');
|
||||||
// of the point.
|
let groupOpen = false;
|
||||||
const P = window.VvAiProfiles || {};
|
try { groupOpen = localStorage.getItem(OPEN_KEY) === '1'; } catch (_) {}
|
||||||
box.innerHTML = '<div class="vv-ai-clist">' + rows.map(c => {
|
|
||||||
|
function rowHtml(c, P) {
|
||||||
const prof = (c.profile && c.profile !== 'chat' && P[c.profile]) ? P[c.profile].short : '';
|
const prof = (c.profile && c.profile !== 'chat' && P[c.profile]) ? P[c.profile].short : '';
|
||||||
const tag = [prof, c.scope || ''].filter(Boolean).join(' · ');
|
const tag = [prof, c.scope || ''].filter(Boolean).join(' · ');
|
||||||
return `<div class="vv-ai-crow${c.id === activeId ? ' active' : ''}" data-id="${esc(c.id)}">`
|
return `<div class="vv-ai-crow${c.id === activeId ? ' active' : ''}" data-id="${esc(c.id)}">`
|
||||||
@@ -2187,7 +2188,47 @@ vv_ai_profiles_script();
|
|||||||
+ `<span class="vv-ai-crow-m">${esc(ago(c.ts))}</span>`
|
+ `<span class="vv-ai-crow-m">${esc(ago(c.ts))}</span>`
|
||||||
+ `<span class="vv-ai-crow-x" data-del="${esc(c.id)}" title="Delete">×</span>`
|
+ `<span class="vv-ai-crow-x" data-del="${esc(c.id)}" title="Delete">×</span>`
|
||||||
+ `</div>`;
|
+ `</div>`;
|
||||||
}).join('') + '</div>';
|
}
|
||||||
|
|
||||||
|
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 `<div class="vv-ai-cgroup">`
|
||||||
|
+ `<div class="vv-ai-chead" data-grp="1" style="display:flex;align-items:center;gap:6px;
|
||||||
|
cursor:pointer;padding:3px 4px;user-select:none;">`
|
||||||
|
+ `<span style="color:#666;font-size:10px;">${groupOpen ? '▾' : '▸'}</span>`
|
||||||
|
+ `<span style="color:#888;font-size:10px;font-weight:600;">${esc(name)}</span>`
|
||||||
|
+ `<span style="color:#333;font-size:9px;">${mine.length}</span>`
|
||||||
|
+ `</div>`
|
||||||
|
+ (groupOpen
|
||||||
|
? `<div class="vv-ai-clist">`
|
||||||
|
+ (mine.length ? mine.map(c => rowHtml(c, P)).join('')
|
||||||
|
: '<div class="vv-ai-none">none yet</div>')
|
||||||
|
+ `</div>`
|
||||||
|
: '')
|
||||||
|
+ `<div style="border-bottom:1px solid #1b1b1b;margin:5px 0;"></div>`
|
||||||
|
+ `</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
if (!rows.length) {
|
||||||
|
box.innerHTML = groupHtml() + '<div class="vv-ai-none">no saved conversations yet</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// The scope is shown, not just stored. A Scheduler thread is about one script or log,
|
||||||
|
// and a list of titles alone makes "why does this one talk about a log I never opened"
|
||||||
|
// an unanswerable question.
|
||||||
|
// Tagged only when the tag says something. A scope always does; a profile does unless it
|
||||||
|
// is the ordinary one — labelling every General Chat row "Chat" is a column of noise that
|
||||||
|
// makes the rows that are genuinely different harder to pick out, which is the opposite
|
||||||
|
// of the point.
|
||||||
|
const P = window.VvAiProfiles || {};
|
||||||
|
box.innerHTML = groupHtml()
|
||||||
|
+ '<div class="vv-ai-clist">' + rows.map(c => rowHtml(c, P)).join('') + '</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
function load() {
|
function load() {
|
||||||
@@ -2197,6 +2238,14 @@ vv_ai_profiles_script();
|
|||||||
}
|
}
|
||||||
|
|
||||||
box.addEventListener('click', e => {
|
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]');
|
const del = e.target.closest('[data-del]');
|
||||||
if (del) {
|
if (del) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|||||||
@@ -2867,7 +2867,14 @@ if (document.getElementById('vv-mon-ai-chat')) {
|
|||||||
// for the poll to reflect it reads as the card being broken.
|
// for the poll to reflect it reads as the card being broken.
|
||||||
onTurn: () => vvLoadTokens(),
|
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();
|
vvMonChatList.reload();
|
||||||
|
|
||||||
// Re-measured rather than computed once. The Assistant changes height when the transcript is
|
// Re-measured rather than computed once. The Assistant changes height when the transcript is
|
||||||
|
|||||||
Reference in New Issue
Block a user