feat: advance local-first workspace checklist
- add admin access-policy UI and local access control surfaces - add local markdown conflict resolution UI and smoke coverage - add ACP local agent changed-files audit scaffold and read-only write guard - document current P0-P2 checklist progress and verification evidence
This commit is contained in:
@@ -7,10 +7,11 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
if (window.__mnoteSidebarTreeRuntimeStarted) return;
|
||||
window.__mnoteSidebarTreeRuntimeStarted = true;
|
||||
|
||||
var PAGE_AI_SESSION_STORAGE_VERSION = 3;
|
||||
var PAGE_DRAG_MIME = 'application/x-mnote-page-tree-node';
|
||||
var FILETREE_DRAG_MIME = 'application/x-mnote-filetree-row-ids';
|
||||
var MNOTE_SIDEBAR_TREE_MODE_KEY = 'mnote.sidebar.tree.mode';
|
||||
var MNOTE_RECENT_LOCAL_ROOTS_KEY = 'mnote.localFolder.recentRoots';
|
||||
var MNOTE_RECENT_LOCAL_ROOTS_KEY_PREFIX = 'mnote.localFolder.recentRoots:';
|
||||
var MNOTE_LAST_CLOUD_WORKSPACE_KEY = 'mnote.workspace.lastCloudWorkspaceId';
|
||||
var MNOTE_GLOBAL_SHOW_HEADING_NUMBERS_KEY = 'mnote.global.showHeadingNumbers';
|
||||
var mnoteNavigationInFlight = '';
|
||||
@@ -39,7 +40,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
pageAiPage: 'chat',
|
||||
pageAiRunStatus: 'idle',
|
||||
pageAiCurrentRunId: '',
|
||||
pageAiAcpRuntime: '',
|
||||
pageAiAcpRuntime: 'reasonix',
|
||||
pageAiAcpRuntimes: [],
|
||||
pageAiQueueLength: 0,
|
||||
pageAiQueuedItems: [],
|
||||
@@ -618,7 +619,22 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
}
|
||||
|
||||
function currentSourceKind() {
|
||||
return (new URLSearchParams(window.location.search).get('sourceKind') || 'convex_workspace').trim() || 'convex_workspace';
|
||||
var params = new URLSearchParams(window.location.search);
|
||||
var fromUrl = (params.get('sourceKind') || '').trim();
|
||||
if (fromUrl) return fromUrl;
|
||||
if (
|
||||
window.location.pathname === '/' &&
|
||||
!(params.get('workspaceId') || '').trim() &&
|
||||
!(params.get('rootUri') || '').trim() &&
|
||||
!(params.get('pageId') || '').trim()
|
||||
) {
|
||||
return 'local_folder';
|
||||
}
|
||||
return 'convex_workspace';
|
||||
}
|
||||
|
||||
function currentRootUri() {
|
||||
return (new URLSearchParams(window.location.search).get('rootUri') || '').trim();
|
||||
}
|
||||
|
||||
function rememberCloudWorkspaceId(workspaceId) {
|
||||
@@ -680,9 +696,22 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
return payload;
|
||||
}
|
||||
|
||||
function currentActorStorageId() {
|
||||
var fromBody = document.body instanceof HTMLElement ? (document.body.getAttribute('data-mnote-actor-id') || '').trim() : '';
|
||||
if (fromBody && fromBody !== 'anonymous') return fromBody;
|
||||
return '';
|
||||
}
|
||||
|
||||
function recentLocalRootsStorageKey() {
|
||||
var actorId = currentActorStorageId();
|
||||
return actorId ? MNOTE_RECENT_LOCAL_ROOTS_KEY_PREFIX + encodeURIComponent(actorId) : '';
|
||||
}
|
||||
|
||||
function readRecentLocalRoots() {
|
||||
try {
|
||||
var raw = window.localStorage ? window.localStorage.getItem(MNOTE_RECENT_LOCAL_ROOTS_KEY) : '';
|
||||
var storageKey = recentLocalRootsStorageKey();
|
||||
if (!storageKey) return [];
|
||||
var raw = window.localStorage ? window.localStorage.getItem(storageKey) : '';
|
||||
var parsed = raw ? JSON.parse(raw) : [];
|
||||
return Array.isArray(parsed) ? parsed.filter(function(value) {
|
||||
return typeof value === 'string' && value.trim();
|
||||
@@ -695,9 +724,11 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
function rememberLocalRoot(rootUri) {
|
||||
try {
|
||||
if (!window.localStorage) return;
|
||||
var storageKey = recentLocalRootsStorageKey();
|
||||
if (!storageKey) return;
|
||||
var roots = readRecentLocalRoots().filter(function(value) { return value !== rootUri; });
|
||||
roots.unshift(rootUri);
|
||||
window.localStorage.setItem(MNOTE_RECENT_LOCAL_ROOTS_KEY, JSON.stringify(roots.slice(0, 10)));
|
||||
window.localStorage.setItem(storageKey, JSON.stringify(roots.slice(0, 10)));
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
@@ -1036,6 +1067,47 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
openLocalFolderDialog('');
|
||||
}
|
||||
|
||||
async function createDefaultLocalWorkspace(trigger) {
|
||||
setCommandPending(trigger, true);
|
||||
try {
|
||||
var response = await fetch('/api/local-folder/workspaces/default', {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: '{}'
|
||||
});
|
||||
var payload = await response.json().catch(function() { return null; });
|
||||
if (!response.ok) {
|
||||
throw new Error((payload && payload.message) || ('local_workspace_create_failed_' + response.status));
|
||||
}
|
||||
var workspace = payload && payload.workspace || {};
|
||||
var rootUri = String(workspace.rootUri || '').trim();
|
||||
if (!rootUri) throw new Error('local_workspace_create_missing_root_uri');
|
||||
openLocalFolderRoot(rootUri);
|
||||
} catch (error) {
|
||||
var status = document.querySelector('[data-testid="mnote-local-folder-status"]');
|
||||
if (status instanceof HTMLElement) {
|
||||
status.textContent = error && error.message ? error.message : String(error);
|
||||
} else {
|
||||
openLocalFolderDialog(error && error.message ? error.message : String(error));
|
||||
}
|
||||
} finally {
|
||||
setCommandPending(trigger, false);
|
||||
}
|
||||
}
|
||||
|
||||
function autoOpenRecentLocalRootOnHome() {
|
||||
if (window.location.pathname !== '/') return false;
|
||||
var params = new URLSearchParams(window.location.search);
|
||||
if ((params.get('sourceKind') || '').trim()) return false;
|
||||
if ((params.get('rootUri') || '').trim()) return false;
|
||||
if ((params.get('workspaceId') || '').trim()) return false;
|
||||
if ((params.get('pageId') || '').trim()) return false;
|
||||
var recent = readRecentLocalRoots();
|
||||
if (!recent.length) return false;
|
||||
openLocalFolderRoot(recent[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
function closeWorkspaceSourceMenu() {
|
||||
var existing = document.querySelector('[data-testid="mnote-workspace-source-menu"]');
|
||||
if (existing && existing.parentElement) existing.parentElement.removeChild(existing);
|
||||
@@ -1109,6 +1181,8 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
trigger.closest('[data-testid="wolai-workspace-identity"]')?.appendChild(menu);
|
||||
}
|
||||
|
||||
autoOpenRecentLocalRootOnHome();
|
||||
|
||||
function setCommandPending(trigger, pending) {
|
||||
if (!(trigger instanceof HTMLElement)) return;
|
||||
trigger.setAttribute('data-pending', pending ? 'true' : 'false');
|
||||
@@ -1970,6 +2044,21 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
return assetId.indexOf('mindmap_') === 0 || assetId.indexOf('mindmap-') === 0;
|
||||
}
|
||||
|
||||
function localFilePathFromAssetId(assetId) {
|
||||
var value = String(assetId || '').trim();
|
||||
return value.indexOf('local-file:') === 0 ? value.slice('local-file:'.length) : '';
|
||||
}
|
||||
|
||||
function buildLocalFileOpenUrl(relativePath, download) {
|
||||
var rootUri = currentRootUri();
|
||||
if (!rootUri || !relativePath) return '';
|
||||
var url = new URL('/api/local-folder/files/open', window.location.origin);
|
||||
url.searchParams.set('rootUri', rootUri);
|
||||
url.searchParams.set('path', relativePath);
|
||||
if (download) url.searchParams.set('download', 'true');
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
function readFileTreeObjectIdentity(row) {
|
||||
if (!row) return null;
|
||||
var raw = row.getAttribute('data-object-identity') || '';
|
||||
@@ -1999,6 +2088,12 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
async function openConvexAssetFromFileTree(detail) {
|
||||
var assetId = String(detail && detail.assetId || '').trim();
|
||||
if (!assetId) return;
|
||||
var localFilePath = localFilePathFromAssetId(assetId);
|
||||
if (localFilePath) {
|
||||
var localFileUrl = buildLocalFileOpenUrl(localFilePath, false);
|
||||
if (localFileUrl) window.open(localFileUrl, '_blank', 'noopener,noreferrer');
|
||||
return;
|
||||
}
|
||||
var documentId = String(detail && detail.documentId || '').trim();
|
||||
if (isMindmapAssetDetail(detail) && documentId) {
|
||||
document.documentElement.setAttribute('data-mnote-last-mindmap-asset-open-mode', 'mindmap-object-shell');
|
||||
@@ -2133,13 +2228,18 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
}
|
||||
|
||||
function uploadedAssetUrl(asset) {
|
||||
return String(asset && (asset.file_url || asset.signedUrl || asset.signed_url || asset.thumbnail_url) || '').trim();
|
||||
return String(asset && (asset.sourcePath || asset.file_url || asset.signedUrl || asset.signed_url || asset.thumbnail_url) || '').trim();
|
||||
}
|
||||
|
||||
function uploadedAssetType(asset) {
|
||||
return String(asset && (asset.asset_type || asset.assetType || asset.mime_type || '') || '').trim();
|
||||
}
|
||||
|
||||
function isLocalUploadedAsset(asset) {
|
||||
var id = String(asset && asset.id || '').trim();
|
||||
return String(asset && asset.sourceKind || '').trim() === 'local_folder' || id.indexOf('local:asset:') === 0;
|
||||
}
|
||||
|
||||
function uploadedAssetExtension(asset) {
|
||||
var match = uploadedAssetTitle(asset).toLowerCase().match(/\.([a-z0-9]+)$/);
|
||||
return match ? match[1] : '';
|
||||
@@ -2224,6 +2324,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
}
|
||||
|
||||
function buildOnlyOfficeAssetOpenUrl(asset, userId) {
|
||||
if (isLocalUploadedAsset(asset)) return '';
|
||||
var title = uploadedAssetTitle(asset);
|
||||
var fileType = inferOnlyOfficeFileType(title, asset && asset.mime_type);
|
||||
if (!fileType) return '';
|
||||
@@ -2591,8 +2692,9 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
if (type === 'image' && url) {
|
||||
return editor.chain().focus().setImage({ src: url, alt: title, title: title }).run() === true;
|
||||
}
|
||||
var isLocalAsset = isLocalUploadedAsset(asset);
|
||||
var userId = '';
|
||||
var onlyOfficeUrl = buildOnlyOfficeAssetOpenUrl(asset, userId);
|
||||
var onlyOfficeUrl = isLocalAsset ? '' : buildOnlyOfficeAssetOpenUrl(asset, userId);
|
||||
if (onlyOfficeUrl && assetId) {
|
||||
userId = await fetchCurrentOnlyOfficeUserId();
|
||||
onlyOfficeUrl = buildOnlyOfficeAssetOpenUrl(asset, userId);
|
||||
@@ -2647,6 +2749,35 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
}
|
||||
|
||||
async function uploadFileToMediaAsset(file, plan, options) {
|
||||
if (currentSourceKind() === 'local_folder') {
|
||||
var rootUri = (new URLSearchParams(window.location.search).get('rootUri') || '').trim();
|
||||
var documentId = String(plan && plan.targetDocumentId || currentDocumentId() || '').trim();
|
||||
if (!rootUri || !documentId) {
|
||||
throw new Error('本地 Markdown 上传缺少 rootUri 或 documentId');
|
||||
}
|
||||
var localForm = new FormData();
|
||||
localForm.append('file', file);
|
||||
localForm.append('rootUri', rootUri);
|
||||
localForm.append('documentId', documentId);
|
||||
localForm.append('kind', file && String(file.type || '').indexOf('image/') === 0 ? 'image' : 'attachment');
|
||||
var localResponse = await fetch('/api/local-folder/assets/upload', {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
body: localForm
|
||||
});
|
||||
var localPayload = await localResponse.json().catch(function() { return null; });
|
||||
if (!localResponse.ok || !localPayload || !localPayload.asset) {
|
||||
throw new Error(localPayload && localPayload.error ? localPayload.error : '上传失败');
|
||||
}
|
||||
if (options && options.insertIntoEditor) {
|
||||
await insertUploadedAssetIntoEditor(localPayload.asset);
|
||||
}
|
||||
void refreshLocalFolderSidebarSnapshot();
|
||||
window.dispatchEvent(new CustomEvent('wolai:local-assets-changed', {
|
||||
detail: { docId: documentId, asset: localPayload.asset, assetIds: [localPayload.asset.id] }
|
||||
}));
|
||||
return localPayload.asset;
|
||||
}
|
||||
var form = new FormData();
|
||||
form.append('file', file);
|
||||
form.append('workspaceId', plan.workspaceId);
|
||||
@@ -3951,6 +4082,8 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
params.set('workspaceId', resolveWorkspaceId(document.body));
|
||||
params.set('documentId', currentDocumentId());
|
||||
params.set('profile', pageAiRunProfile());
|
||||
params.set('sourceKind', currentSourceKind());
|
||||
if (currentRootUri()) params.set('rootUri', currentRootUri());
|
||||
Object.keys(extra || {}).forEach(function(key) {
|
||||
var value = extra[key];
|
||||
if (value !== undefined && value !== null && String(value).trim() !== '') {
|
||||
@@ -3966,7 +4099,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
id: 'sess_' + now + '_' + Math.random().toString(16).slice(2, 8),
|
||||
title: title || '新会话',
|
||||
profile: pageAiCurrentProfile(),
|
||||
acpRuntime: pageUiState.pageAiAcpRuntime || '',
|
||||
acpRuntime: pageUiState.pageAiAcpRuntime || 'reasonix',
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
source: 'local',
|
||||
@@ -4093,14 +4226,20 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
return (Array.isArray(sessions) ? sessions : [])
|
||||
.slice(0, 20)
|
||||
.map(function(session) {
|
||||
var sessionStorage = String(session && (session.sessionStorage || session.session_storage) || '').trim();
|
||||
var persistence = String(session && session.persistence || '').trim();
|
||||
return {
|
||||
id: String(session && session.id || '').trim() || pageAiNewSession().id,
|
||||
title: String(session && session.title || '').trim() || '新会话',
|
||||
profile: String(session && session.profile || pageAiCurrentProfile()).trim() || 'default',
|
||||
acpRuntime: String(session && session.acpRuntime || '').trim(),
|
||||
acpRuntime: String(session && session.acpRuntime || 'reasonix').trim(),
|
||||
createdAt: pageAiTimestamp(session && session.createdAt),
|
||||
updatedAt: pageAiTimestamp(session && session.updatedAt),
|
||||
source: String(session && session.source || 'local').trim() || 'local',
|
||||
persistence: persistence,
|
||||
sessionStorage: sessionStorage,
|
||||
permissionLevel: String(session && (session.permissionLevel || session.permission_level) || '').trim(),
|
||||
shareId: String(session && (session.shareId || session.share_id) || '').trim(),
|
||||
runId: String(session && (session.runId || session.run_id) || '').trim(),
|
||||
status: String(session && session.status || '').trim(),
|
||||
usage: session && session.usage && typeof session.usage === 'object' ? session.usage : null,
|
||||
@@ -4120,6 +4259,10 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
if (!sessionId) return null;
|
||||
var title = String(row.title || payload.title || payload.message || '').trim();
|
||||
if (title.length > 28) title = title.slice(0, 28) + '…';
|
||||
var persistence = String(row.persistence || payload.persistence || '').trim();
|
||||
var sessionStorage = String(row.sessionStorage || row.session_storage || payload.sessionStorage || '').trim();
|
||||
if (!sessionStorage && persistence === 'convex_acp_runtime_store') sessionStorage = 'cloud';
|
||||
if (!sessionStorage && persistence === 'local_ai_session_jsonl') sessionStorage = String(row.shareId || payload.shareId || '').trim() ? 'local_shared' : 'local_private';
|
||||
return {
|
||||
id: sessionId,
|
||||
title: title || '当前页问答',
|
||||
@@ -4127,7 +4270,11 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
acpRuntime: String(row.acpRuntime || row.acp_runtime || payload.acpRuntime || 'reasonix').trim(),
|
||||
createdAt: pageAiTimestamp(row.createdAt || row.created_at),
|
||||
updatedAt: pageAiTimestamp(row.updatedAt || row.updated_at),
|
||||
source: 'acp',
|
||||
source: sessionStorage === 'local_private' || sessionStorage === 'local_shared' ? 'local' : 'acp',
|
||||
persistence: persistence,
|
||||
sessionStorage: sessionStorage,
|
||||
permissionLevel: String(row.permissionLevel || row.permission_level || payload.permissionLevel || '').trim(),
|
||||
shareId: String(row.shareId || row.share_id || payload.shareId || '').trim(),
|
||||
runId: String(row.runId || row.run_id || '').trim(),
|
||||
status: String(row.status || (row.runtime && row.runtime.status) || '').trim(),
|
||||
usage: row.usage && typeof row.usage === 'object' ? row.usage : null,
|
||||
@@ -4150,6 +4297,16 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
return pageAiNormalizeSessions(Object.keys(byId).map(function(id) { return byId[id]; }));
|
||||
}
|
||||
|
||||
function pageAiSessionStorageLabel(session) {
|
||||
var storage = String(session && (session.sessionStorage || session.session_storage) || '').trim();
|
||||
var persistence = String(session && session.persistence || '').trim();
|
||||
if (storage === 'local_shared') return '共享会话';
|
||||
if (storage === 'local_private') return '本地私有';
|
||||
if (storage === 'cloud' || persistence === 'convex_acp_runtime_store') return '云端会话';
|
||||
if (persistence === 'local_ai_session_jsonl') return '本地私有';
|
||||
return String(session && session.source || '').trim() === 'acp' ? '云端会话' : '本地私有';
|
||||
}
|
||||
|
||||
function pageAiLoadSessions() {
|
||||
if (pageUiState.pageAiSessions.length && pageUiState.pageAiActiveSessionId) return;
|
||||
try {
|
||||
@@ -4159,14 +4316,15 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
var activeProfile = String(parsed && parsed.activeProfileName || '').trim();
|
||||
var activeAcpRuntime = String(parsed && parsed.activeAcpRuntime || '').trim();
|
||||
var sessions = pageAiNormalizeSessions(parsed && parsed.sessions);
|
||||
if (activeAcpRuntime) pageUiState.pageAiAcpRuntime = activeAcpRuntime;
|
||||
var storageVersion = Number(parsed && parsed.version || 0);
|
||||
if (storageVersion >= PAGE_AI_SESSION_STORAGE_VERSION && activeAcpRuntime) pageUiState.pageAiAcpRuntime = activeAcpRuntime;
|
||||
if (activeProfile) pageAiSetActiveProfile(activeProfile);
|
||||
if (sessions.length) {
|
||||
pageUiState.pageAiSessions = sessions;
|
||||
var activeSession = sessions.find(function(session) { return session.id === activeId; }) || sessions[0];
|
||||
pageUiState.pageAiActiveSessionId = activeSession.id;
|
||||
if (activeSession.profile) pageAiSetActiveProfile(activeSession.profile);
|
||||
if (activeSession.acpRuntime) pageUiState.pageAiAcpRuntime = activeSession.acpRuntime;
|
||||
if (!pageUiState.pageAiAcpRuntime && activeSession.acpRuntime) pageUiState.pageAiAcpRuntime = activeSession.acpRuntime;
|
||||
pageUiState.pageAiMessages = Array.isArray(activeSession.messages) ? activeSession.messages.slice() : [];
|
||||
return;
|
||||
}
|
||||
@@ -4201,7 +4359,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
var active = pageAiCurrentSession();
|
||||
if (active) {
|
||||
if (active.profile) pageAiSetActiveProfile(active.profile);
|
||||
if (active.acpRuntime) pageUiState.pageAiAcpRuntime = active.acpRuntime;
|
||||
if (!pageUiState.pageAiAcpRuntime && active.acpRuntime) pageUiState.pageAiAcpRuntime = active.acpRuntime;
|
||||
pageUiState.pageAiMessages = Array.isArray(active.messages) ? active.messages.slice() : pageUiState.pageAiMessages;
|
||||
}
|
||||
pageUiState.pageAiSessionError = '';
|
||||
@@ -4319,10 +4477,10 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
try {
|
||||
pageAiSyncCurrentSessionMessages();
|
||||
window.localStorage.setItem(pageAiStorageKey(), JSON.stringify({
|
||||
version: 2,
|
||||
version: PAGE_AI_SESSION_STORAGE_VERSION,
|
||||
activeSessionId: pageUiState.pageAiActiveSessionId,
|
||||
activeProfileName: pageAiCurrentProfile(),
|
||||
activeAcpRuntime: pageUiState.pageAiAcpRuntime || '',
|
||||
activeAcpRuntime: pageUiState.pageAiAcpRuntime || 'reasonix',
|
||||
sessions: pageAiNormalizeSessions(pageUiState.pageAiSessions)
|
||||
}));
|
||||
} catch (_) {}
|
||||
@@ -4338,6 +4496,8 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
body: JSON.stringify({
|
||||
workspaceId: resolveWorkspaceId(document.body),
|
||||
documentId: currentDocumentId(),
|
||||
sourceKind: currentSourceKind(),
|
||||
rootUri: currentRootUri(),
|
||||
traceId: 'page-ai-' + Date.now().toString(36),
|
||||
profile: pageAiCurrentProfile(),
|
||||
title: current && current.title ? current.title : '当前页问答'
|
||||
@@ -4351,7 +4511,11 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
id: String(payload.sessionId || '').trim(),
|
||||
title: String(payload.title || '当前页问答'),
|
||||
profile: String(payload.profile || pageAiCurrentProfile()).trim() || 'default',
|
||||
acpRuntime: pageUiState.pageAiAcpRuntime || '',
|
||||
acpRuntime: pageUiState.pageAiAcpRuntime || 'reasonix',
|
||||
persistence: String(payload.persistence || '').trim(),
|
||||
sessionStorage: String(payload.sessionStorage || '').trim(),
|
||||
permissionLevel: String(payload.permissionLevel || '').trim(),
|
||||
shareId: String(payload.shareId || '').trim(),
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
messages: pageUiState.pageAiMessages.slice()
|
||||
@@ -4411,7 +4575,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
if (!session) return;
|
||||
session.messages = Array.isArray(pageUiState.pageAiMessages) ? pageUiState.pageAiMessages.slice(-300) : [];
|
||||
session.profile = pageAiCurrentProfile();
|
||||
session.acpRuntime = pageUiState.pageAiAcpRuntime || '';
|
||||
session.acpRuntime = pageUiState.pageAiAcpRuntime || 'reasonix';
|
||||
session.updatedAt = Date.now();
|
||||
}
|
||||
|
||||
@@ -4518,6 +4682,36 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
return Array.isArray(value) ? value : [];
|
||||
}
|
||||
|
||||
function pageAiDefaultAcpRuntimes() {
|
||||
return [
|
||||
{
|
||||
name: 'reasonix',
|
||||
title: 'ACP · Reasonix',
|
||||
description: '通过 ACP 协议直连 Reasonix(DeepSeek 缓存优先)',
|
||||
model: 'deepseek-chat',
|
||||
preset: 'auto'
|
||||
},
|
||||
{
|
||||
name: 'hermes',
|
||||
title: 'ACP · Hermes',
|
||||
description: '通过 ACP 协议直连 Hermes agent runtime'
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
function pageAiNormalizeAcpRuntimes(runtimes) {
|
||||
var byName = {};
|
||||
pageAiDefaultAcpRuntimes().forEach(function(runtime) {
|
||||
byName[runtime.name] = Object.assign({}, runtime);
|
||||
});
|
||||
pageAiNormalizeArray(runtimes).forEach(function(runtime) {
|
||||
var name = String(runtime && runtime.name || '').trim();
|
||||
if (name !== 'reasonix' && name !== 'hermes') return;
|
||||
byName[name] = Object.assign({}, byName[name] || {}, runtime, { name: name });
|
||||
});
|
||||
return ['reasonix', 'hermes'].map(function(name) { return byName[name]; }).filter(Boolean);
|
||||
}
|
||||
|
||||
function pageAiUnwrapUpstream(payload) {
|
||||
if (payload && typeof payload === 'object' && payload.upstream) return payload.upstream;
|
||||
return payload || null;
|
||||
@@ -4540,7 +4734,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
}
|
||||
|
||||
function pageAiRunProfile() {
|
||||
return String(pageUiState.pageAiAcpRuntime || '').trim() === 'reasonix' ? 'reasonix' : pageAiCurrentProfile();
|
||||
return String(pageUiState.pageAiAcpRuntime || 'reasonix').trim() === 'reasonix' ? 'reasonix' : pageAiCurrentProfile();
|
||||
}
|
||||
|
||||
function pageAiMnoteToolModel() {
|
||||
@@ -4705,6 +4899,15 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
return String(name || '').trim().replace(/_/g, '.');
|
||||
}
|
||||
|
||||
function pageAiFormatChangedFiles(files) {
|
||||
return pageAiNormalizeArray(files).map(function(file) {
|
||||
var path = String(file && file.path || '').trim();
|
||||
var changeType = String(file && file.changeType || file.change_type || 'modified').trim();
|
||||
var summary = String(file && file.summary || '').trim();
|
||||
return [changeType, path, summary].filter(Boolean).join(' · ');
|
||||
}).filter(Boolean).join('\n');
|
||||
}
|
||||
|
||||
function pageAiToolEventDeepFindString(value, keys, depth) {
|
||||
if (!value || typeof value !== 'object' || depth > 5) return '';
|
||||
for (var index = 0; index < keys.length; index += 1) {
|
||||
@@ -5005,7 +5208,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
body: JSON.stringify({
|
||||
sessionId: pageUiState.pageAiActiveSessionId,
|
||||
profile: pageAiRunProfile(),
|
||||
acpRuntime: pageUiState.pageAiAcpRuntime || '',
|
||||
acpRuntime: pageUiState.pageAiAcpRuntime || 'reasonix',
|
||||
reason: 'page_ai_user_stop'
|
||||
})
|
||||
});
|
||||
@@ -5063,7 +5266,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
var profiles = pageAiNormalizeProfiles(payload);
|
||||
pageUiState.pageAiProfiles = profiles.length ? profiles : [{ name: 'mnoteai', active: true }];
|
||||
var acpRuntimes = Array.isArray(payload.acpRuntimes) ? payload.acpRuntimes : [];
|
||||
pageUiState.pageAiAcpRuntimes = acpRuntimes;
|
||||
pageUiState.pageAiAcpRuntimes = pageAiNormalizeAcpRuntimes(acpRuntimes);
|
||||
var current = pageAiCurrentProfile();
|
||||
var hasCurrent = pageUiState.pageAiProfiles.some(function(profile) { return pageAiProfileValue(profile) === current; });
|
||||
var hasMnoteAi = pageUiState.pageAiProfiles.some(function(profile) { return pageAiProfileValue(profile) === 'mnoteai'; });
|
||||
@@ -5074,6 +5277,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
} catch (error) {
|
||||
pageUiState.pageAiProfileError = error instanceof Error ? error.message : String(error);
|
||||
if (!pageUiState.pageAiProfiles.length) pageUiState.pageAiProfiles = [{ name: 'mnoteai', active: true }];
|
||||
pageUiState.pageAiAcpRuntimes = pageAiNormalizeAcpRuntimes(pageUiState.pageAiAcpRuntimes);
|
||||
pageAiSetActiveProfile(pageAiCurrentProfile());
|
||||
}
|
||||
renderPageAiProviderButtons();
|
||||
@@ -5156,7 +5360,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
|
||||
async function pageAiLoadSkills() {
|
||||
try {
|
||||
var runtime = String(pageUiState.pageAiAcpRuntime || '').trim();
|
||||
var runtime = String(pageUiState.pageAiAcpRuntime || 'reasonix').trim();
|
||||
var params = runtime === 'reasonix'
|
||||
? 'runtime=reasonix'
|
||||
: 'profile=' + encodeURIComponent(pageAiCurrentProfile());
|
||||
@@ -5178,7 +5382,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
async function pageAiToggleSkill(skillName, enabled) {
|
||||
var name = String(skillName || '').trim();
|
||||
if (!name) return;
|
||||
if (String(pageUiState.pageAiAcpRuntime || '').trim() === 'reasonix') return;
|
||||
if (String(pageUiState.pageAiAcpRuntime || 'reasonix').trim() === 'reasonix') return;
|
||||
var previous = null;
|
||||
pageAiSkillListEntries().forEach(function(skill) {
|
||||
if (skill.name === name && previous == null) previous = skill.enabled !== false;
|
||||
@@ -5275,16 +5479,15 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
var isAcp = pageUiState.pageAiAcpRuntime !== '';
|
||||
var activeProfile = pageAiRunProfile();
|
||||
drawer.setAttribute('data-page-ai-page', pageUiState.pageAiPage || 'chat');
|
||||
drawer.setAttribute('data-mnote-acp-runtime', pageUiState.pageAiAcpRuntime || '');
|
||||
drawer.setAttribute('data-mnote-acp-runtime', pageUiState.pageAiAcpRuntime || 'reasonix');
|
||||
// Populate ACP runtime dropdown
|
||||
var acpSelect = drawer.querySelector('[data-page-ai-acp-runtime]');
|
||||
if (acpSelect instanceof HTMLSelectElement) {
|
||||
var runtimes = pageUiState.pageAiAcpRuntimes.length ? pageUiState.pageAiAcpRuntimes : [];
|
||||
acpSelect.innerHTML = '<option value="">默认 (Hermes HTTP)</option>' +
|
||||
runtimes.map(function(rt) {
|
||||
var runtimes = pageUiState.pageAiAcpRuntimes.length ? pageUiState.pageAiAcpRuntimes : pageAiNormalizeAcpRuntimes([]);
|
||||
acpSelect.innerHTML = runtimes.map(function(rt) {
|
||||
return '<option value="' + escapeHtml(rt.name || '') + '"' + ((rt.name || '') === pageUiState.pageAiAcpRuntime ? ' selected' : '') + '>' + escapeHtml(rt.title || rt.name) + '</option>';
|
||||
}).join('');
|
||||
acpSelect.value = pageUiState.pageAiAcpRuntime || '';
|
||||
acpSelect.value = pageUiState.pageAiAcpRuntime || 'reasonix';
|
||||
}
|
||||
// Show/hide Hermes-specific profile select
|
||||
var profileLabel = drawer.querySelector('[data-page-ai-hermes-profile]');
|
||||
@@ -5366,7 +5569,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
var session = pageAiCurrentSession();
|
||||
var usageText = session && session.usage ? pageAiUsageSummary(session.usage) : '';
|
||||
sessionNode.textContent = session && session.id
|
||||
? [String(session.title || '当前页问答'), usageText].filter(Boolean).join(' · ')
|
||||
? [String(session.title || '当前页问答'), pageAiSessionStorageLabel(session), usageText].filter(Boolean).join(' · ')
|
||||
: '等待 Hermes session';
|
||||
}
|
||||
var modelNode = drawer.querySelector('[data-page-ai-model-status]');
|
||||
@@ -5438,7 +5641,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
if (skillList instanceof HTMLElement) {
|
||||
var skills = pageAiFilteredSkillEntries();
|
||||
if (!skills.length) {
|
||||
var emptyText = String(pageUiState.pageAiAcpRuntime || '').trim() === 'reasonix'
|
||||
var emptyText = String(pageUiState.pageAiAcpRuntime || 'reasonix').trim() === 'reasonix'
|
||||
? '没有匹配的 Reasonix skill。'
|
||||
: '没有匹配的 Hermes skill。';
|
||||
skillList.innerHTML = '<div class="wolai-page-ai-empty">' + escapeHtml(emptyText) + '</div>';
|
||||
@@ -5447,7 +5650,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
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 || '').trim() !== 'reasonix';
|
||||
var canToggle = skill.toggleable !== false && String(pageUiState.pageAiAcpRuntime || 'reasonix').trim() !== 'reasonix';
|
||||
return '' +
|
||||
'<div class="wolai-page-ai-skill-row">' +
|
||||
'<div class="wolai-page-ai-skill-copy">' +
|
||||
@@ -5618,9 +5821,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
'<div class="wolai-page-ai-settings-grid">' +
|
||||
'<label class="wolai-page-ai-profile-select">' +
|
||||
'<span>ACP</span>' +
|
||||
'<select data-page-ai-acp-runtime>' +
|
||||
'<option value="">默认 (Hermes HTTP)</option>' +
|
||||
'</select>' +
|
||||
'<select data-page-ai-acp-runtime></select>' +
|
||||
'</label>' +
|
||||
'<label class="wolai-page-ai-profile-select" data-page-ai-hermes-profile>' +
|
||||
'<span>agent / profile</span>' +
|
||||
@@ -5756,7 +5957,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
: (session.snippet || session.preview || '暂无消息');
|
||||
var active = session.id === pageUiState.pageAiActiveSessionId;
|
||||
var usage = pageAiUsageSummary(session.usage);
|
||||
var meta = [session.source === 'acp' ? 'Convex' : '本地', session.status, usage].filter(Boolean).join(' · ');
|
||||
var meta = [pageAiSessionStorageLabel(session), session.permissionLevel, session.shareId, session.status, usage].filter(Boolean).join(' · ');
|
||||
return '' +
|
||||
'<div class="wolai-page-ai-message wolai-page-ai-message--history' + (active ? ' is-active' : '') + '" data-page-ai-session-row="' + escapeHtml(session.id) + '">' +
|
||||
'<button type="button" class="wolai-page-ai-message-text" data-page-ai-session="' + escapeHtml(session.id) + '">' +
|
||||
@@ -5784,6 +5985,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
var detailRows = [
|
||||
item.argsSummary ? '<div class="wolai-page-ai-tool-meta">参数 ' + escapeHtml(item.argsSummary) + '</div>' : '',
|
||||
item.resultSummary ? '<div class="wolai-page-ai-tool-meta">结果 ' + escapeHtml(item.resultSummary) + '</div>' : '',
|
||||
item.changedFiles && item.changedFiles.length ? '<pre class="wolai-page-ai-tool-meta wolai-page-ai-changed-files">' + escapeHtml(pageAiFormatChangedFiles(item.changedFiles)) + '</pre>' : '',
|
||||
(item.traceId || item.auditId) ? '<div class="wolai-page-ai-tool-meta">' + escapeHtml([item.traceId, item.auditId].filter(Boolean).join(' · ')) + '</div>' : ''
|
||||
].filter(Boolean).join('');
|
||||
return '' +
|
||||
@@ -5975,6 +6177,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
}
|
||||
|
||||
async function pageAiTryBlockEditWorkflow(prompt, scopedContext) {
|
||||
if (currentSourceKind() === 'local_folder') return false;
|
||||
if (!pageAiLooksLikeBlockEdit(prompt)) return false;
|
||||
var runId = 'page-ai-fast-' + Date.now().toString(36);
|
||||
var traceId = 'page-ai-fast-' + Date.now().toString(36);
|
||||
@@ -5985,11 +6188,13 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
workspaceId: resolveWorkspaceId(document.body),
|
||||
documentId: currentDocumentId(),
|
||||
sessionId: pageUiState.pageAiActiveSessionId,
|
||||
runId: runId,
|
||||
profile: pageAiCurrentProfile(),
|
||||
workspaceId: resolveWorkspaceId(document.body),
|
||||
documentId: currentDocumentId(),
|
||||
sourceKind: currentSourceKind(),
|
||||
rootUri: currentRootUri(),
|
||||
sessionId: pageUiState.pageAiActiveSessionId,
|
||||
runId: runId,
|
||||
profile: pageAiCurrentProfile(),
|
||||
model: pageAiMnoteToolModel(),
|
||||
message: prompt,
|
||||
pageContext: scopedContext.pageContext,
|
||||
@@ -6083,9 +6288,11 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
body: JSON.stringify({
|
||||
workspaceId: resolveWorkspaceId(document.body),
|
||||
documentId: currentDocumentId(),
|
||||
sourceKind: currentSourceKind(),
|
||||
rootUri: currentRootUri(),
|
||||
sessionId: pageUiState.pageAiActiveSessionId,
|
||||
profile: pageAiRunProfile(),
|
||||
acpRuntime: pageUiState.pageAiAcpRuntime || '',
|
||||
acpRuntime: pageUiState.pageAiAcpRuntime || 'reasonix',
|
||||
contextScope: pageUiState.pageAiContextScope,
|
||||
message: prompt,
|
||||
model: pageAiMnoteToolModel(),
|
||||
@@ -6184,6 +6391,23 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
var completedSession = pageAiCurrentSession();
|
||||
if (completedSession) completedSession.usage = completed.usage;
|
||||
}
|
||||
var agentAudit = completed && completed.agentAudit && typeof completed.agentAudit === 'object' ? completed.agentAudit : null;
|
||||
var changedFiles = pageAiNormalizeArray(agentAudit && agentAudit.changedFiles);
|
||||
if (changedFiles.length) {
|
||||
pageUiState.pageAiMessages.push({
|
||||
role: 'tool',
|
||||
content: 'agent.changed_files',
|
||||
toolCallId: runId + ':agent.changed_files',
|
||||
toolName: 'agent.changed_files',
|
||||
toolKind: 'audit',
|
||||
status: 'completed',
|
||||
argsSummary: String(agentAudit.rootUri || ''),
|
||||
resultSummary: String(agentAudit.diffSummary || changedFiles.length + ' changed file(s)'),
|
||||
changedFiles: changedFiles,
|
||||
traceId: runTraceId,
|
||||
auditId: String(agentAudit.eventId || '')
|
||||
});
|
||||
}
|
||||
} catch (_) {}
|
||||
pageAiSetRunStatus('completed', runId);
|
||||
}
|
||||
@@ -7014,6 +7238,13 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
return;
|
||||
}
|
||||
|
||||
var createLocalWorkspaceTrigger = closestAction(e.target, '[data-mnote-action="create-local-workspace"]');
|
||||
if (createLocalWorkspaceTrigger) {
|
||||
e.preventDefault();
|
||||
void createDefaultLocalWorkspace(createLocalWorkspaceTrigger);
|
||||
return;
|
||||
}
|
||||
|
||||
var createTrigger = closestAction(e.target, '[data-mnote-action="create-page"]');
|
||||
if (createTrigger) {
|
||||
e.preventDefault();
|
||||
@@ -7231,7 +7462,7 @@ const SIDEBAR_TREE_JS: &str = r##"
|
||||
document.addEventListener('change', function(event) {
|
||||
var pageAiAcpRuntimeSelect = closestAction(event.target, '[data-page-ai-acp-runtime]');
|
||||
if (pageAiAcpRuntimeSelect instanceof HTMLSelectElement) {
|
||||
var next = String(pageAiAcpRuntimeSelect.value || '').trim();
|
||||
var next = String(pageAiAcpRuntimeSelect.value || 'reasonix').trim() || 'reasonix';
|
||||
pageUiState.pageAiAcpRuntime = next;
|
||||
pageUiState.pageAiSkills = { categories: [], archived: [] };
|
||||
pageUiState.pageAiSkillError = '';
|
||||
@@ -7829,6 +8060,9 @@ pub fn PageLayout(
|
||||
/// 顶栏当前页面标题(可选)
|
||||
#[prop(optional)]
|
||||
topbar_title: Option<String>,
|
||||
/// 是否显示管理员授权入口
|
||||
#[prop(optional)]
|
||||
show_admin_access_policy: bool,
|
||||
) -> impl IntoView {
|
||||
let sidebar_tree_html = sidebar_tree_html.unwrap_or_default();
|
||||
|
||||
@@ -7894,6 +8128,21 @@ pub fn PageLayout(
|
||||
<a href="/help" title="帮助" aria-label="帮助"><span class="material-symbols-outlined nav-icon" data-icon="help" aria-hidden="true"></span></a>
|
||||
<a href="/files" title="文件" aria-label="文件"><span class="material-symbols-outlined nav-icon" data-icon="inventory_2" aria-hidden="true"></span></a>
|
||||
<button type="button" title="打开本地文件夹" aria-label="打开本地文件夹" data-mnote-action="open-local-folder"><span class="material-symbols-outlined nav-icon" data-icon="folder_open" aria-hidden="true"></span></button>
|
||||
{if show_admin_access_policy {
|
||||
view! {
|
||||
<a
|
||||
href="/admin/access-policy"
|
||||
class:active={current_nav == "admin"}
|
||||
title="目录授权"
|
||||
aria-label="目录授权"
|
||||
data-testid="mnote-admin-access-policy-entry"
|
||||
>
|
||||
<span class="material-symbols-outlined nav-icon" data-icon="admin_panel_settings" aria-hidden="true"></span>
|
||||
</a>
|
||||
}.into_any()
|
||||
} else {
|
||||
view! {}.into_any()
|
||||
}}
|
||||
<a href="/more" title="更多" aria-label="更多"><span class="material-symbols-outlined nav-icon" data-icon="more_horiz" aria-hidden="true"></span></a>
|
||||
</nav>
|
||||
<div class="wolai-sidebar-body" inner_html={sidebar_sections_html}></div>
|
||||
@@ -7987,6 +8236,9 @@ mod tests {
|
||||
assert!(SIDEBAR_TREE_JS.contains("objectIdentity: objectIdentity"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("workspaceId: resolveWorkspaceId(fileRow)"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("/api/media/sign?assetId="));
|
||||
assert!(SIDEBAR_TREE_JS.contains("function localFilePathFromAssetId"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("/api/local-folder/files/open"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("local-file:"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("fetchCurrentOnlyOfficeUserId"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("/api/auth/whoami"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("buildOnlyOfficeOpenUrl"));
|
||||
@@ -7998,8 +8250,11 @@ mod tests {
|
||||
assert!(SIDEBAR_TREE_JS.contains("tree-rename-input"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("sidebarFileTreeClipboard"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("pasteSidebarFileTreeClipboard"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("MNOTE_RECENT_LOCAL_ROOTS_KEY"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("MNOTE_RECENT_LOCAL_ROOTS_KEY_PREFIX"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("function recentLocalRootsStorageKey"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("data-mnote-actor-id"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("requestOpenLocalFolder"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("autoOpenRecentLocalRootOnHome"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("sourceKind', 'local_folder"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("switchToCloudWorkspace"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("mnote-switch-cloud-workspace"));
|
||||
@@ -8018,6 +8273,26 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn page_ai_fast_path_is_not_local_first_main_path() {
|
||||
assert!(
|
||||
SIDEBAR_TREE_JS.contains("if (currentSourceKind() === 'local_folder') return false;"),
|
||||
"local-first 页面 AI 不应继续加厚 page-ai fast-path;本地编辑应走受控文件工具"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn page_ai_local_source_passes_file_reference_fields_to_agent_run() {
|
||||
assert!(SIDEBAR_TREE_JS.contains("function currentRootUri()"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("sourceKind: currentSourceKind()"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("rootUri: currentRootUri()"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("pageContext: scopedContext.pageContext"));
|
||||
assert!(
|
||||
SIDEBAR_TREE_JS.contains("if (currentSourceKind() === 'local_folder') return false;"),
|
||||
"local source 不应进入 page-ai fast-path,后端会把 run 收敛为文件引用 scope"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn page_ai_uses_backend_acp_session_runtime_store() {
|
||||
assert!(SIDEBAR_TREE_JS.contains("function pageAiLoadBackendSessions"));
|
||||
@@ -8043,6 +8318,33 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn page_ai_session_ui_labels_local_shared_and_cloud_storage() {
|
||||
assert!(SIDEBAR_TREE_JS.contains("function pageAiSessionStorageLabel"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("local_private"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("local_shared"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("convex_acp_runtime_store"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("本地私有"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("共享会话"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("云端会话"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("sessionStorage:"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("permissionLevel:"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("shareId:"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn page_ai_acp_runtime_defaults_to_reasonix_and_keeps_hermes_switch() {
|
||||
assert!(SIDEBAR_TREE_JS.contains("var PAGE_AI_SESSION_STORAGE_VERSION = 3"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("pageAiAcpRuntime: 'reasonix'"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("function pageAiNormalizeAcpRuntimes"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("return ['reasonix', 'hermes']"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("pageAiNormalizeAcpRuntimes(acpRuntimes)"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("pageAiAcpRuntimeSelect.value || 'reasonix'"));
|
||||
assert!(SIDEBAR_TREE_JS
|
||||
.contains("activeAcpRuntime: pageUiState.pageAiAcpRuntime || 'reasonix'"));
|
||||
assert!(!SIDEBAR_TREE_JS.contains("默认 (Hermes HTTP)"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sidebar_tree_runtime_does_not_use_retired_query_preferred_snapshot_selector() {
|
||||
assert!(TREE_LIVE_CONTROLLER_JS.contains("tree:snapshot"));
|
||||
@@ -8081,6 +8383,18 @@ mod tests {
|
||||
assert!(!SIDEBAR_TREE_JS.contains("window.location.reload"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sidebar_upload_runtime_routes_local_markdown_assets_to_local_folder() {
|
||||
assert!(SIDEBAR_TREE_JS.contains("currentSourceKind() === 'local_folder'"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("/api/local-folder/assets/upload"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("localForm.append('rootUri', rootUri)"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("localForm.append('documentId', documentId)"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("isLocalUploadedAsset(asset)"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("if (isLocalUploadedAsset(asset)) return '';"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("refreshLocalFolderSidebarSnapshot"));
|
||||
assert!(SIDEBAR_TREE_JS.contains("wolai:local-assets-changed"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sidebar_tree_runtime_contains_dev_hot_reload_client() {
|
||||
assert!(SIDEBAR_TREE_JS.contains("installMnoteDevHotReload"));
|
||||
|
||||
Reference in New Issue
Block a user