mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-15 11:22:21 +02:00
feat(notes): attach selected Notes/Todos as Agent context (#6495)
* feat: add Notes and Todos as agent chat context - Send selected Note/Todo IDs from the Notes UI - Load selected notes with owner scoping in chat routes - Format note content and checklist items for the model - Inject selected notes as untrusted chat context - Add a selected-context indicator beside Shell Access - Keep selected context associated with the active chat session * Improved selected notes context handling and discouragin redundant note lookup and permission requests * Fix selected notes UI when switching chats * fix(notes): harden selected context lifecycle and validation --------- Co-authored-by: Alexandre Teixeira <111787685+alteixeira20@users.noreply.github.com>
This commit is contained in:
co-authored by
Alexandre Teixeira
parent
a8c147b238
commit
8feb50e894
@@ -1178,6 +1178,21 @@
|
||||
<polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/>
|
||||
</svg>
|
||||
</button>
|
||||
<!-- Selected Notes/Todos indicator -->
|
||||
<button
|
||||
type="button"
|
||||
class="input-icon-btn tool-indicator"
|
||||
title="Selected Notes/Todos context — click to clear"
|
||||
id="selected-notes-context-btn"
|
||||
aria-label="Clear selected Notes/Todos context"
|
||||
style="display:none;"
|
||||
>
|
||||
<span id="selected-notes-context-count" style="font-size:11px;margin-left:2px;"></span>
|
||||
<svg class="tool-indicator-x" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" aria-hidden="true">
|
||||
<line x1="6" y1="6" x2="18" y2="18"/>
|
||||
<line x1="18" y1="6" x2="6" y2="18"/>
|
||||
</svg>
|
||||
</button>
|
||||
<!-- Workspace indicator (hidden until a folder is set) -->
|
||||
<button type="button" class="input-icon-btn tool-indicator" title="Workspace - click to clear" id="workspace-indicator-btn" aria-label="Clear workspace" style="display:none;">
|
||||
<svg class="workspace-indicator-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg>
|
||||
|
||||
@@ -65,6 +65,8 @@ import { invalidateSettings } from './appConfig.js';
|
||||
let _contextHeaderAnchorEl = null;
|
||||
let _contextHeaderPopupCleanup = null;
|
||||
let _pendingToolApproval = null;
|
||||
const _selectedNoteIdsBySession = new Map();
|
||||
let _pendingSelectedNoteIds = [];
|
||||
let _lastPrivateBrowserUrl = '';
|
||||
function _isPrivateBrowserTool(tool) {
|
||||
const name = String(tool || '').toLowerCase();
|
||||
@@ -240,6 +242,46 @@ import { invalidateSettings } from './appConfig.js';
|
||||
_submitToolApprovalWhenIdle(_pendingToolApproval.approval_id);
|
||||
});
|
||||
|
||||
window.addEventListener('odysseus:notes-context-selected', (event) => {
|
||||
const noteIds = event.detail?.noteIds;
|
||||
if (!Array.isArray(noteIds) || noteIds.length === 0) return;
|
||||
|
||||
const sessionId = sessionModule.getCurrentSessionId?.();
|
||||
if (!sessionId && !sessionModule.hasPendingChat?.()) {
|
||||
console.warn('Cannot attach Notes context: no active or pending chat');
|
||||
return;
|
||||
}
|
||||
|
||||
if (sessionId) {
|
||||
_selectedNoteIdsBySession.set(sessionId, [...noteIds]);
|
||||
} else {
|
||||
_pendingSelectedNoteIds = [...noteIds];
|
||||
}
|
||||
if (event.detail) event.detail.accepted = true;
|
||||
_renderSelectedNotesIndicator(noteIds);
|
||||
});
|
||||
|
||||
window.addEventListener('odysseus:session-changed', (event) => {
|
||||
const sessionId = event.detail?.sessionId;
|
||||
if (sessionId && event.detail?.materialized && _pendingSelectedNoteIds.length) {
|
||||
_selectedNoteIdsBySession.set(sessionId, _pendingSelectedNoteIds);
|
||||
}
|
||||
_pendingSelectedNoteIds = [];
|
||||
_renderSelectedNotesIndicator(
|
||||
sessionId ? (_selectedNoteIdsBySession.get(sessionId) || []) : []
|
||||
);
|
||||
});
|
||||
|
||||
document.getElementById('selected-notes-context-btn')?.addEventListener('click', () => {
|
||||
const sessionId = sessionModule.getCurrentSessionId?.();
|
||||
if (sessionId) {
|
||||
_selectedNoteIdsBySession.delete(sessionId);
|
||||
} else {
|
||||
_pendingSelectedNoteIds = [];
|
||||
}
|
||||
_renderSelectedNotesIndicator([]);
|
||||
});
|
||||
|
||||
function _fmtContextNumber(n) {
|
||||
const v = Number(n || 0);
|
||||
return v ? v.toLocaleString() : '?';
|
||||
@@ -290,6 +332,26 @@ import { invalidateSettings } from './appConfig.js';
|
||||
return Math.max(50, Math.min(95, Math.round(n)));
|
||||
}
|
||||
|
||||
function _renderSelectedNotesIndicator(noteIds){
|
||||
const button = document.getElementById('selected-notes-context-btn');
|
||||
const count = document.getElementById('selected-notes-context-count');
|
||||
|
||||
if (!button || !count) return;
|
||||
|
||||
const total = Array.isArray(noteIds) ? noteIds.length : 0;
|
||||
if(!total){
|
||||
button.style.display = 'none';
|
||||
count.textContent = '';
|
||||
return;
|
||||
}
|
||||
count.textContent = `${total} note${total === 1 ? '' : 's'} selected`;
|
||||
button.style.display = '';
|
||||
button.title = `${total} selected Note/Todo${total === 1 ? '' : 's'} — click to clear`;
|
||||
button.setAttribute(
|
||||
'aria-label',
|
||||
`Clear ${total} selected Note/Todo${total === 1 ? '' : 's'} context`
|
||||
);
|
||||
}
|
||||
function _liveSessionModule() {
|
||||
return (window.sessionModule && window.sessionModule.getCurrentSessionId)
|
||||
? window.sessionModule
|
||||
@@ -2497,6 +2559,12 @@ import { invalidateSettings } from './appConfig.js';
|
||||
const fd = new FormData();
|
||||
fd.append('message', approvalForSend ? '' : _finalMsgWithInject);
|
||||
fd.append('session', streamSessionId);
|
||||
|
||||
const selectedNoteIds = _selectedNoteIdsBySession.get(streamSessionId) || [];
|
||||
if(selectedNoteIds.length){
|
||||
fd.append('selected_note_ids', JSON.stringify(selectedNoteIds))
|
||||
}
|
||||
|
||||
if (approvalForSend) {
|
||||
fd.append('tool_approval_id', approvalForSend.approval_id);
|
||||
fd.append('tool_approval_decision', approvalForSend.decision);
|
||||
|
||||
@@ -1230,6 +1230,7 @@ export function openPanel() {
|
||||
<label class="memory-bulk-check-all"><input type="checkbox" id="notes-select-all" /> All</label>
|
||||
<span id="notes-selected-count">0 Selected</span>
|
||||
<span style="flex:1"></span>
|
||||
<button id="notes-send-agent" class="memory-toolbar-btn" disabled><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:5px;opacity:0.6"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"></path></svg>Send to Agent</button>
|
||||
<button id="notes-bulk-archive" class="memory-toolbar-btn" disabled>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:4px;"><rect x="2" y="3" width="20" height="5" rx="1"/><path d="M4 8v11a2 2 0 002 2h12a2 2 0 002-2V8"/><path d="M10 12h4"/></svg>Archive
|
||||
</button>
|
||||
@@ -1379,6 +1380,17 @@ export function openPanel() {
|
||||
_renderNotes();
|
||||
_updateBulkBar();
|
||||
});
|
||||
document.getElementById('notes-send-agent').addEventListener('click', async () => {
|
||||
const ids = [..._selectedIds];
|
||||
if (!ids.length) return;
|
||||
const detail = { noteIds: ids, accepted: false };
|
||||
window.dispatchEvent(new CustomEvent('odysseus:notes-context-selected', { detail }));
|
||||
if (!detail.accepted) {
|
||||
uiModule.showToast('Choose a chat or model before attaching notes');
|
||||
return;
|
||||
}
|
||||
_exitSelectMode();
|
||||
})
|
||||
document.getElementById('notes-bulk-archive').addEventListener('click', async () => {
|
||||
const ids = [..._selectedIds];
|
||||
if (!ids.length) return;
|
||||
@@ -1511,10 +1523,12 @@ function _exitSelectMode() {
|
||||
function _updateBulkBar() {
|
||||
const count = _selectedIds.size;
|
||||
const countEl = document.getElementById('notes-selected-count');
|
||||
const sendAgentBtn = document.getElementById('notes-send-agent');
|
||||
const archiveBtn = document.getElementById('notes-bulk-archive');
|
||||
const deleteBtn = document.getElementById('notes-bulk-delete');
|
||||
const allEl = document.getElementById('notes-select-all');
|
||||
if (countEl) countEl.textContent = `${count} Selected`;
|
||||
if (sendAgentBtn) sendAgentBtn.disabled = count === 0;
|
||||
if (archiveBtn) archiveBtn.disabled = count === 0;
|
||||
if (deleteBtn) deleteBtn.disabled = count === 0;
|
||||
if (allEl) allEl.checked = _notes.length > 0 && _notes.every(n => _selectedIds.has(n.id));
|
||||
|
||||
@@ -351,6 +351,9 @@ let _sessionListFocused = false;
|
||||
function _deselectCurrentSession(sid) {
|
||||
if (currentSessionId !== sid) return;
|
||||
currentSessionId = null;
|
||||
window.dispatchEvent(new CustomEvent('odysseus:session-changed', {
|
||||
detail: { sessionId: null },
|
||||
}));
|
||||
uiModule.el('chat-history').innerHTML = '';
|
||||
uiModule.el('current-meta').textContent = 'Odysseus Chat';
|
||||
Storage.remove('lastSessionId');
|
||||
@@ -2065,6 +2068,9 @@ export async function selectSession(id, { keepSidebar = false, showLoading = tru
|
||||
try { window.documentModule.clearSelection(); } catch {}
|
||||
}
|
||||
currentSessionId = id;
|
||||
window.dispatchEvent(new CustomEvent('odysseus:session-changed', {
|
||||
detail: { sessionId: id ? id : null },
|
||||
}));
|
||||
try { window.__odysseusLastSelectedSessionId = id; } catch (_) {}
|
||||
// Identify Assistant / task-output sessions so we don't "trap" the user
|
||||
// there on return. Skipped from both `lastSessionId` persistence and the
|
||||
@@ -2445,6 +2451,9 @@ export function createDirectChat(url, modelId, endpointId, opts = {}) {
|
||||
_skipAutoSelect = true;
|
||||
_suppressNextSessionLoading = true;
|
||||
currentSessionId = null;
|
||||
window.dispatchEvent(new CustomEvent('odysseus:session-changed', {
|
||||
detail: { sessionId: null },
|
||||
}));
|
||||
try { window.__odysseusLastSelectedSessionId = ''; } catch (_) {}
|
||||
Storage.remove('lastSessionId');
|
||||
history.replaceState(null, '', window.location.pathname);
|
||||
@@ -2555,6 +2564,9 @@ export async function materializePendingSession() {
|
||||
}
|
||||
_pendingChat = null;
|
||||
currentSessionId = payload.id;
|
||||
window.dispatchEvent(new CustomEvent('odysseus:session-changed', {
|
||||
detail: { sessionId: payload.id, materialized: true },
|
||||
}));
|
||||
if (!isIncognito) {
|
||||
Storage.set('lastSessionId', payload.id);
|
||||
history.replaceState(null, '', '#' + payload.id);
|
||||
@@ -2626,6 +2638,9 @@ export function getCurrentEndpointUrl() {
|
||||
export function setCurrentSessionId(id) {
|
||||
_sessionNavToken++;
|
||||
currentSessionId = id;
|
||||
window.dispatchEvent(new CustomEvent('odysseus:session-changed', {
|
||||
detail: { sessionId: id ? id : null },
|
||||
}));
|
||||
try { window.__odysseusLastSelectedSessionId = id || ''; } catch (_) {}
|
||||
if (!id) {
|
||||
_suppressNextSessionLoading = true;
|
||||
|
||||
Reference in New Issue
Block a user