feat(page-ai): add skill context and agent profile policy
This commit is contained in:
@@ -320,9 +320,11 @@ export function createSidebarPageAiRuntime(context) {
|
||||
var record = pageAiAgentRecord(next);
|
||||
pageUiState.pageAiAgentId = next;
|
||||
pageUiState.pageAiAcpRuntime = record.acpRuntime || 'reasonix';
|
||||
pageUiState.pageAiAgentPopoverOpen = next === 'hermes';
|
||||
if (next === 'hermes') pageAiSetActiveProfile(pageAiCurrentProfile());
|
||||
document.documentElement.setAttribute('data-mnote-page-ai-agent-id', next);
|
||||
pageAiPersistAiPreference('default_agent_id', next);
|
||||
void pageAiLoadSkills();
|
||||
renderPageAiProviderButtons();
|
||||
renderPageAiControls();
|
||||
}
|
||||
@@ -337,7 +339,62 @@ export function createSidebarPageAiRuntime(context) {
|
||||
renderPageAiControls();
|
||||
}
|
||||
|
||||
function pageAiSetContextPopoverOpen(open) {
|
||||
pageUiState.pageAiContextPopoverOpen = Boolean(open);
|
||||
if (open) pageUiState.pageAiAgentPopoverOpen = false;
|
||||
renderPageAiControls();
|
||||
}
|
||||
|
||||
function pageAiSetAgentPopoverOpen(open) {
|
||||
pageUiState.pageAiAgentPopoverOpen = Boolean(open);
|
||||
if (open) pageUiState.pageAiContextPopoverOpen = false;
|
||||
renderPageAiControls();
|
||||
}
|
||||
|
||||
function pageAiContextRefLabel(kind) {
|
||||
var record = PAGE_AI_CONTEXT_REF_REGISTRY.find(function(ref) { return ref.id === kind; });
|
||||
return record ? record.label : kind;
|
||||
}
|
||||
|
||||
function pageAiContextButtonSummary() {
|
||||
var selected = pageAiEnsureContextRefState();
|
||||
var labels = PAGE_AI_CONTEXT_REF_REGISTRY
|
||||
.filter(function(ref) { return selected[ref.id] === true; })
|
||||
.map(function(ref) { return ref.label; });
|
||||
if (!labels.length) return '选择上下文';
|
||||
var head = labels.slice(0, 2).join(' + ');
|
||||
return labels.length > 2 ? head + ' +' + String(labels.length - 2) : head;
|
||||
}
|
||||
|
||||
function pageAiContextRefDetail(kind) {
|
||||
var documentId = currentDocumentId();
|
||||
var relativePath = localMarkdownRelativePathFromPageAiDocumentId(documentId);
|
||||
if (kind === 'current_page') return relativePath || documentId || '当前页面';
|
||||
if (kind === 'selection') return currentPageAiSelectedText() ? '当前选区可用' : '当前没有选区';
|
||||
if (kind === 'active_editor') {
|
||||
var target = currentPageAiEditorTarget();
|
||||
return String(target && (target.title || target.documentId) || relativePath || '当前打开资源');
|
||||
}
|
||||
if (kind === 'file') return relativePath || '当前文件';
|
||||
if (kind === 'folder') {
|
||||
var roots = pageAiBuildAllowedRoots();
|
||||
if (!roots.length) return '需要授权后可用';
|
||||
return roots.map(function(root) { return root.rootUri.replace(/^file:\/\//, ''); }).filter(Boolean)[0] || '已授权文件夹';
|
||||
}
|
||||
if (kind === 'changed_files') return '本次 run 后可用于追问';
|
||||
return '';
|
||||
}
|
||||
|
||||
function pageAiContextRefDisabled(kind) {
|
||||
if (kind === 'selection') return !currentPageAiSelectedText();
|
||||
return false;
|
||||
}
|
||||
|
||||
function pageAiPersistAiPreference(key, value) {
|
||||
pageAiPersistRawAiPreference('ai.common.' + key, value);
|
||||
}
|
||||
|
||||
function pageAiPersistRawAiPreference(key, value) {
|
||||
try {
|
||||
var workspaceId = resolveWorkspaceId(document.body);
|
||||
var body = {
|
||||
@@ -347,7 +404,7 @@ export function createSidebarPageAiRuntime(context) {
|
||||
documentId: currentDocumentId(),
|
||||
updates: {}
|
||||
};
|
||||
body.updates['ai.common.' + key] = value;
|
||||
body.updates[key] = value;
|
||||
void fetch('/api/ui/preferences', {
|
||||
method: 'PUT',
|
||||
headers: { 'content-type': 'application/json', accept: 'application/json' },
|
||||
@@ -369,16 +426,23 @@ export function createSidebarPageAiRuntime(context) {
|
||||
var preferences = payload.result.aiPreferences && typeof payload.result.aiPreferences === 'object'
|
||||
? payload.result.aiPreferences
|
||||
: {};
|
||||
pageUiState.pageAiSkillPreferences = Object.assign({}, preferences);
|
||||
var defaultAgent = String(preferences['ai.common.default_agent_id'] || '').trim();
|
||||
if (defaultAgent) {
|
||||
var agent = pageAiAgentRecord(defaultAgent);
|
||||
pageUiState.pageAiAgentId = agent.id;
|
||||
pageUiState.pageAiAcpRuntime = agent.acpRuntime || pageUiState.pageAiAcpRuntime || 'reasonix';
|
||||
}
|
||||
var hermesProfile = String(preferences['ai.agent.hermes.profile_id'] || '').trim();
|
||||
if (hermesProfile) pageAiSetActiveProfile(hermesProfile);
|
||||
var selected = preferences['ai.common.context_refs.default_selected'];
|
||||
if (selected && typeof selected === 'object' && !Array.isArray(selected)) {
|
||||
pageUiState.pageAiSelectedContextRefs = Object.assign({}, pageAiEnsureContextRefState(), selected);
|
||||
}
|
||||
var collapsedSkillGroups = preferences['ai.common.skills.groups.collapsed'];
|
||||
if (collapsedSkillGroups && typeof collapsedSkillGroups === 'object' && !Array.isArray(collapsedSkillGroups)) {
|
||||
pageUiState.pageAiCollapsedSkillGroups = Object.assign({}, collapsedSkillGroups);
|
||||
}
|
||||
} catch (_) {}
|
||||
renderPageAiControls();
|
||||
}
|
||||
@@ -531,22 +595,18 @@ export function createSidebarPageAiRuntime(context) {
|
||||
|
||||
function pageAiPageContextForRefs(pageContext, contextRefs) {
|
||||
var cloned = pageAiCloneJson(pageContext) || {};
|
||||
var kinds = pageAiContextKindsFromRefs(contextRefs);
|
||||
var aiContext = cloned.aiContext && typeof cloned.aiContext === 'object' ? cloned.aiContext : {};
|
||||
if (!kinds.current_page) {
|
||||
delete cloned.documentBlocks;
|
||||
delete aiContext.contextBlocks;
|
||||
delete aiContext.pageText;
|
||||
delete aiContext.pageXml;
|
||||
delete aiContext.truncated;
|
||||
delete aiContext.warnings;
|
||||
}
|
||||
if (!kinds.selection) {
|
||||
delete aiContext.selectedText;
|
||||
delete aiContext.selectedBlockIds;
|
||||
delete aiContext.selectedBlocks;
|
||||
delete aiContext.allowedTargetBlockIds;
|
||||
}
|
||||
delete cloned.documentBlocks;
|
||||
delete cloned.evidence;
|
||||
delete aiContext.contextBlocks;
|
||||
delete aiContext.pageText;
|
||||
delete aiContext.pageXml;
|
||||
delete aiContext.truncated;
|
||||
delete aiContext.warnings;
|
||||
delete aiContext.selectedText;
|
||||
delete aiContext.selectedBlockIds;
|
||||
delete aiContext.selectedBlocks;
|
||||
delete aiContext.allowedTargetBlockIds;
|
||||
cloned.aiContext = aiContext;
|
||||
return cloned;
|
||||
}
|
||||
@@ -754,7 +814,7 @@ export function createSidebarPageAiRuntime(context) {
|
||||
pageSubtreeSource: scope === 'page' ? (contextSnapshot.pageSubtreeSource || 'none') : 'scope:' + scope,
|
||||
evidence: selectedText ? [{ kind: 'selection', text: selectedText }] : null,
|
||||
pageOptions: scope === 'options' || scope === 'page' ? currentPageOptions() : null,
|
||||
contentAccess: 'mnote.doc.fetch',
|
||||
contentAccess: 'mnote.context.read_current_page',
|
||||
aiContext: aiContext
|
||||
},
|
||||
editorTarget: editorTarget,
|
||||
@@ -1532,13 +1592,17 @@ export function createSidebarPageAiRuntime(context) {
|
||||
skills: pageAiNormalizeArray(category && category.skills).map(function(skill) {
|
||||
return {
|
||||
name: String(skill && skill.name || '').trim(),
|
||||
id: String(skill && skill.id || skill && skill.name || '').trim(),
|
||||
title: String(skill && skill.title || skill && skill.name || skill && skill.id || '').trim(),
|
||||
description: String(skill && skill.description || '').trim(),
|
||||
enabled: skill && skill.enabled !== false,
|
||||
toggleable: skill && skill.toggleable !== false,
|
||||
source: String(skill && skill.source || 'local').trim() || 'local',
|
||||
origin: String(skill && skill.origin || '').trim(),
|
||||
createdBy: String(skill && skill.createdBy || '').trim(),
|
||||
patchCount: Number(skill && skill.patchCount || 0),
|
||||
modified: Boolean(skill && skill.modified)
|
||||
modified: Boolean(skill && skill.modified),
|
||||
category: String(category && category.name || '').trim()
|
||||
};
|
||||
})
|
||||
};
|
||||
@@ -1546,8 +1610,11 @@ export function createSidebarPageAiRuntime(context) {
|
||||
archived: archived.map(function(skill) {
|
||||
return {
|
||||
name: String(skill && skill.name || '').trim(),
|
||||
id: String(skill && skill.id || skill && skill.name || '').trim(),
|
||||
title: String(skill && skill.title || skill && skill.name || skill && skill.id || '').trim(),
|
||||
description: String(skill && skill.description || '').trim(),
|
||||
enabled: skill && skill.enabled !== false,
|
||||
toggleable: skill && skill.toggleable !== false,
|
||||
source: String(skill && skill.source || 'local').trim() || 'local',
|
||||
origin: String(skill && skill.origin || '').trim(),
|
||||
createdBy: String(skill && skill.createdBy || '').trim(),
|
||||
@@ -1565,8 +1632,11 @@ export function createSidebarPageAiRuntime(context) {
|
||||
result.push({
|
||||
category: category.name,
|
||||
name: skill.name,
|
||||
id: skill.id || skill.name,
|
||||
title: skill.title || skill.name,
|
||||
description: skill.description,
|
||||
enabled: skill.enabled !== false,
|
||||
toggleable: skill.toggleable !== false,
|
||||
source: skill.source || 'local',
|
||||
origin: skill.origin || '',
|
||||
createdBy: skill.createdBy || '',
|
||||
@@ -1666,6 +1736,74 @@ export function createSidebarPageAiRuntime(context) {
|
||||
}).filter(Boolean).join('\n');
|
||||
}
|
||||
|
||||
function pageAiParentRelativePath(path) {
|
||||
var normalized = String(path || '').trim().replace(/\\/g, '/').replace(/^\/+/, '');
|
||||
if (!normalized || normalized.indexOf('/') < 0) return '';
|
||||
return normalized.split('/').slice(0, -1).join('/');
|
||||
}
|
||||
|
||||
function pageAiChangedFileRelativePath(file) {
|
||||
return String(file && (file.path || file.relativePath || file.relative_path || file.filePath || file.file_path || '') || '').trim();
|
||||
}
|
||||
|
||||
function pageAiDispatchReceiptRefresh(receipt, changedFiles, fallbackAudit, runId, traceId) {
|
||||
var refresh = receipt && typeof receipt === 'object' && receipt.refresh && typeof receipt.refresh === 'object'
|
||||
? receipt.refresh
|
||||
: {};
|
||||
var files = pageAiNormalizeArray(
|
||||
receipt && typeof receipt === 'object' && receipt.changedFiles !== undefined
|
||||
? receipt.changedFiles
|
||||
: changedFiles
|
||||
);
|
||||
if (files.length) {
|
||||
var changedPaths = files.map(function(file) {
|
||||
var relativePath = pageAiChangedFileRelativePath(file);
|
||||
return {
|
||||
relativePath: relativePath,
|
||||
changeType: String(file && (file.changeType || file.change_type || 'modified') || 'modified')
|
||||
};
|
||||
}).filter(function(item) { return item.relativePath; });
|
||||
var affectedParents = changedPaths.map(function(item) {
|
||||
return { relativePath: pageAiParentRelativePath(item.relativePath), reason: 'agent-run-receipt' };
|
||||
}).filter(function(item, index, list) {
|
||||
return index === list.findIndex(function(candidate) { return candidate.relativePath === item.relativePath; });
|
||||
});
|
||||
if (changedPaths.length) {
|
||||
document.documentElement.setAttribute('data-mnote-page-ai-receipt-filetree-refresh', 'true');
|
||||
window.dispatchEvent(new CustomEvent('tree:local-folder-watch-batch', {
|
||||
detail: {
|
||||
payload: {
|
||||
schema: 'mnote.local_folder.watch_batch.v1',
|
||||
source: 'agent_run_receipt',
|
||||
runId: runId,
|
||||
rootUri: String((receipt && receipt.rootUri) || (fallbackAudit && fallbackAudit.rootUri) || currentRootUri() || ''),
|
||||
changedPaths: changedPaths,
|
||||
affectedParents: affectedParents
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
if (refresh.touchesCurrentFile === true) {
|
||||
var documentId = String(refresh.currentDocumentId || receipt && receipt.documentId || currentDocumentId() || '').trim();
|
||||
document.documentElement.setAttribute('data-mnote-page-ai-receipt-current-refresh', 'true');
|
||||
window.dispatchEvent(new CustomEvent('mnote:page-ai-tool-write-completed', {
|
||||
detail: {
|
||||
toolName: 'agent.run_receipt',
|
||||
normalizedToolName: 'agent.run_receipt',
|
||||
documentId: documentId,
|
||||
workspaceId: String(receipt && receipt.workspaceId || resolveWorkspaceId(document.body) || ''),
|
||||
rootUri: String(receipt && receipt.rootUri || fallbackAudit && fallbackAudit.rootUri || currentRootUri() || ''),
|
||||
changedFiles: files,
|
||||
receipt: receipt || null,
|
||||
runId: runId,
|
||||
traceId: traceId,
|
||||
toolCallId: runId + ':agent.run_receipt'
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
function pageAiToolEventDeepFindString(value, keys, depth) {
|
||||
if (!value || typeof value !== 'object' || depth > 5) return '';
|
||||
for (var index = 0; index < keys.length; index += 1) {
|
||||
@@ -1992,9 +2130,11 @@ export function createSidebarPageAiRuntime(context) {
|
||||
|
||||
function pageAiFilteredSkillEntries() {
|
||||
var query = String(pageUiState.pageAiSkillQuery || '').trim().toLowerCase();
|
||||
return pageAiSkillListEntries().filter(function(skill) {
|
||||
return pageAiAllSkillEntries().filter(function(skill) {
|
||||
if (skill.group === 'hermes' && pageAiHideHermesBuiltinSkills(pageAiCurrentProfile()) && skill.builtin === true) return false;
|
||||
if (!query) return true;
|
||||
return String(skill.name || '').toLowerCase().indexOf(query) >= 0
|
||||
return String(skill.name || skill.id || '').toLowerCase().indexOf(query) >= 0
|
||||
|| String(skill.title || '').toLowerCase().indexOf(query) >= 0
|
||||
|| String(skill.description || '').toLowerCase().indexOf(query) >= 0
|
||||
|| String(skill.category || '').toLowerCase().indexOf(query) >= 0
|
||||
|| pageAiSkillOriginLabel(skill).toLowerCase().indexOf(query) >= 0;
|
||||
@@ -2018,6 +2158,162 @@ export function createSidebarPageAiRuntime(context) {
|
||||
return '本地';
|
||||
}
|
||||
|
||||
function pageAiSkillPreferenceKey(group, profile) {
|
||||
var groupName = String(group || '').trim();
|
||||
if (groupName === 'mnote') return 'ai.common.skills.mnote.enabled';
|
||||
if (groupName === 'reasonix') return 'ai.agent.reasonix.skills.enabled';
|
||||
if (groupName === 'hermes') {
|
||||
var nextProfile = String(profile || pageAiCurrentProfile() || 'default').trim() || 'default';
|
||||
return 'ai.agent.hermes.profile.' + nextProfile + '.skills.enabled';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function pageAiSkillPreferenceTable(group, profile) {
|
||||
var key = pageAiSkillPreferenceKey(group, profile);
|
||||
var preferences = pageUiState.pageAiSkillPreferences || {};
|
||||
var value = preferences[key];
|
||||
if (value && typeof value === 'object' && !Array.isArray(value)) return value;
|
||||
return {};
|
||||
}
|
||||
|
||||
function pageAiHermesHideBuiltinPreferenceKey(profile) {
|
||||
var nextProfile = String(profile || pageAiCurrentProfile() || 'default').trim() || 'default';
|
||||
return 'ai.agent.hermes.profile.' + nextProfile + '.skills.hide_builtin';
|
||||
}
|
||||
|
||||
function pageAiHideHermesBuiltinSkills(profile) {
|
||||
var preferences = pageUiState.pageAiSkillPreferences || {};
|
||||
return preferences[pageAiHermesHideBuiltinPreferenceKey(profile)] === true;
|
||||
}
|
||||
|
||||
function pageAiSetHideHermesBuiltinSkills(enabled, profile) {
|
||||
var key = pageAiHermesHideBuiltinPreferenceKey(profile);
|
||||
var preferences = Object.assign({}, pageUiState.pageAiSkillPreferences || {});
|
||||
preferences[key] = Boolean(enabled);
|
||||
pageUiState.pageAiSkillPreferences = preferences;
|
||||
pageAiPersistRawAiPreference(key, Boolean(enabled));
|
||||
renderPageAiControls();
|
||||
}
|
||||
|
||||
function pageAiSkillIsBuiltin(skill) {
|
||||
var origin = String(skill && skill.origin || '').trim();
|
||||
var source = String(skill && skill.source || '').trim();
|
||||
return origin === 'builtin' || source === 'builtin';
|
||||
}
|
||||
|
||||
function pageAiToggleableSkillEntries(group, profile) {
|
||||
var catalog = pageUiState.pageAiSkillCatalogs && pageUiState.pageAiSkillCatalogs[group]
|
||||
? pageUiState.pageAiSkillCatalogs[group]
|
||||
: { categories: [], archived: [] };
|
||||
var overrides = pageAiSkillPreferenceTable(group, profile);
|
||||
var result = [];
|
||||
pageAiNormalizeArray(catalog.categories).forEach(function(category) {
|
||||
pageAiNormalizeArray(category.skills).forEach(function(skill) {
|
||||
var id = String(skill.id || skill.name || '').trim();
|
||||
if (!id) return;
|
||||
result.push({
|
||||
group: group,
|
||||
profile: profile || '',
|
||||
category: category.name,
|
||||
id: id,
|
||||
name: skill.name || id,
|
||||
title: skill.title || skill.name || id,
|
||||
description: skill.description || '',
|
||||
enabled: group === 'hermes' ? skill.enabled !== false : overrides[id] !== false,
|
||||
toggleable: skill.toggleable !== false,
|
||||
source: skill.source || group,
|
||||
origin: skill.origin || '',
|
||||
readOnly: skill.readOnly === true,
|
||||
builtin: pageAiSkillIsBuiltin(skill),
|
||||
toolNames: skill.toolNames || [],
|
||||
requiresContextRefs: skill.requiresContextRefs || []
|
||||
});
|
||||
});
|
||||
});
|
||||
pageAiNormalizeArray(catalog.archived).forEach(function(skill) {
|
||||
var id = String(skill.id || skill.name || '').trim();
|
||||
if (!id) return;
|
||||
result.push({
|
||||
group: group,
|
||||
profile: profile || '',
|
||||
category: 'archived',
|
||||
id: id,
|
||||
name: skill.name || id,
|
||||
title: skill.title || skill.name || id,
|
||||
description: skill.description || '',
|
||||
enabled: group === 'hermes' ? skill.enabled !== false : overrides[id] !== false,
|
||||
toggleable: skill.toggleable !== false,
|
||||
source: skill.source || group,
|
||||
origin: skill.origin || '',
|
||||
readOnly: skill.readOnly === true,
|
||||
builtin: pageAiSkillIsBuiltin(skill),
|
||||
toolNames: skill.toolNames || [],
|
||||
requiresContextRefs: skill.requiresContextRefs || []
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function pageAiAllSkillEntries() {
|
||||
return []
|
||||
.concat(pageAiToggleableSkillEntries('mnote', ''))
|
||||
.concat(pageAiToggleableSkillEntries('reasonix', ''))
|
||||
.concat(pageAiToggleableSkillEntries('hermes', pageAiCurrentProfile()));
|
||||
}
|
||||
|
||||
function pageAiSkillGroupCollapsed(group) {
|
||||
var table = pageUiState.pageAiCollapsedSkillGroups || {};
|
||||
return table[String(group || '').trim()] === true;
|
||||
}
|
||||
|
||||
function pageAiToggleSkillGroup(group) {
|
||||
var normalized = String(group || '').trim();
|
||||
if (!normalized) return;
|
||||
var table = Object.assign({}, pageUiState.pageAiCollapsedSkillGroups || {});
|
||||
table[normalized] = table[normalized] !== true;
|
||||
pageUiState.pageAiCollapsedSkillGroups = table;
|
||||
pageAiPersistRawAiPreference('ai.common.skills.groups.collapsed', table);
|
||||
renderPageAiControls();
|
||||
}
|
||||
|
||||
function pageAiSetSkillPreference(group, skillId, enabled, profile) {
|
||||
var key = pageAiSkillPreferenceKey(group, profile);
|
||||
if (!key || !skillId) return;
|
||||
var preferences = Object.assign({}, pageUiState.pageAiSkillPreferences || {});
|
||||
var current = preferences[key] && typeof preferences[key] === 'object' && !Array.isArray(preferences[key])
|
||||
? Object.assign({}, preferences[key])
|
||||
: {};
|
||||
current[skillId] = Boolean(enabled);
|
||||
preferences[key] = current;
|
||||
pageUiState.pageAiSkillPreferences = preferences;
|
||||
pageAiPersistRawAiPreference(key, current);
|
||||
}
|
||||
|
||||
function pageAiSkillEnabled(skill) {
|
||||
return skill.enabled !== false;
|
||||
}
|
||||
|
||||
function pageAiLoadSkillCatalog(runtime, profile) {
|
||||
var params = new URLSearchParams();
|
||||
params.set('runtime', runtime);
|
||||
if (runtime === 'mnote') {
|
||||
params.set('agentId', pageUiState.pageAiAgentId || 'reasonix');
|
||||
} else if (runtime === 'reasonix') {
|
||||
params.set('runtime', 'reasonix');
|
||||
} else if (runtime === 'hermes' && profile) {
|
||||
params.set('profile', profile);
|
||||
}
|
||||
return fetch('/api/hermes/client/skills?' + params.toString(), {
|
||||
headers: { 'accept': 'application/json' }
|
||||
}).then(function(response) {
|
||||
return response.json().catch(function(){ return null; }).then(function(payload) {
|
||||
if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'skills_failed_' + response.status));
|
||||
return pageAiNormalizeSkills(payload);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function pageAiLoadProfiles() {
|
||||
try {
|
||||
var response = await fetch('/api/hermes/client/profiles', {
|
||||
@@ -2050,6 +2346,12 @@ export function createSidebarPageAiRuntime(context) {
|
||||
var next = String(profileName || '').trim();
|
||||
if (!next) return;
|
||||
pageAiSetActiveProfile(next);
|
||||
pageAiPersistRawAiPreference('ai.agent.hermes.profile_id', next);
|
||||
pageUiState.pageAiSkills = { categories: [], archived: [] };
|
||||
pageUiState.pageAiSkillCatalogs = Object.assign({}, pageUiState.pageAiSkillCatalogs || {}, {
|
||||
hermes: { categories: [], archived: [] }
|
||||
});
|
||||
pageUiState.pageAiSkillError = '';
|
||||
renderPageAiProviderButtons();
|
||||
renderPageAiControls();
|
||||
try {
|
||||
@@ -2121,59 +2423,81 @@ export function createSidebarPageAiRuntime(context) {
|
||||
}
|
||||
|
||||
async function pageAiLoadSkills() {
|
||||
var requestedProfile = pageAiCurrentProfile();
|
||||
var loadSeq = (Number(pageUiState.pageAiSkillLoadSeq || 0) || 0) + 1;
|
||||
pageUiState.pageAiSkillLoadSeq = loadSeq;
|
||||
try {
|
||||
var runtime = String(pageUiState.pageAiAcpRuntime || 'reasonix').trim();
|
||||
var params = runtime === 'reasonix'
|
||||
? 'runtime=reasonix'
|
||||
: 'profile=' + encodeURIComponent(pageAiCurrentProfile());
|
||||
var response = await fetch('/api/hermes/client/skills?' + params, {
|
||||
headers: { 'accept': 'application/json' }
|
||||
});
|
||||
var payload = await response.json().catch(function(){ return null; });
|
||||
if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'skills_failed_' + response.status));
|
||||
pageUiState.pageAiSkills = pageAiNormalizeSkills(payload);
|
||||
var catalogs = await Promise.all([
|
||||
pageAiLoadSkillCatalog('mnote', ''),
|
||||
pageAiLoadSkillCatalog('reasonix', ''),
|
||||
pageAiLoadSkillCatalog('hermes', requestedProfile)
|
||||
]);
|
||||
if (pageUiState.pageAiSkillLoadSeq !== loadSeq || pageAiCurrentProfile() !== requestedProfile) return;
|
||||
pageUiState.pageAiSkillCatalogs = {
|
||||
mnote: catalogs[0],
|
||||
reasonix: catalogs[1],
|
||||
hermes: catalogs[2]
|
||||
};
|
||||
pageUiState.pageAiSkills = catalogs[2];
|
||||
pageUiState.pageAiSkillError = '';
|
||||
} catch (error) {
|
||||
if (pageUiState.pageAiSkillLoadSeq !== loadSeq || pageAiCurrentProfile() !== requestedProfile) return;
|
||||
pageUiState.pageAiSkillError = error instanceof Error ? error.message : String(error);
|
||||
pageUiState.pageAiSkills = { categories: [], archived: [] };
|
||||
pageUiState.pageAiSkillCatalogs = pageUiState.pageAiSkillCatalogs || { mnote: { categories: [], archived: [] }, reasonix: { categories: [], archived: [] }, hermes: { categories: [], archived: [] } };
|
||||
}
|
||||
renderPageAiControls();
|
||||
renderPageAiConversation();
|
||||
}
|
||||
|
||||
async function pageAiToggleSkill(skillName, enabled) {
|
||||
async function pageAiToggleSkill(skillName, enabled, group, profile) {
|
||||
var name = String(skillName || '').trim();
|
||||
if (!name) return;
|
||||
if (String(pageUiState.pageAiAcpRuntime || 'reasonix').trim() === 'reasonix') return;
|
||||
var skillGroup = String(group || 'hermes').trim() || 'hermes';
|
||||
var skillProfile = String(profile || pageAiCurrentProfile() || '').trim();
|
||||
if (skillGroup === 'mnote' || skillGroup === 'reasonix') {
|
||||
pageAiSetSkillPreference(skillGroup, name, Boolean(enabled), skillProfile);
|
||||
document.documentElement.setAttribute('data-mnote-page-ai-skill-toggled', skillGroup + ':' + name);
|
||||
renderPageAiControls();
|
||||
return;
|
||||
}
|
||||
var previous = null;
|
||||
pageAiSkillListEntries().forEach(function(skill) {
|
||||
if (skill.name === name && previous == null) previous = skill.enabled !== false;
|
||||
pageAiToggleableSkillEntries('hermes', pageAiCurrentProfile()).forEach(function(skill) {
|
||||
if (skill.id === name || skill.name === name) previous = skill.enabled !== false;
|
||||
});
|
||||
try {
|
||||
var response = await fetch('/api/hermes/client/skills/toggle', {
|
||||
method: 'PUT',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
profile: pageAiCurrentProfile(),
|
||||
profile: skillProfile || pageAiCurrentProfile(),
|
||||
name: name,
|
||||
enabled: Boolean(enabled)
|
||||
})
|
||||
});
|
||||
var payload = await response.json().catch(function(){ return null; });
|
||||
if (!response.ok) throw new Error(pageAiErrorMessage(payload, 'skill_toggle_failed_' + response.status));
|
||||
pageAiNormalizeArray(pageUiState.pageAiSkills.categories).forEach(function(category) {
|
||||
var hermesCatalog = pageUiState.pageAiSkillCatalogs && pageUiState.pageAiSkillCatalogs.hermes
|
||||
? pageUiState.pageAiSkillCatalogs.hermes
|
||||
: pageUiState.pageAiSkills;
|
||||
pageAiNormalizeArray(hermesCatalog.categories).forEach(function(category) {
|
||||
pageAiNormalizeArray(category.skills).forEach(function(skill) {
|
||||
if (skill.name === name) skill.enabled = Boolean(enabled);
|
||||
if (skill.name === name || skill.id === name) skill.enabled = Boolean(enabled);
|
||||
});
|
||||
});
|
||||
pageUiState.pageAiSkills = hermesCatalog;
|
||||
pageAiSetSkillPreference('hermes', name, Boolean(enabled), skillProfile || pageAiCurrentProfile());
|
||||
document.documentElement.setAttribute('data-mnote-page-ai-skill-toggled', name);
|
||||
pageUiState.pageAiSkillError = '';
|
||||
} catch (error) {
|
||||
pageUiState.pageAiSkillError = error instanceof Error ? error.message : String(error);
|
||||
if (previous != null) {
|
||||
pageAiNormalizeArray(pageUiState.pageAiSkills.categories).forEach(function(category) {
|
||||
var rollbackCatalog = pageUiState.pageAiSkillCatalogs && pageUiState.pageAiSkillCatalogs.hermes
|
||||
? pageUiState.pageAiSkillCatalogs.hermes
|
||||
: pageUiState.pageAiSkills;
|
||||
pageAiNormalizeArray(rollbackCatalog.categories).forEach(function(category) {
|
||||
pageAiNormalizeArray(category.skills).forEach(function(skill) {
|
||||
if (skill.name === name) skill.enabled = previous;
|
||||
if (skill.name === name || skill.id === name) skill.enabled = previous;
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -2240,25 +2564,85 @@ export function createSidebarPageAiRuntime(context) {
|
||||
var drawer = ensurePageAiDrawer();
|
||||
var isAcp = pageUiState.pageAiAcpRuntime !== '';
|
||||
var activeAgentId = pageAiCurrentAgentId();
|
||||
var activeProfile = pageAiRunProfile();
|
||||
var activeProfile = pageAiCurrentProfile();
|
||||
drawer.setAttribute('data-page-ai-page', pageUiState.pageAiPage || 'chat');
|
||||
drawer.setAttribute('data-page-ai-active-agent-id', activeAgentId);
|
||||
drawer.setAttribute('data-mnote-acp-runtime', pageUiState.pageAiAcpRuntime || 'reasonix');
|
||||
document.documentElement.setAttribute('data-mnote-page-ai-agent-id', activeAgentId);
|
||||
var agentSelector = drawer.querySelector('[data-page-ai-agent-selector]');
|
||||
if (agentSelector instanceof HTMLElement) {
|
||||
agentSelector.innerHTML = PAGE_AI_AGENT_REGISTRY.map(function(agent) {
|
||||
var active = agent.id === activeAgentId;
|
||||
return '<button type="button" class="wolai-page-ai-tab' + (active ? ' is-active' : '') + '" data-page-ai-agent-id="' + escapeHtml(agent.id) + '" aria-pressed="' + (active ? 'true' : 'false') + '">' + escapeHtml(agent.label) + '</button>';
|
||||
}).join('');
|
||||
var agentButton = drawer.querySelector('[data-page-ai-agent-button]');
|
||||
var agentPopoverId = 'mnote-page-ai-agent-popover';
|
||||
if (agentButton instanceof HTMLElement) {
|
||||
var activeAgent = pageAiAgentRecord(activeAgentId);
|
||||
agentButton.textContent = 'AI';
|
||||
agentButton.setAttribute('aria-expanded', pageUiState.pageAiAgentPopoverOpen ? 'true' : 'false');
|
||||
agentButton.setAttribute('aria-controls', agentPopoverId);
|
||||
agentButton.setAttribute('data-page-ai-agent-summary', activeAgent.label);
|
||||
agentButton.setAttribute('aria-label', 'Agent:' + activeAgent.label);
|
||||
agentButton.setAttribute('title', 'Agent:' + activeAgent.label);
|
||||
}
|
||||
var contextRefs = drawer.querySelector('[data-page-ai-context-refs]');
|
||||
if (contextRefs instanceof HTMLElement) {
|
||||
var agentPopover = drawer.querySelector('[data-page-ai-agent-popover]');
|
||||
if (agentPopover instanceof HTMLElement) {
|
||||
agentPopover.id = agentPopoverId;
|
||||
agentPopover.hidden = !pageUiState.pageAiAgentPopoverOpen;
|
||||
agentPopover.innerHTML = '' +
|
||||
'<div class="wolai-page-ai-context-popover-head">' +
|
||||
'<strong>选择 Agent</strong>' +
|
||||
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-action="close-agent-popover">完成</button>' +
|
||||
'</div>' +
|
||||
'<div class="wolai-page-ai-agent-option-list">' +
|
||||
PAGE_AI_AGENT_REGISTRY.map(function(agent) {
|
||||
var active = agent.id === activeAgentId;
|
||||
var detail = agent.canWriteFiles ? '可读取并在授权目录内写文件' : '只聊天,不申请文件写权限';
|
||||
return '' +
|
||||
'<button type="button" class="wolai-page-ai-agent-option' + (active ? ' is-active' : '') + '" data-page-ai-agent-id="' + escapeHtml(agent.id) + '" aria-pressed="' + (active ? 'true' : 'false') + '">' +
|
||||
'<span class="wolai-page-ai-agent-option-label">' + escapeHtml(agent.label) + '</span>' +
|
||||
'<span class="wolai-page-ai-agent-option-detail">' + escapeHtml(detail) + '</span>' +
|
||||
'</button>';
|
||||
}).join('') +
|
||||
'</div>' +
|
||||
(activeAgentId === 'hermes'
|
||||
? '<label class="wolai-page-ai-agent-profile"><span>Hermes profile</span><select data-page-ai-profile-select></select></label>'
|
||||
: '');
|
||||
}
|
||||
var contextButton = drawer.querySelector('[data-page-ai-context-button]');
|
||||
var contextPopoverId = 'mnote-page-ai-context-popover';
|
||||
if (contextButton instanceof HTMLElement) {
|
||||
var summary = pageAiContextButtonSummary();
|
||||
contextButton.textContent = '⇅';
|
||||
contextButton.setAttribute('aria-expanded', pageUiState.pageAiContextPopoverOpen ? 'true' : 'false');
|
||||
contextButton.setAttribute('aria-controls', contextPopoverId);
|
||||
contextButton.setAttribute('data-page-ai-context-summary', summary);
|
||||
contextButton.setAttribute('aria-label', '上下文:' + summary);
|
||||
contextButton.setAttribute('title', '上下文:' + summary);
|
||||
}
|
||||
var contextPopover = drawer.querySelector('[data-page-ai-context-popover]');
|
||||
if (contextPopover instanceof HTMLElement) {
|
||||
var selectedRefs = pageAiEnsureContextRefState();
|
||||
contextRefs.innerHTML = PAGE_AI_CONTEXT_REF_REGISTRY.map(function(ref) {
|
||||
var active = selectedRefs[ref.id] !== false && selectedRefs[ref.id] === true;
|
||||
return '<button type="button" class="wolai-page-ai-ghost' + (active ? ' is-active' : '') + '" data-page-ai-context-ref="' + escapeHtml(ref.id) + '" aria-pressed="' + (active ? 'true' : 'false') + '">' + escapeHtml(ref.label) + '</button>';
|
||||
}).join('');
|
||||
contextPopover.id = contextPopoverId;
|
||||
contextPopover.hidden = !pageUiState.pageAiContextPopoverOpen;
|
||||
contextPopover.innerHTML = '' +
|
||||
'<div class="wolai-page-ai-context-popover-head">' +
|
||||
'<strong>发送给 AI 的上下文</strong>' +
|
||||
'<button type="button" class="wolai-page-ai-ghost" data-page-ai-action="close-context-popover">完成</button>' +
|
||||
'</div>' +
|
||||
'<div class="wolai-page-ai-context-option-list">' +
|
||||
PAGE_AI_CONTEXT_REF_REGISTRY.map(function(ref) {
|
||||
var checked = selectedRefs[ref.id] === true;
|
||||
var disabled = pageAiContextRefDisabled(ref.id);
|
||||
return '' +
|
||||
'<label class="wolai-page-ai-context-option' + (disabled ? ' is-disabled' : '') + '">' +
|
||||
'<input type="checkbox" data-page-ai-context-ref="' + escapeHtml(ref.id) + '"' + (checked ? ' checked' : '') + (disabled ? ' disabled' : '') + ' />' +
|
||||
'<span class="wolai-page-ai-context-option-main">' +
|
||||
'<span class="wolai-page-ai-context-option-label">' + escapeHtml(ref.label) + '</span>' +
|
||||
'<span class="wolai-page-ai-context-option-detail">' + escapeHtml(pageAiContextRefDetail(ref.id)) + '</span>' +
|
||||
'</span>' +
|
||||
'</label>';
|
||||
}).join('') +
|
||||
'</div>' +
|
||||
'<div class="wolai-page-ai-context-popover-foot">' +
|
||||
'<span>授权区域</span>' +
|
||||
'<span>' + escapeHtml(pageAiBuildAllowedRoots().length ? pageAiBuildAllowedRoots().map(function(root) { return root.permission + ' · ' + root.rootUri.replace(/^file:\/\//, ''); }).join(' / ') : '未选择授权区域') + '</span>' +
|
||||
'</div>';
|
||||
}
|
||||
var allowedRootsNode = drawer.querySelector('[data-page-ai-allowed-roots]');
|
||||
if (allowedRootsNode instanceof HTMLElement) {
|
||||
@@ -2424,29 +2808,40 @@ export function createSidebarPageAiRuntime(context) {
|
||||
if (skillList instanceof HTMLElement) {
|
||||
var skills = pageAiFilteredSkillEntries();
|
||||
if (!skills.length) {
|
||||
var emptyText = String(pageUiState.pageAiAcpRuntime || 'reasonix').trim() === 'reasonix'
|
||||
? '没有匹配的 Reasonix skill。'
|
||||
: '没有匹配的 Hermes skill。';
|
||||
skillList.innerHTML = '<div class="wolai-page-ai-empty">' + escapeHtml(emptyText) + '</div>';
|
||||
skillList.innerHTML = '<div class="wolai-page-ai-empty">没有匹配的技能。</div>';
|
||||
} else {
|
||||
skillList.innerHTML = skills.map(function(skill) {
|
||||
var sourceText = pageAiSkillOriginLabel(skill) + (skill.modified ? ' · modified' : '');
|
||||
var description = String(skill.description || '').trim();
|
||||
var hasDescription = description && description !== '---' && description !== '无描述';
|
||||
var canToggle = skill.toggleable !== false && String(pageUiState.pageAiAcpRuntime || 'reasonix').trim() !== 'reasonix';
|
||||
var groupLabels = { mnote: 'MNote 内置技能', reasonix: 'Reasonix 技能', hermes: 'Hermes 技能' };
|
||||
skillList.innerHTML = ['mnote', 'reasonix', 'hermes'].map(function(group) {
|
||||
var groupSkills = skills.filter(function(skill) { return skill.group === group; });
|
||||
if (!groupSkills.length) return '';
|
||||
var headingExtra = group === 'hermes' ? ' · ' + pageAiCurrentProfile() : '';
|
||||
var collapsed = pageAiSkillGroupCollapsed(group);
|
||||
return '' +
|
||||
'<div class="wolai-page-ai-skill-row">' +
|
||||
'<div class="wolai-page-ai-skill-copy">' +
|
||||
'<div class="wolai-page-ai-skill-main">' +
|
||||
'<div class="wolai-page-ai-skill-name">' + escapeHtml(skill.name) + '</div>' +
|
||||
'<div class="wolai-page-ai-skill-source">' + escapeHtml(sourceText) + '</div>' +
|
||||
'</div>' +
|
||||
(hasDescription ? '<div class="wolai-page-ai-skill-desc">' + escapeHtml(description) + '</div>' : '') +
|
||||
'</div>' +
|
||||
'<button type="button" class="wolai-page-ai-skill-switch' + (skill.enabled !== false ? ' is-on' : '') + '" data-page-ai-skill-toggle="' + escapeHtml(skill.name) + '" aria-pressed="' + (skill.enabled !== false ? 'true' : 'false') + '"' + (canToggle ? '' : ' disabled title="Reasonix skills 当前为只读展示"') + '>' +
|
||||
'<span></span>' +
|
||||
'<section class="wolai-page-ai-skill-group' + (collapsed ? ' is-collapsed' : '') + '" data-page-ai-skill-group="' + escapeHtml(group) + '">' +
|
||||
'<button type="button" class="wolai-page-ai-skill-group-head" data-page-ai-skill-group-toggle="' + escapeHtml(group) + '" aria-expanded="' + (collapsed ? 'false' : 'true') + '">' +
|
||||
'<span class="wolai-page-ai-skill-group-title"><span class="wolai-page-ai-skill-group-chevron">' + (collapsed ? '›' : '⌄') + '</span>' + escapeHtml(groupLabels[group] || group) + '</span>' +
|
||||
'<span>' + escapeHtml(String(groupSkills.length) + headingExtra) + '</span>' +
|
||||
'</button>' +
|
||||
'</div>';
|
||||
(collapsed ? '' : groupSkills.map(function(skill) {
|
||||
var sourceText = pageAiSkillOriginLabel(skill) + (skill.modified ? ' · modified' : '');
|
||||
var description = String(skill.description || '').trim();
|
||||
var hasDescription = description && description !== '---' && description !== '无描述';
|
||||
var displayName = String(skill.title || skill.name || skill.id || '').trim();
|
||||
return '' +
|
||||
'<div class="wolai-page-ai-skill-row">' +
|
||||
'<div class="wolai-page-ai-skill-copy">' +
|
||||
'<div class="wolai-page-ai-skill-main">' +
|
||||
'<div class="wolai-page-ai-skill-name">' + escapeHtml(displayName) + '</div>' +
|
||||
'<div class="wolai-page-ai-skill-source">' + escapeHtml(sourceText) + '</div>' +
|
||||
'</div>' +
|
||||
(hasDescription ? '<div class="wolai-page-ai-skill-desc">' + escapeHtml(description) + '</div>' : '') +
|
||||
'</div>' +
|
||||
'<button type="button" class="wolai-page-ai-skill-switch' + (pageAiSkillEnabled(skill) ? ' is-on' : '') + '" data-page-ai-skill-toggle="' + escapeHtml(skill.id || skill.name) + '" data-page-ai-skill-group="' + escapeHtml(group) + '" data-page-ai-skill-profile="' + escapeHtml(skill.profile || '') + '" aria-pressed="' + (pageAiSkillEnabled(skill) ? 'true' : 'false') + '"' + (skill.toggleable === false ? ' disabled' : '') + '>' +
|
||||
'<span></span>' +
|
||||
'</button>' +
|
||||
'</div>';
|
||||
}).join('')) +
|
||||
'</section>';
|
||||
}).join('');
|
||||
}
|
||||
}
|
||||
@@ -2574,6 +2969,14 @@ export function createSidebarPageAiRuntime(context) {
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="wolai-page-ai-header-actions">' +
|
||||
'<button type="button" class="wolai-page-ai-icon" data-page-ai-tab="skills" aria-label="技能" title="技能">' +
|
||||
'<svg class="wolai-page-ai-icon-svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false">' +
|
||||
'<path d="M12 3l1.35 4.15L17.5 8.5l-4.15 1.35L12 14l-1.35-4.15L6.5 8.5l4.15-1.35L12 3z" />' +
|
||||
'<path d="M18.5 13l.8 2.4 2.2.8-2.2.8-.8 2.4-.8-2.4-2.2-.8 2.2-.8.8-2.4z" />' +
|
||||
'<path d="M5.5 14l.65 1.85L8 16.5l-1.85.65L5.5 19l-.65-1.85L3 16.5l1.85-.65L5.5 14z" />' +
|
||||
'</svg>' +
|
||||
'</button>' +
|
||||
'<button type="button" class="wolai-page-ai-icon" data-page-ai-action="history" aria-label="历史会话">⌕</button>' +
|
||||
'<button type="button" class="wolai-page-ai-icon" data-page-ai-tab="agent" aria-label="打开页面 AI 设置">⚙</button>' +
|
||||
'<button type="button" class="wolai-page-ai-icon" data-page-ai-action="close" aria-label="关闭页面 AI">×</button>' +
|
||||
'</div>' +
|
||||
@@ -2581,9 +2984,9 @@ export function createSidebarPageAiRuntime(context) {
|
||||
'<div class="wolai-page-ai-body">' +
|
||||
'<section class="wolai-page-ai-panel-section" data-page-ai-panel="chat">' +
|
||||
'<div class="wolai-page-ai-chat-meta">' +
|
||||
'<button type="button" class="wolai-page-ai-session-button" data-page-ai-action="history">' +
|
||||
'<span class="wolai-page-ai-session-summary">' +
|
||||
'<span data-page-ai-session-status>等待 AI session</span>' +
|
||||
'</button>' +
|
||||
'</span>' +
|
||||
'<span data-page-ai-context-scope-label>当前页</span>' +
|
||||
'<span data-page-ai-run-status>空闲</span>' +
|
||||
'</div>' +
|
||||
@@ -2681,6 +3084,14 @@ export function createSidebarPageAiRuntime(context) {
|
||||
'<span>搜索技能</span>' +
|
||||
'<input type="search" data-page-ai-skill-search placeholder="搜索技能…" />' +
|
||||
'</label>' +
|
||||
'<label class="wolai-page-ai-profile-select" data-page-ai-skills-hermes-profile>' +
|
||||
'<span>Hermes profile</span>' +
|
||||
'<select data-page-ai-profile-select></select>' +
|
||||
'</label>' +
|
||||
'<label class="wolai-page-ai-skill-filter-toggle">' +
|
||||
'<input type="checkbox" data-page-ai-hide-hermes-builtin' + (pageAiHideHermesBuiltinSkills(pageAiCurrentProfile()) ? ' checked' : '') + ' />' +
|
||||
'<span>隐藏 Hermes 内置</span>' +
|
||||
'</label>' +
|
||||
'</div>' +
|
||||
'<div class="wolai-page-ai-inline-error" data-page-ai-skill-error hidden></div>' +
|
||||
'<div class="wolai-page-ai-skill-list" data-page-ai-skill-list></div>' +
|
||||
@@ -2742,13 +3153,17 @@ export function createSidebarPageAiRuntime(context) {
|
||||
'</div>' +
|
||||
'<div class="wolai-page-ai-footer">' +
|
||||
'<div class="wolai-page-ai-composer">' +
|
||||
'<div class="wolai-page-ai-agent-selector" data-page-ai-agent-selector></div>' +
|
||||
'<div class="wolai-page-ai-context-refs" data-page-ai-context-refs></div>' +
|
||||
'<div class="wolai-page-ai-allowed-roots" data-page-ai-allowed-roots></div>' +
|
||||
'<textarea class="wolai-page-ai-input" data-page-ai-input rows="3" placeholder="输入消息…"></textarea>' +
|
||||
'<div class="wolai-page-ai-composer-bar">' +
|
||||
'<button type="button" class="wolai-page-ai-tool-button" data-page-ai-action="new-session" title="新会话">+</button>' +
|
||||
'<button type="button" class="wolai-page-ai-tool-button" data-page-ai-action="history" title="历史会话">⌕</button>' +
|
||||
'<div class="wolai-page-ai-agent-picker">' +
|
||||
'<button type="button" class="wolai-page-ai-tool-button wolai-page-ai-agent-button" data-page-ai-agent-button aria-expanded="false" aria-label="Agent:Reasonix" title="Agent:Reasonix">AI</button>' +
|
||||
'<div class="wolai-page-ai-agent-popover" data-page-ai-agent-popover hidden></div>' +
|
||||
'</div>' +
|
||||
'<div class="wolai-page-ai-context-picker">' +
|
||||
'<button type="button" class="wolai-page-ai-tool-button wolai-page-ai-context-button" data-page-ai-context-button aria-expanded="false" aria-label="上下文:当前页 + 打开资源" title="上下文:当前页 + 打开资源">⇅</button>' +
|
||||
'<div class="wolai-page-ai-context-popover" data-page-ai-context-popover hidden></div>' +
|
||||
'</div>' +
|
||||
'<span class="wolai-page-ai-composer-spacer"></span>' +
|
||||
'<button type="button" class="wolai-page-ai-stop" data-page-ai-action="stop-run" disabled>停止</button>' +
|
||||
'<button type="button" class="wolai-page-ai-send" data-page-ai-action="send" aria-label="发送">发送</button>' +
|
||||
@@ -3177,13 +3592,18 @@ export function createSidebarPageAiRuntime(context) {
|
||||
contextScope: pageUiState.pageAiContextScope,
|
||||
contextRefs: contextRefs,
|
||||
allowedRoots: allowedRoots,
|
||||
skillPreferences: {
|
||||
mnote: pageAiSkillPreferenceTable('mnote', ''),
|
||||
reasonix: pageAiSkillPreferenceTable('reasonix', ''),
|
||||
hermes: pageAiSkillPreferenceTable('hermes', pageAiCurrentProfile())
|
||||
},
|
||||
message: prompt,
|
||||
model: pageAiMnoteToolModel(),
|
||||
pageContext: requestPageContext,
|
||||
editorTarget: scopedContext.editorTarget,
|
||||
runTargetSnapshot: runTargetSnapshot,
|
||||
selectedBlockId: scopedContext.selectedBlockId,
|
||||
selectedText: pageAiContextKindsFromRefs(contextRefs).selection ? scopedContext.selectedText : '',
|
||||
selectedText: '',
|
||||
traceId: 'page-ai-run-' + Date.now().toString(36)
|
||||
})
|
||||
});
|
||||
@@ -3282,6 +3702,9 @@ export function createSidebarPageAiRuntime(context) {
|
||||
if (completedSession) completedSession.usage = completed.usage;
|
||||
}
|
||||
var agentAudit = completed && completed.agentAudit && typeof completed.agentAudit === 'object' ? completed.agentAudit : null;
|
||||
var agentRunReceipt = agentAudit && agentAudit.agentRunReceipt && typeof agentAudit.agentRunReceipt === 'object'
|
||||
? agentAudit.agentRunReceipt
|
||||
: null;
|
||||
var changedFiles = pageAiNormalizeArray(agentAudit && agentAudit.changedFiles).map(function(file) {
|
||||
if (!file || typeof file !== 'object') return file;
|
||||
return Object.assign({
|
||||
@@ -3307,26 +3730,10 @@ export function createSidebarPageAiRuntime(context) {
|
||||
traceId: runTraceId,
|
||||
auditId: String(agentAudit.eventId || '')
|
||||
});
|
||||
}
|
||||
if (agentRunReceipt || changedFiles.length) {
|
||||
try {
|
||||
var currentId = currentDocumentId();
|
||||
var currentPath = String(currentId || '').replace(/^local-md:/, '').replace(/~2F/g, '/');
|
||||
var touchesCurrent = changedFiles.some(function(file) {
|
||||
var path = String(file && (file.documentId || file.path || file.filePath || '') || '');
|
||||
return path === currentId || (currentPath && path.indexOf(currentPath) >= 0);
|
||||
});
|
||||
if (touchesCurrent) {
|
||||
window.dispatchEvent(new CustomEvent('mnote:page-ai-tool-write-completed', {
|
||||
detail: {
|
||||
toolName: 'agent.changed_files',
|
||||
normalizedToolName: 'agent.changed_files',
|
||||
documentId: currentId,
|
||||
workspaceId: resolveWorkspaceId(document.body),
|
||||
runId: runId,
|
||||
traceId: runTraceId,
|
||||
toolCallId: runId + ':agent.changed_files'
|
||||
}
|
||||
}));
|
||||
}
|
||||
pageAiDispatchReceiptRefresh(agentRunReceipt, changedFiles, agentAudit, runId, runTraceId);
|
||||
} catch (_) {}
|
||||
}
|
||||
} catch (_) {}
|
||||
@@ -3447,9 +3854,13 @@ export function createSidebarPageAiRuntime(context) {
|
||||
pageAiLoadProfileMemory: (...args) => pageAiLoadProfileMemory(...args),
|
||||
pageAiLoadSkills: (...args) => pageAiLoadSkills(...args),
|
||||
pageAiSwitchProfile: (...args) => pageAiSwitchProfile(...args),
|
||||
pageAiToggleSkillGroup: (...args) => pageAiToggleSkillGroup(...args),
|
||||
pageAiSetHideHermesBuiltinSkills: (...args) => pageAiSetHideHermesBuiltinSkills(...args),
|
||||
pageAiSetContextScope: (...args) => pageAiSetContextScope(...args),
|
||||
pageAiSetAgentId: (...args) => pageAiSetAgentId(...args),
|
||||
pageAiToggleContextRef: (...args) => pageAiToggleContextRef(...args),
|
||||
pageAiSetContextPopoverOpen: (...args) => pageAiSetContextPopoverOpen(...args),
|
||||
pageAiSetAgentPopoverOpen: (...args) => pageAiSetAgentPopoverOpen(...args),
|
||||
pageAiLoadAllowedRoots: (...args) => pageAiLoadAllowedRoots(...args),
|
||||
updatePageAiTriggerState: (...args) => updatePageAiTriggerState(...args)
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user