feat: add pi lab ai management integration
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -950,6 +950,7 @@ export const createSidebarWorkspaceRuntime = (dependencies = {}) => {
|
||||
menu.innerHTML =
|
||||
'<button type="button" class="mnote-workspace-source-menu__item mnote-account-menu__item" data-testid="mnote-account-profile" role="menuitem"><span class="material-symbols-outlined" data-icon="account_circle" aria-hidden="true"></span><span>个人信息</span></button>' +
|
||||
'<button type="button" class="mnote-workspace-source-menu__item mnote-account-menu__item" data-testid="mnote-account-access-policy" role="menuitem" hidden><span class="material-symbols-outlined" data-icon="admin_panel_settings" aria-hidden="true"></span><span>授权管理</span></button>' +
|
||||
'<button type="button" class="mnote-workspace-source-menu__item mnote-account-menu__item" data-testid="mnote-account-ai-management" role="menuitem" hidden><span class="material-symbols-outlined" data-icon="smart_toy" aria-hidden="true"></span><span>AI 管理</span></button>' +
|
||||
'<button type="button" class="mnote-workspace-source-menu__item mnote-account-menu__logout" data-testid="mnote-account-sign-out" role="menuitem">退出登录</button>' +
|
||||
'<div class="mnote-account-menu__error" data-account-error hidden></div>';
|
||||
var sessionPromise = fetchAccountSession();
|
||||
@@ -972,6 +973,20 @@ export const createSidebarWorkspaceRuntime = (dependencies = {}) => {
|
||||
openAdminAccessPolicyDialog(accessPolicyLink);
|
||||
});
|
||||
}
|
||||
var aiManagementLink = menu.querySelector('[data-testid="mnote-account-ai-management"]');
|
||||
sessionPromise.then(function(session) {
|
||||
if (!(aiManagementLink instanceof HTMLElement)) return;
|
||||
aiManagementLink.hidden = false;
|
||||
aiManagementLink.setAttribute('data-ai-management-role', sessionIsAdmin(session) ? 'admin' : 'user');
|
||||
});
|
||||
if (aiManagementLink) {
|
||||
aiManagementLink.addEventListener('click', function(event) {
|
||||
event.preventDefault();
|
||||
sessionPromise.then(function(session) {
|
||||
window.location.assign(sessionIsAdmin(session) ? '/admin/ai' : '/user/ai');
|
||||
});
|
||||
});
|
||||
}
|
||||
var signOutButton = menu.querySelector('[data-testid="mnote-account-sign-out"]');
|
||||
if (signOutButton) {
|
||||
signOutButton.addEventListener('click', function(event) {
|
||||
|
||||
@@ -31,6 +31,7 @@ pub struct AppConfig {
|
||||
pub enable_legacy_next_compat: bool,
|
||||
pub enable_debug_shell_routes: bool,
|
||||
pub enable_editor_actor: bool,
|
||||
pub enable_page_ai_pi_lab: bool,
|
||||
pub hermes_base_path: String,
|
||||
pub compat_next_base_path: String,
|
||||
pub convex_url: Option<String>,
|
||||
@@ -64,6 +65,7 @@ impl AppConfig {
|
||||
.map(|value| matches!(value.trim(), "1" | "true" | "TRUE" | "yes" | "YES"))
|
||||
.unwrap_or(false),
|
||||
enable_editor_actor: env_bool("MNOTE_WEB_ENABLE_EDITOR_ACTOR", true),
|
||||
enable_page_ai_pi_lab: env_bool("MNOTE_PAGE_AI_PI_LAB", true),
|
||||
hermes_base_path: env::var("MNOTE_WEB_HERMES_BASE_PATH")
|
||||
.unwrap_or_else(|_| "/api/hermes".into()),
|
||||
compat_next_base_path: env::var("MNOTE_WEB_COMPAT_NEXT_BASE_PATH")
|
||||
@@ -382,6 +384,7 @@ mod tests {
|
||||
enable_legacy_next_compat: false,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: true,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -167,6 +167,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
|
||||
@@ -73,6 +73,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
@@ -112,6 +113,7 @@ mod tests {
|
||||
enable_legacy_next_compat: false,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
@@ -168,6 +170,7 @@ mod tests {
|
||||
enable_legacy_next_compat: false,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
@@ -236,6 +239,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
|
||||
@@ -164,10 +164,7 @@ pub async fn seed(
|
||||
Ok(Json(DevSeedResponse { ok: true, results }))
|
||||
}
|
||||
|
||||
fn apply_seed_operation(
|
||||
state: &AppState,
|
||||
operation: DevSeedOperation,
|
||||
) -> Result<Value, WebError> {
|
||||
fn apply_seed_operation(state: &AppState, operation: DevSeedOperation) -> Result<Value, WebError> {
|
||||
match operation {
|
||||
DevSeedOperation::SetupWorkspace {
|
||||
user_id,
|
||||
@@ -478,6 +475,7 @@ mod tests {
|
||||
enable_legacy_next_compat: false,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: false,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
@@ -573,10 +571,7 @@ mod tests {
|
||||
assert_eq!(payload["results"][0]["run"]["profile"], "test-profile");
|
||||
assert_eq!(payload["results"][0]["run"]["acp_runtime"], "reasonix");
|
||||
assert_eq!(payload["results"][0]["run"]["status"], "running");
|
||||
assert_eq!(
|
||||
payload["results"][0]["events"].as_array().unwrap().len(),
|
||||
1
|
||||
);
|
||||
assert_eq!(payload["results"][0]["events"].as_array().unwrap().len(), 1);
|
||||
|
||||
// Step 3: getAiRuntimeRun → 回读验证
|
||||
let (status, payload) = send_seed(
|
||||
|
||||
@@ -1196,6 +1196,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
|
||||
@@ -1779,6 +1779,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: true,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
|
||||
@@ -388,6 +388,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
|
||||
@@ -261,6 +261,88 @@ pub async fn user_access_policy_entry(
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub async fn admin_ai_entry(
|
||||
State(state): State<AppState>,
|
||||
Extension(context): Extension<RequestContext>,
|
||||
) -> Result<Response, WebError> {
|
||||
if !has_real_auth_context(&state, &context) {
|
||||
let mut response = Response::builder()
|
||||
.status(StatusCode::SEE_OTHER)
|
||||
.header(header::LOCATION, "/auth")
|
||||
.body(Body::empty())
|
||||
.map_err(|error| WebError::internal(format!("认证入口跳转响应构造失败: {error}")))?;
|
||||
stamp_gateway_headers(response.headers_mut(), false);
|
||||
return Ok(response);
|
||||
}
|
||||
if !is_local_access_policy_admin_context(&context) {
|
||||
return Err(WebError::new(
|
||||
StatusCode::FORBIDDEN,
|
||||
"ai_admin_required",
|
||||
"只有管理员可以访问 AI 管理中心",
|
||||
)
|
||||
.with_context(&context));
|
||||
}
|
||||
ai_management_response(&state, &context, true)
|
||||
}
|
||||
|
||||
pub async fn user_ai_entry(
|
||||
State(state): State<AppState>,
|
||||
Extension(context): Extension<RequestContext>,
|
||||
) -> Result<Response, WebError> {
|
||||
if !has_real_auth_context(&state, &context) {
|
||||
let mut response = Response::builder()
|
||||
.status(StatusCode::SEE_OTHER)
|
||||
.header(header::LOCATION, "/auth")
|
||||
.body(Body::empty())
|
||||
.map_err(|error| WebError::internal(format!("认证入口跳转响应构造失败: {error}")))?;
|
||||
stamp_gateway_headers(response.headers_mut(), false);
|
||||
return Ok(response);
|
||||
}
|
||||
ai_management_response(&state, &context, false)
|
||||
}
|
||||
|
||||
fn ai_management_response(
|
||||
state: &AppState,
|
||||
context: &RequestContext,
|
||||
is_admin: bool,
|
||||
) -> Result<Response, WebError> {
|
||||
let workspace_name = default_workspace_name_for_context(state, context);
|
||||
let content = crate::ssr::render_view(leptos::view! {
|
||||
<crate::ssr::pages::ai_admin::AiManagementPage
|
||||
workspace_name={workspace_name}
|
||||
is_admin={is_admin}
|
||||
/>
|
||||
});
|
||||
let title = if is_admin { "AI 管理" } else { "AI 设置" };
|
||||
let shell = if is_admin {
|
||||
"admin-ai"
|
||||
} else {
|
||||
"user-ai-admin"
|
||||
};
|
||||
let mut response = Html(format!(
|
||||
r#"<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{}</title>
|
||||
<style>{}</style>
|
||||
</head>
|
||||
<body data-mnote-web-owner="mnote-web" data-mnote-shell="{}" data-mnote-actor-id="{}">
|
||||
{}
|
||||
</body>
|
||||
</html>"#,
|
||||
title,
|
||||
crate::ssr::MNOTE_CSS,
|
||||
shell,
|
||||
escape_html(context.auth.actor_id.as_str()),
|
||||
content
|
||||
))
|
||||
.into_response();
|
||||
stamp_gateway_headers(response.headers_mut(), false);
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub async fn root_entry(
|
||||
State(state): State<AppState>,
|
||||
Extension(context): Extension<RequestContext>,
|
||||
@@ -579,6 +661,21 @@ pub async fn root_entry(
|
||||
escape_script_json(&panes_bootstrap_json),
|
||||
render_editor_island_adapter_script(),
|
||||
);
|
||||
let body_extra = if state.config().enable_page_ai_pi_lab {
|
||||
body_extra
|
||||
+ &format!(
|
||||
r#"<script>
|
||||
(function() {{
|
||||
var s = document.createElement('script');
|
||||
s.src = '/api/mnote-browser-runtime/sidebar-page-ai-pi-lab-runtime.js';
|
||||
s.onload = function() {{ if (window.createSidebarPageAiPiLabRuntime) window.createSidebarPageAiPiLabRuntime({{}}); }};
|
||||
document.body.appendChild(s);
|
||||
}})();
|
||||
</script>"#
|
||||
)
|
||||
} else {
|
||||
body_extra
|
||||
};
|
||||
("MNOTE".to_string(), render_workspace_entry(), body_extra)
|
||||
} else {
|
||||
match build_page_aggregate_snapshot(
|
||||
@@ -639,6 +736,21 @@ pub async fn root_entry(
|
||||
render_document_title_controller_script(),
|
||||
render_editor_island_adapter_script(),
|
||||
);
|
||||
let body_extra = if state.config().enable_page_ai_pi_lab {
|
||||
body_extra
|
||||
+ &format!(
|
||||
r#"<script>
|
||||
(function() {{
|
||||
var s = document.createElement('script');
|
||||
s.src = '/api/mnote-browser-runtime/sidebar-page-ai-pi-lab-runtime.js';
|
||||
s.onload = function() {{ if (window.createSidebarPageAiPiLabRuntime) window.createSidebarPageAiPiLabRuntime({{}}); }};
|
||||
document.body.appendChild(s);
|
||||
}})();
|
||||
</script>"#
|
||||
)
|
||||
} else {
|
||||
body_extra
|
||||
};
|
||||
(title.to_string(), content, body_extra)
|
||||
}
|
||||
Err(_) => ("MNOTE".to_string(), render_workspace_entry(), String::new()),
|
||||
@@ -2554,6 +2666,7 @@ mod tests {
|
||||
enable_legacy_next_compat,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url,
|
||||
@@ -3074,6 +3187,7 @@ mod tests {
|
||||
enable_legacy_next_compat: false,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
@@ -3366,6 +3480,7 @@ mod tests {
|
||||
enable_legacy_next_compat: false,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
|
||||
@@ -166,6 +166,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
|
||||
@@ -1450,7 +1450,9 @@ pub async fn toggle_skill(
|
||||
return Ok((
|
||||
StatusCode::OK,
|
||||
stamp_client_headers(),
|
||||
Json(json!({"ok": true, "skillKind": "mnote_builtin", "configScope": "user_control_plane"})),
|
||||
Json(
|
||||
json!({"ok": true, "skillKind": "mnote_builtin", "configScope": "user_control_plane"}),
|
||||
),
|
||||
));
|
||||
}
|
||||
let access =
|
||||
@@ -14553,6 +14555,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
@@ -15520,6 +15523,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
@@ -15562,6 +15566,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
@@ -15991,7 +15996,10 @@ mod tests {
|
||||
.await
|
||||
.expect("detail body");
|
||||
let detail_payload: Value = serde_json::from_slice(&detail_body).expect("detail json");
|
||||
assert_eq!(detail_payload["persistence"], ACP_RUNTIME_CONTROL_PLANE_STORE);
|
||||
assert_eq!(
|
||||
detail_payload["persistence"],
|
||||
ACP_RUNTIME_CONTROL_PLANE_STORE
|
||||
);
|
||||
assert_eq!(
|
||||
detail_payload["session"]["runs"][0]["sessionId"],
|
||||
session_id
|
||||
@@ -16399,6 +16407,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: Some(convex_url),
|
||||
@@ -16558,6 +16567,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: Some(convex_url),
|
||||
@@ -16646,6 +16656,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: Some(convex_url),
|
||||
@@ -16722,6 +16733,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: Some(convex_url),
|
||||
@@ -16905,6 +16917,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: Some(convex_url),
|
||||
|
||||
@@ -1113,6 +1113,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
@@ -1180,6 +1181,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
|
||||
@@ -282,6 +282,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
|
||||
@@ -504,6 +504,7 @@ mod tests {
|
||||
enable_legacy_next_compat: false,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
|
||||
@@ -9630,6 +9630,7 @@ mod tests {
|
||||
enable_legacy_next_compat: false,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
|
||||
@@ -2147,10 +2147,7 @@ fn clear_local_search_index(root_path: &Path) -> Result<(), WebError> {
|
||||
Err(error) => {
|
||||
return Err(WebError::bad_request_code(
|
||||
"local_search_index_delete_failed",
|
||||
format!(
|
||||
"无法删除本地搜索索引文件 {}: {error}",
|
||||
index_path.display()
|
||||
),
|
||||
format!("无法删除本地搜索索引文件 {}: {error}", index_path.display()),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -486,6 +486,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
|
||||
@@ -352,6 +352,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
@@ -420,6 +421,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
mod ai_settings;
|
||||
mod bridge;
|
||||
pub(crate) mod command_support;
|
||||
mod compat;
|
||||
mod dev_seed;
|
||||
pub(crate) mod dev_hot;
|
||||
mod dev_seed;
|
||||
mod documents;
|
||||
mod editor;
|
||||
pub(crate) mod evidence;
|
||||
@@ -28,6 +29,7 @@ pub(crate) mod onlyoffice_bridge;
|
||||
mod page_ai_board;
|
||||
mod page_ai_opencode;
|
||||
mod page_ai_openhub;
|
||||
mod page_ai_pi;
|
||||
mod page_ai_workflow;
|
||||
mod query_support;
|
||||
mod resource_trash;
|
||||
@@ -49,8 +51,7 @@ pub(crate) use local_folder_source::{
|
||||
control_plane_status_display, decode_local_id_segment, ensure_local_path_read_access,
|
||||
ensure_local_workspace_access, ensure_local_workspace_read_access_with_state,
|
||||
ensure_local_workspace_write_access_with_state, local_markdown_conflict_detection_key,
|
||||
local_workspace_id_from_root_uri, update_local_markdown_title,
|
||||
write_local_markdown_page_body,
|
||||
local_workspace_id_from_root_uri, update_local_markdown_title, write_local_markdown_page_body,
|
||||
};
|
||||
#[cfg(test)]
|
||||
pub(crate) use local_search_index::write_local_index_settings;
|
||||
@@ -84,6 +85,8 @@ pub fn build_router(state: AppState) -> Router {
|
||||
"/user/access-policy",
|
||||
get(gateway::user_access_policy_entry),
|
||||
)
|
||||
.route("/admin/ai", get(gateway::admin_ai_entry))
|
||||
.route("/user/ai", get(gateway::user_ai_entry))
|
||||
.route("/auth", get(gateway::auth_entry).post(gateway::auth_entry))
|
||||
.route("/search", get(search::shell))
|
||||
.route("/knowledge", get(gateway::root_entry))
|
||||
@@ -275,6 +278,10 @@ pub fn build_router(state: AppState) -> Router {
|
||||
"/api/mnote-browser-runtime/sidebar-page-ai-target-runtime.js",
|
||||
get(web_shell::sidebar_page_ai_target_runtime_asset),
|
||||
)
|
||||
.route(
|
||||
"/api/mnote-browser-runtime/sidebar-page-ai-pi-lab-runtime.js",
|
||||
get(web_shell::sidebar_page_ai_pi_lab_runtime_asset),
|
||||
)
|
||||
.route(
|
||||
"/api/mnote-browser-runtime/sidebar-page-settings-runtime.js",
|
||||
get(web_shell::sidebar_page_settings_runtime_asset),
|
||||
@@ -588,6 +595,27 @@ pub fn build_router(state: AppState) -> Router {
|
||||
"/api/page-ai/board/runs/{run_id}/cancel",
|
||||
post(page_ai_board::cancel_run),
|
||||
)
|
||||
.route("/api/page-ai/pi/status", get(page_ai_pi::status))
|
||||
.route("/api/page-ai/pi/bootstrap", post(page_ai_pi::bootstrap))
|
||||
.route("/api/page-ai/pi/start", post(page_ai_pi::start))
|
||||
.route("/api/page-ai/pi/send", post(page_ai_pi::send))
|
||||
.route("/api/page-ai/pi/abort", post(page_ai_pi::abort))
|
||||
.route("/api/page-ai/pi/events", get(page_ai_pi::events))
|
||||
.route("/api/page-ai/pi/sessions", get(page_ai_pi::list_sessions))
|
||||
.route(
|
||||
"/api/page-ai/pi/sessions/{session_id}",
|
||||
get(page_ai_pi::get_session_history),
|
||||
)
|
||||
.route(
|
||||
"/api/page-ai/pi/sessions/{session_id}/events",
|
||||
get(page_ai_pi::get_session_events),
|
||||
)
|
||||
.route("/api/page-ai/pi/tool-call", post(page_ai_pi::tool_call))
|
||||
.route(
|
||||
"/api/page-ai/pi/tool-call-bridge",
|
||||
post(page_ai_pi::tool_call_bridge),
|
||||
)
|
||||
.route("/page-ai/pi", get(page_ai_pi::shell))
|
||||
.route(
|
||||
"/api/sidebar/shortcuts",
|
||||
get(sidebar_shortcuts::list_shortcuts).post(sidebar_shortcuts::upsert_shortcut),
|
||||
@@ -628,6 +656,29 @@ pub fn build_router(state: AppState) -> Router {
|
||||
"/api/user/access-policy/grants/{grant_id}",
|
||||
delete(local_folder_source::delete_user_access_grant),
|
||||
)
|
||||
.route(
|
||||
"/api/ai-settings/effective",
|
||||
get(ai_settings::effective_settings),
|
||||
)
|
||||
.route(
|
||||
"/api/ai-settings/access-scopes",
|
||||
get(ai_settings::user_access_scopes),
|
||||
)
|
||||
.route(
|
||||
"/api/ai-admin/access-scopes",
|
||||
get(ai_settings::admin_access_scopes),
|
||||
)
|
||||
.route("/api/ai-settings/receipts", get(ai_settings::user_receipts))
|
||||
.route("/api/ai-admin/receipts", get(ai_settings::admin_receipts))
|
||||
.route(
|
||||
"/api/ai-admin/settings",
|
||||
get(ai_settings::admin_get_settings).put(ai_settings::admin_put_settings),
|
||||
)
|
||||
.route("/api/ai-admin/users", get(ai_settings::admin_list_users))
|
||||
.route(
|
||||
"/api/ai-admin/users/{user_id}/settings",
|
||||
get(ai_settings::admin_get_user_settings).put(ai_settings::admin_put_user_settings),
|
||||
)
|
||||
.route(
|
||||
"/api/admin/share-links",
|
||||
get(local_folder_source::get_share_links).post(local_folder_source::create_share_link),
|
||||
@@ -1035,6 +1086,7 @@ mod tests {
|
||||
enable_legacy_next_compat: false,
|
||||
enable_debug_shell_routes,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
|
||||
@@ -369,6 +369,7 @@ mod tests {
|
||||
enable_legacy_next_compat: false,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
|
||||
@@ -1998,6 +1998,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -611,6 +611,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
|
||||
@@ -1163,6 +1163,7 @@ mod tests {
|
||||
enable_legacy_next_compat: false,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
|
||||
@@ -776,6 +776,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
@@ -906,6 +907,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
@@ -1517,6 +1519,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
|
||||
@@ -229,6 +229,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
@@ -331,6 +332,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
@@ -400,6 +402,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
@@ -480,6 +483,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
|
||||
@@ -358,6 +358,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
|
||||
@@ -2656,6 +2656,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: true,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
@@ -3001,6 +3002,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: true,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
@@ -3682,6 +3684,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: true,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
|
||||
@@ -298,6 +298,8 @@ pub async fn document_page_shell(
|
||||
secondary_requested,
|
||||
secondary_invalid,
|
||||
);
|
||||
let pi_lab_loader_script =
|
||||
render_page_ai_pi_lab_loader_script(state.config().enable_page_ai_pi_lab);
|
||||
let body_content = crate::ssr::render_view(leptos::view! {
|
||||
<DocumentPage
|
||||
title={title.to_string()}
|
||||
@@ -339,6 +341,7 @@ pub async fn document_page_shell(
|
||||
{}
|
||||
{}
|
||||
{}
|
||||
{}
|
||||
</body>
|
||||
</html>"#,
|
||||
escape_html(title),
|
||||
@@ -368,6 +371,7 @@ pub async fn document_page_shell(
|
||||
r#"<script type="module" src="{}"></script>"#,
|
||||
mnote_browser_runtime_src("document-conflict-panel-runtime.js")
|
||||
),
|
||||
pi_lab_loader_script,
|
||||
);
|
||||
let mut response = Html(html).into_response();
|
||||
stamp_shell_headers(response.headers_mut(), "document");
|
||||
@@ -850,6 +854,23 @@ pub(crate) fn render_editor_island_adapter_script() -> String {
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn render_page_ai_pi_lab_loader_script(enabled: bool) -> String {
|
||||
if !enabled {
|
||||
return String::new();
|
||||
}
|
||||
format!(
|
||||
r#"<script>
|
||||
(function() {{
|
||||
var s = document.createElement('script');
|
||||
s.src = '{}';
|
||||
s.onload = function() {{ if (window.createSidebarPageAiPiLabRuntime) window.createSidebarPageAiPiLabRuntime({{}}); }};
|
||||
document.body.appendChild(s);
|
||||
}})();
|
||||
</script>"#,
|
||||
mnote_browser_runtime_src("sidebar-page-ai-pi-lab-runtime.js")
|
||||
)
|
||||
}
|
||||
|
||||
fn leptos_tiptap_runtime_src(asset: &str) -> String {
|
||||
let base = format!("/api/leptos-tiptap-runtime/{asset}");
|
||||
if let Some(cache_buster) = crate::routes::dev_hot::dev_hot_cache_buster() {
|
||||
@@ -2591,6 +2612,20 @@ pub async fn sidebar_page_ai_target_runtime_asset() -> Response {
|
||||
.unwrap_or_else(|_| Response::new(Body::empty()))
|
||||
}
|
||||
|
||||
pub async fn sidebar_page_ai_pi_lab_runtime_asset() -> Response {
|
||||
const JS: &str = include_str!("../../browser/sidebar-page-ai-pi-lab-runtime.js");
|
||||
Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header(
|
||||
header::CONTENT_TYPE,
|
||||
"application/javascript; charset=utf-8",
|
||||
)
|
||||
.header(header::CACHE_CONTROL, runtime_asset_cache_control())
|
||||
.header(HEADER_MNOTE_WEB_OWNER, "mnote-web")
|
||||
.body(browser_runtime_js_body(JS))
|
||||
.unwrap_or_else(|_| Response::new(Body::empty()))
|
||||
}
|
||||
|
||||
pub async fn sidebar_page_settings_runtime_asset() -> Response {
|
||||
const JS: &str = include_str!("../../browser/sidebar-page-settings-runtime.js");
|
||||
Response::builder()
|
||||
@@ -3669,6 +3704,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
@@ -3726,6 +3762,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
@@ -3847,6 +3884,7 @@ mod tests {
|
||||
enable_legacy_next_compat: false,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: Some("http://127.0.0.1:9".into()),
|
||||
@@ -3989,6 +4027,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
@@ -4729,6 +4768,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
@@ -4797,6 +4837,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
@@ -4935,7 +4976,8 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn local_folder_page_aggregate_prefers_control_plane_ui_preference_over_default_title_header() {
|
||||
async fn local_folder_page_aggregate_prefers_control_plane_ui_preference_over_default_title_header(
|
||||
) {
|
||||
let root = std::env::temp_dir().join(format!(
|
||||
"mnote-local-page-aggregate-ui-pref-{}",
|
||||
std::process::id()
|
||||
@@ -4954,6 +4996,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
@@ -5499,6 +5542,7 @@ mod tests {
|
||||
enable_legacy_next_compat: true,
|
||||
enable_debug_shell_routes: false,
|
||||
enable_editor_actor: true,
|
||||
enable_page_ai_pi_lab: false,
|
||||
hermes_base_path: "/api/hermes".into(),
|
||||
compat_next_base_path: "/api/compat/next".into(),
|
||||
convex_url: None,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
||||
//! SSR 页面组件
|
||||
|
||||
pub mod admin;
|
||||
pub mod ai_admin;
|
||||
pub mod auth;
|
||||
pub mod document;
|
||||
pub mod home;
|
||||
|
||||
Reference in New Issue
Block a user