diff --git a/Plugin/unraid/include/ai_chat.php b/Plugin/unraid/include/ai_chat.php index 6934942..6088546 100644 --- a/Plugin/unraid/include/ai_chat.php +++ b/Plugin/unraid/include/ai_chat.php @@ -943,6 +943,14 @@ vv_ai_profiles_script(); // Per instance, like the reasoning key beside it: the Monitor card and a tab's assistant are // opened for different reasons and should not share one answer to "pick up where I left off". const RESUME_KEY = 'vvAiResume:' + P; + // WHICH thread this card was last on, per card. The chat store is shared on purpose — a + // conversation started on the dashboard is the one you carry on in a tab — but "resume" was + // resolved as "the newest thread in this profile", and Fallback, Partnership and Monitor all + // pin the same profile. Opening any of them dragged all three onto whichever had been typed + // in last, so two cards could never hold two different conversations. The pool below is still + // the shared list; this only decides which of it this card comes back to. + const LAST_CHAT_KEY = 'vvAiLastChat:' + P; + const rememberChat = id => { try { id ? localStorage.setItem(LAST_CHAT_KEY, id) : localStorage.removeItem(LAST_CHAT_KEY); } catch (_) {} }; const seeThink = () => { const c = $('see-think'); return !!c && c.checked; }; // Paths ride in data attributes rather than an onclick. They come out of the index, and a @@ -1518,7 +1526,7 @@ vv_ai_profiles_script(); messages: JSON.stringify(messages), }) }) .then(r => r.json()) - .then(d => { if (d.ok) { chatId = d.id; onChats(chatId); } }) + .then(d => { if (d.ok) { chatId = d.id; rememberChat(chatId); onChats(chatId); } }) .catch(e => vvFetchErr('save conversation', e)); } @@ -1598,6 +1606,7 @@ vv_ai_profiles_script(); // end would send the model an empty history for a transcript full of context. sendFrom = 0; chatId = c.id || ''; + rememberChat(chatId); lastSources = []; if (PROFILES[c.profile]) applyProfile(c.profile); render(); @@ -1607,6 +1616,9 @@ vv_ai_profiles_script(); function newChat() { messages = []; sendFrom = 0; chatId = ''; lastSources = []; + // Forget, so New means new on the next visit too. Without this the card would reopen the + // thread the operator had just deliberately stepped away from. + rememberChat(''); reset(); onChats(''); } @@ -2119,7 +2131,15 @@ vv_ai_profiles_script(); const pool = want ? d.chats.filter(c => (c.profile || 'chat') === want) : d.chats; if (!pool.length) return; - const last = pool.slice().sort((a, b) => (b.updated || 0) - (a.updated || 0))[0]; + // This card's own last thread first, and only if it is still in the pool — deleted + // elsewhere, or moved to a profile this card does not show, both mean it is not ours to + // reopen. Newest-in-pool remains the answer for a first visit and for that case, which is + // what every card did unconditionally before. + let want_id = ''; + try { want_id = localStorage.getItem(LAST_CHAT_KEY) || ''; } catch (_) {} + const mine = want_id ? pool.find(c => c.id === want_id) : null; + + const last = mine || pool.slice().sort((a, b) => (b.updated || 0) - (a.updated || 0))[0]; if (last) loadChat(last.id); }).catch(e => vvFetchErr('resume conversation', e)); }