refactor: split editor mount and sidebar page ai

This commit is contained in:
lix-2026
2026-05-26 04:01:52 +08:00
parent c65135f7ed
commit a8bc87173f
10 changed files with 3124 additions and 2973 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+5
View File
@@ -154,6 +154,10 @@ pub fn build_router(state: AppState) -> Router {
"/api/mnote-browser-runtime/sidebar-attachment-open-runtime.js",
get(web_shell::sidebar_attachment_open_runtime_asset),
)
.route(
"/api/mnote-browser-runtime/sidebar-page-ai-runtime.js",
get(web_shell::sidebar_page_ai_runtime_asset),
)
.route(
"/api/mnote-browser-runtime/sidebar-tree-runtime.js",
get(web_shell::sidebar_tree_runtime_asset),
@@ -650,6 +654,7 @@ mod tests {
"/api/mnote-browser-runtime/sidebar-filetree-command-runtime.js",
"/api/mnote-browser-runtime/sidebar-filetree-upload-runtime.js",
"/api/mnote-browser-runtime/sidebar-attachment-open-runtime.js",
"/api/mnote-browser-runtime/sidebar-page-ai-runtime.js",
"/api/mnote-browser-runtime/sidebar-tree-runtime.js",
"/api/mnote-browser-runtime/tree-live-controller.js",
"/api/mnote-browser-runtime/tree-shell-runtime.js",
@@ -728,6 +728,20 @@ pub async fn sidebar_tree_runtime_asset() -> Response {
.unwrap_or_else(|_| Response::new(Body::empty()))
}
pub async fn sidebar_page_ai_runtime_asset() -> Response {
const JS: &str = include_str!("../../browser/sidebar-page-ai-runtime.js");
Response::builder()
.status(StatusCode::OK)
.header(
header::CONTENT_TYPE,
"application/javascript; charset=utf-8",
)
.header(header::CACHE_CONTROL, "no-store")
.header(HEADER_MNOTE_WEB_OWNER, "mnote-web")
.body(Body::from(JS))
.unwrap_or_else(|_| Response::new(Body::empty()))
}
pub async fn sidebar_shell_runtime_asset() -> Response {
const JS: &str = include_str!("../../browser/sidebar-shell-runtime.js");
Response::builder()
+45 -43
View File
@@ -215,6 +215,8 @@ mod tests {
include_str!("../../../browser/sidebar-workspace-runtime.js");
const SIDEBAR_PAGE_TREE_RUNTIME_JS: &str =
include_str!("../../../browser/sidebar-page-tree-runtime.js");
const SIDEBAR_PAGE_AI_RUNTIME_JS: &str =
include_str!("../../../browser/sidebar-page-ai-runtime.js");
const FILETREE_CONTEXT_MENU_RUNTIME_JS: &str =
include_str!("../../../browser/filetree-context-menu-runtime.js");
const FILETREE_DND_RUNTIME_JS: &str = include_str!("../../../browser/filetree-dnd-runtime.js");
@@ -352,7 +354,7 @@ mod tests {
#[test]
fn page_ai_fast_path_is_not_local_first_main_path() {
assert!(
SIDEBAR_TREE_RUNTIME_JS
SIDEBAR_PAGE_AI_RUNTIME_JS
.contains("if (currentSourceKind() === 'local_folder') return false;"),
"local-first 页面 AI 不应继续加厚 page-ai fast-path;本地编辑应走受控文件工具"
);
@@ -361,15 +363,15 @@ mod tests {
#[test]
fn page_ai_local_source_passes_file_reference_fields_to_agent_run() {
assert!(SIDEBAR_WORKSPACE_RUNTIME_JS.contains("function currentRootUri()"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("sourceKind: currentSourceKind()"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("rootUri: currentRootUri()"));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("sourceKind: currentSourceKind()"));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("rootUri: currentRootUri()"));
assert!(SIDEBAR_WORKSPACE_RUNTIME_JS
.contains("if (sourceKind && !payload.sourceKind) payload.sourceKind = sourceKind"));
assert!(SIDEBAR_WORKSPACE_RUNTIME_JS
.contains("if (rootUri && !payload.rootUri) payload.rootUri = rootUri"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("pageContext: scopedContext.pageContext"));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("pageContext: scopedContext.pageContext"));
assert!(
SIDEBAR_TREE_RUNTIME_JS
SIDEBAR_PAGE_AI_RUNTIME_JS
.contains("if (currentSourceKind() === 'local_folder') return false;"),
"local source 不应进入 page-ai fast-path,后端会把 run 收敛为文件引用 scope"
);
@@ -377,70 +379,70 @@ mod tests {
#[test]
fn page_ai_uses_backend_acp_session_runtime_store() {
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("function pageAiLoadBackendSessions"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("/api/hermes/client/sessions?"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("params.set('source', 'acp')"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("function pageAiLoadBackendSessionDetail"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("function pageAiSearchBackendSessions"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("data-page-ai-session-rename"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("data-page-ai-session-delete"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("data-page-ai-session-resume"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("data-page-ai-session-search"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("pageAiUsageSummary"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("usage.updated"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("thought.delta"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("permission.requested"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("data-page-ai-permission-action=\"allow\""));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("data-page-ai-permission-action=\"deny\""));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("function pageAiHidePermissionDialog"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("if (!message.resolved)"));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("function pageAiLoadBackendSessions"));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("/api/hermes/client/sessions?"));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("params.set('source', 'acp')"));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("function pageAiLoadBackendSessionDetail"));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("function pageAiSearchBackendSessions"));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("data-page-ai-session-rename"));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("data-page-ai-session-delete"));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("data-page-ai-session-resume"));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("data-page-ai-session-search"));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("pageAiUsageSummary"));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("usage.updated"));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("thought.delta"));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("permission.requested"));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("data-page-ai-permission-action=\"allow\""));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("data-page-ai-permission-action=\"deny\""));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("function pageAiHidePermissionDialog"));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("if (!message.resolved)"));
assert!(
!SIDEBAR_TREE_RUNTIME_JS.contains("(item.resolved ? ' disabled' : '')"),
!SIDEBAR_PAGE_AI_RUNTIME_JS.contains("(item.resolved ? ' disabled' : '')"),
"已决 ACP permission 事件不能继续展示假审批按钮"
);
assert!(
SIDEBAR_TREE_RUNTIME_JS.contains("session.info.updated"),
SIDEBAR_PAGE_AI_RUNTIME_JS.contains("session.info.updated"),
"ACP SessionInfoUpdate 事件应通过 session.info.updated SSE 转发到前端"
);
assert!(
SIDEBAR_TREE_RUNTIME_JS.contains("plan.updated"),
SIDEBAR_PAGE_AI_RUNTIME_JS.contains("plan.updated"),
"ACP PlanUpdate 事件应通过 plan.updated SSE 转发到前端"
);
assert!(
SIDEBAR_TREE_RUNTIME_JS.contains("data-page-ai-plan"),
SIDEBAR_PAGE_AI_RUNTIME_JS.contains("data-page-ai-plan"),
"plan 消息应渲染为 data-page-ai-plan 标记的轻量系统状态面板"
);
assert!(
SIDEBAR_TREE_RUNTIME_JS.contains("执行计划 · "),
SIDEBAR_PAGE_AI_RUNTIME_JS.contains("执行计划 · "),
"plan 面板标题应显示执行计划和步数"
);
}
#[test]
fn page_ai_session_ui_labels_local_shared_and_cloud_storage() {
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("function pageAiSessionStorageLabel"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("local_private"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("local_shared"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("convex_acp_runtime_store"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("本地私有"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("共享会话"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("云端会话"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("sessionStorage:"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("permissionLevel:"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("shareId:"));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("function pageAiSessionStorageLabel"));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("local_private"));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("local_shared"));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("convex_acp_runtime_store"));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("本地私有"));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("共享会话"));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("云端会话"));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("sessionStorage:"));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("permissionLevel:"));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("shareId:"));
}
#[test]
fn page_ai_acp_runtime_defaults_to_reasonix_and_keeps_hermes_switch() {
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("var PAGE_AI_SESSION_STORAGE_VERSION = 3"));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("var PAGE_AI_SESSION_STORAGE_VERSION = 3"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("pageAiAcpRuntime: 'reasonix'"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("function pageAiNormalizeAcpRuntimes"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("return ['reasonix', 'hermes']"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("pageAiNormalizeAcpRuntimes(acpRuntimes)"));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("function pageAiNormalizeAcpRuntimes"));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("return ['reasonix', 'hermes']"));
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS.contains("pageAiNormalizeAcpRuntimes(acpRuntimes)"));
assert!(SIDEBAR_TREE_RUNTIME_JS.contains("pageAiAcpRuntimeSelect.value || 'reasonix'"));
assert!(SIDEBAR_TREE_RUNTIME_JS
assert!(SIDEBAR_PAGE_AI_RUNTIME_JS
.contains("activeAcpRuntime: pageUiState.pageAiAcpRuntime || 'reasonix'"));
assert!(!SIDEBAR_TREE_RUNTIME_JS.contains("默认 (Hermes HTTP)"));
assert!(!SIDEBAR_PAGE_AI_RUNTIME_JS.contains("默认 (Hermes HTTP)"));
}
#[test]