feat(ai): switch page ai to hermes panel
This commit is contained in:
@@ -3014,7 +3014,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
}
|
||||
|
||||
function pageAiStorageKey() {
|
||||
return 'doc_ai_sessions:' + currentDocumentId();
|
||||
return 'hermes_page_ai_session:' + currentDocumentId();
|
||||
}
|
||||
|
||||
function pageAiNewSession(title) {
|
||||
@@ -3046,46 +3046,90 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
}
|
||||
|
||||
function pageAiLoadSessions() {
|
||||
if (pageUiState.pageAiSessions.length && pageUiState.pageAiActiveSessionId) return;
|
||||
try {
|
||||
var raw = window.localStorage.getItem(pageAiStorageKey());
|
||||
if (!raw) {
|
||||
var fresh = pageAiNewSession();
|
||||
pageUiState.pageAiSessions = [fresh];
|
||||
pageUiState.pageAiActiveSessionId = fresh.id;
|
||||
pageUiState.pageAiMessages = [];
|
||||
return;
|
||||
}
|
||||
var parsed = JSON.parse(raw);
|
||||
var sessions = pageAiNormalizeSessions(parsed && parsed.sessions ? parsed.sessions : []);
|
||||
if (!sessions.length) {
|
||||
var fallback = pageAiNewSession();
|
||||
pageUiState.pageAiSessions = [fallback];
|
||||
pageUiState.pageAiActiveSessionId = fallback.id;
|
||||
pageUiState.pageAiMessages = [];
|
||||
return;
|
||||
}
|
||||
pageUiState.pageAiSessions = sessions;
|
||||
var parsed = raw ? JSON.parse(raw) : null;
|
||||
var activeId = String(parsed && parsed.activeSessionId || '').trim();
|
||||
var active = sessions.find(function(session) { return session.id === activeId; }) || sessions[0];
|
||||
pageUiState.pageAiActiveSessionId = active.id;
|
||||
pageUiState.pageAiMessages = Array.isArray(active.messages) ? active.messages.slice() : [];
|
||||
} catch (_) {
|
||||
var reset = pageAiNewSession();
|
||||
pageUiState.pageAiSessions = [reset];
|
||||
pageUiState.pageAiActiveSessionId = reset.id;
|
||||
pageUiState.pageAiMessages = [];
|
||||
}
|
||||
if (activeId) {
|
||||
pageUiState.pageAiSessions = [pageAiNewSession('当前页问答')];
|
||||
pageUiState.pageAiSessions[0].id = activeId;
|
||||
pageUiState.pageAiActiveSessionId = activeId;
|
||||
pageUiState.pageAiMessages = [];
|
||||
return;
|
||||
}
|
||||
} catch (_) {}
|
||||
var fresh = pageAiNewSession();
|
||||
pageUiState.pageAiSessions = [fresh];
|
||||
pageUiState.pageAiActiveSessionId = fresh.id;
|
||||
pageUiState.pageAiMessages = [];
|
||||
}
|
||||
|
||||
function pageAiPersistSessions() {
|
||||
document.documentElement.setAttribute('data-mnote-page-ai-session-owner', 'hermes');
|
||||
document.documentElement.setAttribute('data-mnote-page-ai-session-key', pageAiStorageKey());
|
||||
try {
|
||||
window.localStorage.setItem(pageAiStorageKey(), JSON.stringify({
|
||||
activeSessionId: pageUiState.pageAiActiveSessionId,
|
||||
sessions: pageAiNormalizeSessions(pageUiState.pageAiSessions)
|
||||
activeSessionId: pageUiState.pageAiActiveSessionId
|
||||
}));
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
async function pageAiEnsureHermesSession() {
|
||||
pageAiLoadSessions();
|
||||
var current = pageAiCurrentSession();
|
||||
if (current && String(current.id || '').startsWith('mnote_')) return current;
|
||||
var response = await fetch('/api/hermes/client/sessions', {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
workspaceId: resolveWorkspaceId(document.body),
|
||||
documentId: currentDocumentId(),
|
||||
traceId: 'page-ai-' + Date.now().toString(36),
|
||||
title: current && current.title ? current.title : '当前页问答'
|
||||
})
|
||||
});
|
||||
var payload = await response.json().catch(function(){ return null; });
|
||||
if (!response.ok || !payload || payload.ok !== true) {
|
||||
var code = payload && payload.code ? payload.code : 'hermes_session_failed_' + response.status;
|
||||
throw new Error(code);
|
||||
}
|
||||
var session = {
|
||||
id: String(payload.sessionId || '').trim(),
|
||||
title: String(payload.title || '当前页问答'),
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
messages: pageUiState.pageAiMessages.slice()
|
||||
};
|
||||
pageUiState.pageAiSessions = pageAiNormalizeSessions([session]);
|
||||
pageUiState.pageAiActiveSessionId = session.id;
|
||||
pageAiPersistSessions();
|
||||
renderPageAiConversation();
|
||||
return session;
|
||||
}
|
||||
|
||||
async function pageAiRestoreHermesSession() {
|
||||
var current = pageAiCurrentSession();
|
||||
if (!current || !String(current.id || '').startsWith('mnote_')) return;
|
||||
var response = await fetch('/api/hermes/client/sessions/' + encodeURIComponent(current.id), {
|
||||
headers: { 'accept': 'application/json' }
|
||||
});
|
||||
if (!response.ok) return;
|
||||
var payload = await response.json().catch(function(){ return null; });
|
||||
var session = payload && (payload.session || (payload.upstream && payload.upstream.session) || payload);
|
||||
var messages = session && Array.isArray(session.messages) ? session.messages : [];
|
||||
if (!messages.length) return;
|
||||
pageUiState.pageAiMessages = messages.slice(-40).map(function(message) {
|
||||
return {
|
||||
role: message.role === 'user' ? 'user' : (message.role === 'tool' ? 'tool' : 'assistant'),
|
||||
content: String(message.content || '')
|
||||
};
|
||||
});
|
||||
current.messages = pageUiState.pageAiMessages.slice();
|
||||
current.updatedAt = Date.now();
|
||||
renderPageAiConversation();
|
||||
}
|
||||
|
||||
function pageAiCurrentSession() {
|
||||
return pageUiState.pageAiSessions.find(function(session) {
|
||||
return session.id === pageUiState.pageAiActiveSessionId;
|
||||
@@ -3146,7 +3190,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
var args = normalized && normalized.args ? normalized.args : {};
|
||||
var documentId = searchText(args.documentId || args.pageId || currentDocumentId());
|
||||
var toolName = searchText(operation.tool_name || normalized.toolName || 'doc_get');
|
||||
return providerLabel + ' 已收到你的问题“' + searchText(promptText) + '”。当前已通过 mnote-cli 建立页面级工具调用,目标页面是 ' + (documentId || '当前页面') + ',本次选择的工具是 ' + toolName + '。如果你继续追问,我会沿当前页面上下文继续回复。';
|
||||
return providerLabel + ' 已收到你的问题“' + searchText(promptText) + '”。当前已通过 Hermes 调用 mnote plugin 工具,目标页面是 ' + (documentId || '当前页面') + ',本次选择的工具是 ' + toolName + '。如果你继续追问,我会沿当前页面上下文继续回复。';
|
||||
} catch (_) {
|
||||
return providerLabel + ' 已返回结果,但当前结果是结构化文本,已为你保留原始内容。';
|
||||
}
|
||||
@@ -3175,7 +3219,11 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
'<div class="wolai-page-ai-suggestions">' +
|
||||
'<div class="wolai-page-ai-suggestions-header">' +
|
||||
'<span>推荐问题</span>' +
|
||||
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-action="rotate">换一换</button>' +
|
||||
'<div class="wolai-page-ai-intents">' +
|
||||
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-intent="create-summary">创建 Summary</button>' +
|
||||
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-intent="create-ai-note">创建 AI Note</button>' +
|
||||
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-action="rotate">换一换</button>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="wolai-page-ai-suggestion-list" data-page-ai-suggestion-list></div>' +
|
||||
'</div>' +
|
||||
@@ -3187,8 +3235,6 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
'<button type="button" class="wolai-page-ai-tab" data-page-ai-action="history">历史会话</button>' +
|
||||
'<div class="wolai-page-ai-provider-group">' +
|
||||
'<button type="button" class="wolai-page-ai-model-chip is-active" data-page-ai-provider="hermes" aria-pressed="true">Hermes</button>' +
|
||||
'<button type="button" class="wolai-page-ai-model-chip" data-page-ai-provider="codex" aria-pressed="false">Codex</button>' +
|
||||
'<button type="button" class="wolai-page-ai-model-chip" data-page-ai-provider="claudecode" aria-pressed="false">ClaudeCode</button>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="wolai-page-ai-input-row">' +
|
||||
@@ -3240,9 +3286,10 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
return;
|
||||
}
|
||||
conversation.innerHTML = pageUiState.pageAiMessages.map(function(item) {
|
||||
var roleLabel = item.role === 'user' ? '你' : (item.role === 'tool' ? '工具' : 'AI');
|
||||
return '' +
|
||||
'<div class="wolai-page-ai-message wolai-page-ai-message--' + escapeHtml(item.role || 'assistant') + '">' +
|
||||
'<div class="wolai-page-ai-message-role">' + escapeHtml(item.role === 'user' ? '你' : 'AI') + '</div>' +
|
||||
'<div class="wolai-page-ai-message-role">' + escapeHtml(roleLabel) + '</div>' +
|
||||
'<div class="wolai-page-ai-message-text">' + escapeHtml(item.content || '') + '</div>' +
|
||||
'</div>';
|
||||
}).join('');
|
||||
@@ -3258,6 +3305,15 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
drawer.hidden = false;
|
||||
pageUiState.pageAiOpen = true;
|
||||
updatePageAiTriggerState();
|
||||
pageAiEnsureHermesSession().then(function() {
|
||||
return pageAiRestoreHermesSession();
|
||||
}).catch(function(error) {
|
||||
pageUiState.pageAiMessages.push({
|
||||
role: 'assistant',
|
||||
content: 'Hermes 当前不可用:' + (error instanceof Error ? error.message : String(error))
|
||||
});
|
||||
renderPageAiConversation();
|
||||
});
|
||||
}
|
||||
|
||||
function closePageAiDrawer() {
|
||||
@@ -3290,7 +3346,14 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
if (line.startsWith('event:')) eventName = line.slice(6).trim();
|
||||
if (line.startsWith('data:')) dataLines.push(line.slice(5).trim());
|
||||
});
|
||||
if (eventName) onEvent(eventName, dataLines.join('\n'));
|
||||
var payloadText = dataLines.join('\n');
|
||||
if (!eventName && payloadText) {
|
||||
try {
|
||||
var parsed = JSON.parse(payloadText);
|
||||
eventName = parsed && parsed.event ? String(parsed.event) : '';
|
||||
} catch (_) {}
|
||||
}
|
||||
if (eventName) onEvent(eventName, payloadText);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -3299,39 +3362,35 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
if (pageUiState.pageAiBusy) return;
|
||||
var prompt = searchText(text);
|
||||
if (!prompt) return;
|
||||
pageAiLoadSessions();
|
||||
var contextSnapshot = currentPageAiContextSnapshot();
|
||||
var aggregate = contextSnapshot.aggregate || {};
|
||||
var body = contextSnapshot.body || {};
|
||||
var subtree = contextSnapshot.subtree || null;
|
||||
var outline = subtree && subtree.outline ? subtree.outline : null;
|
||||
pageUiState.pageAiBusy = true;
|
||||
pageUiState.pageAiMessages.push({ role: 'user', content: prompt });
|
||||
var currentSession = pageAiCurrentSession();
|
||||
if (currentSession) {
|
||||
if (currentSession.title === '新会话') {
|
||||
currentSession.title = prompt.length > 18 ? prompt.slice(0, 18) + '…' : prompt;
|
||||
}
|
||||
currentSession.messages = pageUiState.pageAiMessages.slice();
|
||||
currentSession.updatedAt = Date.now();
|
||||
}
|
||||
pageAiPersistSessions();
|
||||
renderPageAiConversation();
|
||||
var currentSession = null;
|
||||
try {
|
||||
var response = await fetch('/api/ai-agent/run', {
|
||||
await pageAiEnsureHermesSession();
|
||||
var contextSnapshot = currentPageAiContextSnapshot();
|
||||
var aggregate = contextSnapshot.aggregate || {};
|
||||
var body = contextSnapshot.body || {};
|
||||
var subtree = contextSnapshot.subtree || null;
|
||||
var outline = subtree && subtree.outline ? subtree.outline : null;
|
||||
pageUiState.pageAiMessages.push({ role: 'user', content: prompt });
|
||||
currentSession = pageAiCurrentSession();
|
||||
if (currentSession) {
|
||||
if (currentSession.title === '新会话') {
|
||||
currentSession.title = prompt.length > 18 ? prompt.slice(0, 18) + '…' : prompt;
|
||||
}
|
||||
currentSession.messages = pageUiState.pageAiMessages.slice();
|
||||
currentSession.updatedAt = Date.now();
|
||||
}
|
||||
renderPageAiConversation();
|
||||
var response = await fetch('/api/hermes/client/runs', {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
stream: true,
|
||||
maxSteps: 8,
|
||||
scope: 'document',
|
||||
messages: [{ role: 'user', content: prompt }],
|
||||
toolChoice: {
|
||||
mode: 'auto',
|
||||
toolSets: ['toolset.readonly', 'toolset.rag_read', 'toolset.docs_read', 'toolset.media_read', 'toolset.doc_read', 'toolset.doc_write', 'toolset.slash_write']
|
||||
},
|
||||
context: {
|
||||
documentId: currentDocumentId(),
|
||||
workspaceId: resolveWorkspaceId(document.body),
|
||||
documentId: currentDocumentId(),
|
||||
sessionId: pageUiState.pageAiActiveSessionId,
|
||||
message: prompt,
|
||||
model: 'hermes-agent',
|
||||
pageContext: {
|
||||
documentBlocks: body.content || null,
|
||||
node: {
|
||||
documentId: currentDocumentId(),
|
||||
@@ -3343,23 +3402,54 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
evidence: null,
|
||||
pageOptions: currentPageOptions()
|
||||
},
|
||||
options: {
|
||||
searxng: true,
|
||||
ai: { provider: pageUiState.pageAiProvider }
|
||||
}
|
||||
selectedBlockId: null,
|
||||
selectedText: null,
|
||||
traceId: 'page-ai-run-' + Date.now().toString(36)
|
||||
})
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('page_ai_failed_' + response.status);
|
||||
var errorPayload = await response.json().catch(function(){ return null; });
|
||||
throw new Error(errorPayload && errorPayload.code ? errorPayload.code : 'page_ai_failed_' + response.status);
|
||||
}
|
||||
var runPayload = await response.json().catch(function(){ return null; });
|
||||
var upstream = runPayload && runPayload.upstream ? runPayload.upstream : runPayload;
|
||||
var runId = upstream && (upstream.run_id || upstream.runId);
|
||||
if (!runId) throw new Error('hermes_run_missing_run_id');
|
||||
var eventResponse = await fetch('/api/hermes/client/events/' + encodeURIComponent(runId), {
|
||||
headers: { 'accept': 'text/event-stream' }
|
||||
});
|
||||
if (!eventResponse.ok) {
|
||||
var eventError = await eventResponse.json().catch(function(){ return null; });
|
||||
throw new Error(eventError && eventError.code ? eventError.code : 'hermes_events_failed_' + eventResponse.status);
|
||||
}
|
||||
var assistantText = '';
|
||||
await streamPageAiResponse(response, function(eventName, payloadText) {
|
||||
if (eventName === 'assistant_message') {
|
||||
await streamPageAiResponse(eventResponse, function(eventName, payloadText) {
|
||||
if (eventName === 'assistant_message' || eventName === 'message.delta') {
|
||||
try {
|
||||
var payload = JSON.parse(payloadText || 'null');
|
||||
assistantText = searchText(payload && payload.text);
|
||||
assistantText += searchText((payload && (payload.text || payload.delta)) || '');
|
||||
} catch (_) {
|
||||
assistantText += searchText(payloadText);
|
||||
}
|
||||
}
|
||||
if (eventName === 'run.completed') {
|
||||
try {
|
||||
var completed = JSON.parse(payloadText || 'null');
|
||||
if (completed && completed.output) assistantText = searchText(completed.output);
|
||||
} catch (_) {}
|
||||
}
|
||||
if (eventName === 'tool.started' || eventName === 'tool.completed' || eventName === 'tool.failed') {
|
||||
try {
|
||||
var toolEvent = JSON.parse(payloadText || 'null');
|
||||
pageUiState.pageAiMessages.push({
|
||||
role: 'tool',
|
||||
content: String(toolEvent && (toolEvent.name || toolEvent.tool || eventName) || eventName)
|
||||
});
|
||||
} catch (_) {
|
||||
pageUiState.pageAiMessages.push({ role: 'tool', content: eventName });
|
||||
}
|
||||
renderPageAiConversation();
|
||||
}
|
||||
});
|
||||
pageUiState.pageAiMessages.push({
|
||||
role: 'assistant',
|
||||
@@ -3370,18 +3460,16 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
currentSession.messages = pageUiState.pageAiMessages.slice();
|
||||
currentSession.updatedAt = Date.now();
|
||||
}
|
||||
pageAiPersistSessions();
|
||||
} catch (error) {
|
||||
pageUiState.pageAiMessages.push({
|
||||
role: 'assistant',
|
||||
content: pageAiProviderLabel(pageUiState.pageAiProvider) + ' 当前请求失败:' + (error instanceof Error ? error.message : String(error))
|
||||
content: 'Hermes 当前请求失败:' + (error instanceof Error ? error.message : String(error))
|
||||
});
|
||||
currentSession = pageAiCurrentSession();
|
||||
if (currentSession) {
|
||||
currentSession.messages = pageUiState.pageAiMessages.slice();
|
||||
currentSession.updatedAt = Date.now();
|
||||
}
|
||||
pageAiPersistSessions();
|
||||
} finally {
|
||||
pageUiState.pageAiBusy = false;
|
||||
renderPageAiConversation();
|
||||
@@ -3937,6 +4025,20 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
return;
|
||||
}
|
||||
|
||||
var pageAiIntent = closestAction(e.target, '[data-page-ai-intent]');
|
||||
if (pageAiIntent) {
|
||||
e.preventDefault();
|
||||
var intentName = pageAiIntent.getAttribute('data-page-ai-intent') || '';
|
||||
if (intentName === 'create-summary') {
|
||||
void sendPageAiMessage('请通过 Hermes 调用 mnote.artifact.create_summary,为当前页面创建或更新 AI Summary。');
|
||||
return;
|
||||
}
|
||||
if (intentName === 'create-ai-note') {
|
||||
void sendPageAiMessage('请通过 Hermes 调用 mnote.artifact.create_ai_note,基于当前页面创建一篇新的 AI Note。');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var pageAiProvider = closestAction(e.target, '[data-page-ai-provider]');
|
||||
if (pageAiProvider) {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -2690,6 +2690,13 @@ body {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.wolai-page-ai-intents {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.wolai-page-ai-suggestion-list,
|
||||
.wolai-page-ai-toolbar {
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user