chore: retire openhub and pi ts runtime

This commit is contained in:
Agent Board
2026-07-12 14:18:26 +08:00
parent 62959b0c4d
commit 2f5902e3e7
45 changed files with 367 additions and 925 deletions
@@ -1,13 +1,10 @@
use crate::error::WebError;
use control_plane::{ControlPlaneStore, DirectoryGrantRecord, UserRecord};
use control_plane::UserRecord;
use reqwest::header::{HeaderMap, HeaderValue};
use serde_json::{json, Value};
use std::collections::hash_map::DefaultHasher;
use std::env;
use std::hash::{Hash, Hasher};
use std::time::Duration;
const DEFAULT_OPENHUB_BASE_URL: &str = "http://127.0.0.1:18080";
const DEFAULT_WEKNORA_ENDPOINT: &str = "http://127.0.0.1:8080/api/v1";
const PROVIDER_IDENTITY_SYNC_TIMEOUT_MS: u64 = 1_500;
@@ -31,13 +28,6 @@ fn clean_url(value: String) -> Option<String> {
(!trimmed.is_empty()).then_some(trimmed)
}
fn openhub_base_url() -> String {
env::var("MNOTE_OPENHUB_BASE_URL")
.ok()
.and_then(clean_url)
.unwrap_or_else(|| DEFAULT_OPENHUB_BASE_URL.to_string())
}
fn weknora_endpoint() -> String {
env::var("MNOTE_WEKNORA_ENDPOINT")
.or_else(|_| env::var("WEKNORA_ENDPOINT"))
@@ -67,12 +57,6 @@ fn internal_headers() -> HeaderMap {
headers
}
fn stable_openhub_user_id(user_id: &str) -> i64 {
let mut hasher = DefaultHasher::new();
format!("mnote-openhub-user:{user_id}").hash(&mut hasher);
(hasher.finish() % 1_900_000_000) as i64 + 100_000_000
}
fn fallback_email(user: &UserRecord) -> String {
user.email
.as_deref()
@@ -82,13 +66,6 @@ fn fallback_email(user: &UserRecord) -> String {
.unwrap_or_else(|| format!("{}@mnote.local", user.username))
}
fn default_workspace_path(user: &UserRecord) -> String {
format!(
"/mnt/Data1T/Mnote_data/users/{}/workspaces/my-space",
user.id
)
}
fn sync_disabled() -> bool {
!env_flag("MNOTE_PROVIDER_IDENTITY_SYNC", true)
}
@@ -96,7 +73,6 @@ fn sync_disabled() -> bool {
pub async fn sync_provider_identities(
user: &UserRecord,
password: &str,
directory_grants: &[DirectoryGrantRecord],
) -> Vec<ProviderIdentitySyncResult> {
if sync_disabled() {
return vec![ProviderIdentitySyncResult {
@@ -107,78 +83,16 @@ pub async fn sync_provider_identities(
}];
}
let sync_openhub = env_flag("MNOTE_PROVIDER_IDENTITY_SYNC_OPENHUB", true);
let sync_weknora = env_flag("MNOTE_PROVIDER_IDENTITY_SYNC_WEKNORA", true);
match (sync_openhub, sync_weknora) {
(true, true) => {
let (openhub, weknora) = tokio::join!(
sync_openhub_identity(user, password, directory_grants),
sync_weknora_identity(user, password)
);
vec![openhub, weknora]
}
(true, false) => vec![sync_openhub_identity(user, password, directory_grants).await],
(false, true) => vec![sync_weknora_identity(user, password).await],
(false, false) => vec![ProviderIdentitySyncResult {
if sync_weknora {
vec![sync_weknora_identity(user, password).await]
} else {
vec![ProviderIdentitySyncResult {
provider: "all",
ok: true,
message: "provider identity sync disabled".to_string(),
provider_user_id: None,
}],
}
}
pub async fn sync_openhub_directory_permissions_for_user_id(
control_plane: &dyn ControlPlaneStore,
user_id: &str,
) -> ProviderIdentitySyncResult {
if sync_disabled() || !env_flag("MNOTE_PROVIDER_IDENTITY_SYNC_OPENHUB", true) {
return ProviderIdentitySyncResult {
provider: "openhub",
ok: true,
message: "provider identity sync disabled".to_string(),
provider_user_id: None,
};
}
let user_id = user_id.trim();
let provider_user_id = stable_openhub_user_id(user_id);
let directory_grants = match control_plane.list_directory_grants_for_actor(user_id) {
Ok(grants) => grants,
Err(error) => {
return ProviderIdentitySyncResult {
provider: "openhub",
ok: false,
message: format!("mnote directory grants read failed: {error}"),
provider_user_id: Some(provider_user_id.to_string()),
};
}
};
let payload = json!({
"mnote_user_id": user_id,
"allowedRoots": openhub_allowed_roots_payload(&directory_grants),
});
match post_json(
join_url(
&openhub_base_url(),
&format!("/api/internal/mnote/users/{provider_user_id}/directory-permissions/sync"),
),
payload,
)
.await
{
Ok(_) => ProviderIdentitySyncResult {
provider: "openhub",
ok: true,
message: "directory permissions synced".to_string(),
provider_user_id: Some(provider_user_id.to_string()),
},
Err(error) => ProviderIdentitySyncResult {
provider: "openhub",
ok: false,
message: error.message().to_string(),
provider_user_id: Some(provider_user_id.to_string()),
},
}]
}
}
@@ -210,74 +124,6 @@ async fn post_json(url: String, body: Value) -> Result<Value, WebError> {
Ok(payload)
}
async fn sync_openhub_identity(
user: &UserRecord,
password: &str,
directory_grants: &[DirectoryGrantRecord],
) -> ProviderIdentitySyncResult {
let provider_user_id = stable_openhub_user_id(&user.id);
let payload = json!({
"provider_user_id": provider_user_id,
"mnote_user_id": user.id,
"username": user.username,
"email": fallback_email(user),
"password": password,
"workspace_path": default_workspace_path(user),
"allowedRoots": openhub_allowed_roots_payload(directory_grants),
"disabled": user.status != "active",
"is_admin": false,
});
match post_json(
join_url(&openhub_base_url(), "/api/internal/mnote/users/provision"),
payload,
)
.await
{
Ok(_) => ProviderIdentitySyncResult {
provider: "openhub",
ok: true,
message: "synced".to_string(),
provider_user_id: Some(provider_user_id.to_string()),
},
Err(error) => ProviderIdentitySyncResult {
provider: "openhub",
ok: false,
message: error.message().to_string(),
provider_user_id: Some(provider_user_id.to_string()),
},
}
}
fn openhub_allowed_roots_payload(directory_grants: &[DirectoryGrantRecord]) -> Vec<Value> {
directory_grants
.iter()
.filter(|grant| !is_default_workspace_auto_grant(grant))
.filter(|grant| grant.status.trim() == "active")
.filter(|grant| !grant.root_path.trim().is_empty())
.map(|grant| {
json!({
"grantId": grant.id,
"rootUri": grant.root_uri,
"rootPath": grant.root_path,
"permission": grant.permission,
"recursive": grant.recursive,
"capabilities": serde_json::from_str::<Vec<String>>(&grant.capabilities_json).unwrap_or_default(),
"source": grant.source,
})
})
.collect()
}
fn is_default_workspace_auto_grant(grant: &DirectoryGrantRecord) -> bool {
grant.source.trim() == "auto"
&& grant.permission.trim() == "write"
&& grant.recursive
&& grant.created_by.as_deref().map(str::trim) == Some(grant.user_id.as_str())
&& grant.workspace_id.is_some()
&& grant.root_uri.starts_with("local://users/")
&& grant.root_uri.ends_with("/workspaces/my-space")
}
async fn sync_weknora_identity(user: &UserRecord, password: &str) -> ProviderIdentitySyncResult {
let payload = json!({
"mnote_user_id": user.id,
+1 -10
View File
@@ -2301,16 +2301,7 @@ async fn handle_control_plane_auth_action(
});
let provider_sync_results = if matches!(flow, "signUp" | "signIn") {
let directory_grants = state
.control_plane()
.list_directory_grants_for_actor(&resolved.user.id)
.unwrap_or_default();
let results = sync_provider_identities(
&resolved.user,
&password_for_provider_sync,
&directory_grants,
)
.await;
let results = sync_provider_identities(&resolved.user, &password_for_provider_sync).await;
let _ = state.control_plane().append_audit(AppendAuditInput {
actor_user_id: Some(resolved.user.id.clone()),
action: "control.auth.provider_identities_synced".to_string(),
@@ -5,7 +5,6 @@ use crate::page_aggregate::{
PageAggregate, PageAggregateSource, PageBody, PageHead, PageIdentity, PageLayout, PageOptions,
PagePermissions, PageStats, PageTree,
};
use crate::provider_identity_sync::sync_openhub_directory_permissions_for_user_id;
use crate::routes::local_markdown_parser::{
file_stem_title, parse_markdown_attachment_refs, parse_markdown_page, split_frontmatter,
};
@@ -2442,14 +2441,6 @@ pub async fn create_local_access_grant(
) -> Result<(StatusCode, Json<Value>), WebError> {
let payload = add_control_plane_local_access_grant_for_context(&state, &context, request)
.map_err(|error| error.with_context(&context))?;
if let Some(user_id) = payload
.pointer("/grant/userId")
.and_then(Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
{
sync_openhub_directory_permissions_for_user_id(state.control_plane(), user_id).await;
}
Ok((StatusCode::OK, Json(payload)))
}
@@ -2460,14 +2451,6 @@ pub async fn create_user_access_grant(
) -> Result<(StatusCode, Json<Value>), WebError> {
let payload = add_control_plane_user_access_grant_for_context(&state, &context, request)
.map_err(|error| error.with_context(&context))?;
if let Some(user_id) = payload
.pointer("/grant/userId")
.and_then(Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
{
sync_openhub_directory_permissions_for_user_id(state.control_plane(), user_id).await;
}
Ok((StatusCode::OK, Json(payload)))
}
@@ -2478,14 +2461,6 @@ pub async fn delete_local_access_grant(
) -> Result<(StatusCode, Json<Value>), WebError> {
let payload = delete_control_plane_local_access_grant_for_context(&state, &context, &grant_id)
.map_err(|error| error.with_context(&context))?;
if let Some(user_id) = payload
.get("revokedUserId")
.and_then(Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
{
sync_openhub_directory_permissions_for_user_id(state.control_plane(), user_id).await;
}
Ok((StatusCode::OK, Json(payload)))
}
@@ -2496,14 +2471,6 @@ pub async fn delete_user_access_grant(
) -> Result<(StatusCode, Json<Value>), WebError> {
let payload = delete_control_plane_user_access_grant_for_context(&state, &context, &grant_id)
.map_err(|error| error.with_context(&context))?;
if let Some(user_id) = payload
.get("revokedUserId")
.and_then(Value::as_str)
.map(str::trim)
.filter(|value| !value.is_empty())
{
sync_openhub_directory_permissions_for_user_id(state.control_plane(), user_id).await;
}
Ok((StatusCode::OK, Json(payload)))
}
-63
View File
@@ -28,7 +28,6 @@ mod onlyoffice;
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;
@@ -408,68 +407,6 @@ pub fn build_router(state: AppState) -> Router {
"/api/page-ai/opencode/status",
get(page_ai_opencode::status),
)
.route("/api/page-ai/openhub/status", get(page_ai_openhub::status))
.route(
"/api/page-ai/openhub/bootstrap",
post(page_ai_openhub::bootstrap),
)
.route(
"/api/page-ai/openhub/artifact-index",
get(page_ai_openhub::artifact_index_get).post(page_ai_openhub::artifact_index_upsert),
)
.route("/page-ai/openhub/ai", get(page_ai_openhub::ai_shell))
.route(
"/page-ai/openhub/ai/{*path}",
any(page_ai_openhub::ai_proxy),
)
.route(
"/page-ai/openhub/login",
any(page_ai_openhub::non_ai_route_guard),
)
.route(
"/page-ai/openhub/login/{*path}",
any(page_ai_openhub::non_ai_route_guard),
)
.route(
"/page-ai/openhub/admin",
any(page_ai_openhub::non_ai_route_guard),
)
.route(
"/page-ai/openhub/admin/{*path}",
any(page_ai_openhub::non_ai_route_guard),
)
.route(
"/page-ai/openhub/file",
any(page_ai_openhub::non_ai_route_guard),
)
.route(
"/page-ai/openhub/file/{*path}",
any(page_ai_openhub::non_ai_route_guard),
)
.route(
"/page-ai/openhub/files",
any(page_ai_openhub::non_ai_route_guard),
)
.route(
"/page-ai/openhub/files/{*path}",
any(page_ai_openhub::non_ai_route_guard),
)
.route(
"/page-ai/openhub/knowledge",
any(page_ai_openhub::non_ai_route_guard),
)
.route(
"/page-ai/openhub/knowledge/{*path}",
any(page_ai_openhub::non_ai_route_guard),
)
.route(
"/page-ai/openhub/git",
any(page_ai_openhub::non_ai_route_guard),
)
.route(
"/page-ai/openhub/git/{*path}",
any(page_ai_openhub::non_ai_route_guard),
)
.route(
"/api/page-ai/opencode/session",
post(page_ai_opencode::bind_session),
File diff suppressed because it is too large Load Diff
+2 -12
View File
@@ -1,6 +1,6 @@
//! Pi-first Page AI Lab — MNote 托管的后端垂直切片。
//!
//! 该模块默认启用 MNote 托管的 Pi Rust Page AI 后端;OpenHub 仅保留为迁移期兼容 / admin 边界。
//! 该模块默认启用 MNote 托管的 Pi Rust Page AI 后端;OpenHub 与 Pi TS 已退役到 recycle 边界。
//! Pi 进程通过 RPC subprocess 托管,MNote bridge tools 在 Rust 后端按 allowed roots 执行权限校验。
use crate::app::AppState;
@@ -60,7 +60,6 @@ const PI_LAB_MAX_STARTS_PER_WINDOW: usize = 4;
const PI_LAB_MAX_SENDS_PER_WINDOW: usize = 12;
const PI_LAB_MAX_TOOLS_PER_WINDOW: usize = 40;
const PI_LAB_RUNTIME_IMPL_RUST: &str = "pi-rust";
const PI_LAB_RUNTIME_IMPL_TS: &str = "pi-ts";
const PI_LAB_MANAGED_BUILTIN_TOOLS: [&str; 8] = [
"read",
"write",
@@ -970,9 +969,6 @@ fn pi_binary() -> String {
if let Some(explicit) = env_trimmed("MNOTE_PAGE_AI_PI_BIN") {
return explicit;
}
if pi_runtime_impl() == PI_LAB_RUNTIME_IMPL_TS {
return env_trimmed("MNOTE_PAGE_AI_PI_TS_BIN").unwrap_or_else(|| "pi".into());
}
env_trimmed("MNOTE_PAGE_AI_PI_RUST_BIN")
.or_else(first_existing_pi_rust_binary)
.unwrap_or_else(|| "pi-rust".into())
@@ -983,7 +979,6 @@ fn pi_runtime_impl() -> String {
.or_else(|| env_trimmed("MNOTE_PAGE_AI_PI_RUNTIME_IMPL"))
.unwrap_or_else(|| PI_LAB_RUNTIME_IMPL_RUST.into());
match raw.trim().to_ascii_lowercase().as_str() {
"ts" | "typescript" | "pi-ts" | "legacy-ts" => PI_LAB_RUNTIME_IMPL_TS.into(),
"rust" | "rs" | "pi-rust" | "pi_agent_rust" | "pi-agent-rust" => {
PI_LAB_RUNTIME_IMPL_RUST.into()
}
@@ -1026,15 +1021,10 @@ fn pi_runtime_binary_available(binary: &str) -> bool {
.any(|candidate| candidate.is_file())
}
fn pi_runtime_install_hint(runtime_impl: &str, binary: &str) -> Option<String> {
fn pi_runtime_install_hint(_runtime_impl: &str, binary: &str) -> Option<String> {
if runtime_mode() == "mock" || pi_runtime_binary_available(binary) {
return None;
}
if runtime_impl == PI_LAB_RUNTIME_IMPL_TS {
return Some(format!(
"未找到 Pi TS runtime: {binary}。请设置 MNOTE_PAGE_AI_PI_TS_BIN 或 MNOTE_PAGE_AI_PI_BIN。"
));
}
Some(format!(
"未找到 Pi Rust runtime: {binary}。请安装到 ~/.local/share/mnote/pi-rust/bin/pi,或设置 MNOTE_PAGE_AI_PI_RUST_BIN / MNOTE_PAGE_AI_PI_BIN。"
))
+36 -34
View File
@@ -1272,7 +1272,6 @@ const AI_ADMIN_SCRIPT: &str = r#"
function renderServicePanels(piStatus, effectivePayload) {
var providers = effectivePayload && Array.isArray(effectivePayload.providers) ? effectivePayload.providers : [];
var openhubStatus = '兼容保留';
var defaultModel = effectivePayload && (effectivePayload.defaultModel || effectivePayload.default_model || '');
var piDefaultModel = piStatus
? normalizeModelRef(piStatus.defaultModelProvider || piStatus.default_model_provider, piStatus.defaultModelId || piStatus.default_model_id)
@@ -1329,20 +1328,6 @@ const AI_ADMIN_SCRIPT: &str = r#"
'</details>' +
'</div>' +
'</div>' +
'<div class="mnote-ai-admin-admin-card">' +
'<div class="mnote-ai-admin-admin-card-head"><h3>OpenHub</h3><span class="mnote-ai-admin-actions-bar">' +
renderTag(openhubStatus, 'orange') +
'<a href="/page-ai/openhub/admin" class="mnote-ai-admin-policy-link">打开 OpenHub Admin</a>' +
'</span></div>' +
'<div class="mnote-ai-admin-admin-card-body">' +
'<div class="mnote-ai-admin-kv-grid">' +
renderKv('定位', '迁移期兼容 / admin 边界,不再作为默认 Page AI chat') +
renderKv('MNote 侧入口', '/page-ai/openhub/ai') +
renderKv('管理入口', '/page-ai/openhub/admin') +
renderKv('后续策略', '只保留一个用户 Page AI 入口,OpenHub 不再扩展重复面板能力') +
'</div>' +
'</div>' +
'</div>' +
'</div>';
}
if (channelsPanel) {
@@ -1408,7 +1393,6 @@ const AI_ADMIN_SCRIPT: &str = r#"
if (healthGrid) {
healthGrid.innerHTML = [
['Pi Rust Page AI', piRunning ? '运行中' : (piRuntimeAvailable ? '可启动' : '不可用'), 'impl=' + piRuntimeImpl + ' · runtime=' + piRuntime + ' · model=' + piDefaultModel],
['OpenHub', '兼容保留', '迁移期 admin / fallback 边界,不再作为默认 Page AI chat'],
['LightRAG', '默认', '唯一默认 knowledge providerPi 通过 MNote facade 查询'],
['Control-plane', effectivePayload && (effectivePayload.sourceOfTruth || effectivePayload.source_of_truth) || 'directory_grants', '授权、策略、receipt 真相层']
].map(function(item) {
@@ -1605,7 +1589,7 @@ const AI_ADMIN_SCRIPT: &str = r#"
'</div>';
var body = '';
if (activePanel === 'tools') {
body = '<div class="mnote-ai-admin-user-section"><div class="mnote-ai-admin-user-section-title"><h3>工具权限</h3><small>按 OpenHub risk_level 分组</small></div>' + (tools || '<div class="mnote-ai-admin-empty">暂无工具</div>') + '</div>';
body = '<div class="mnote-ai-admin-user-section"><div class="mnote-ai-admin-user-section-title"><h3>工具权限</h3><small>按 MNote policy 分组</small></div>' + (tools || '<div class="mnote-ai-admin-empty">暂无工具</div>') + '</div>';
} else if (activePanel === 'skills') {
body = '<div class="mnote-ai-admin-user-section"><div class="mnote-ai-admin-user-section-title"><h3>Skills</h3><small>全局启用后用户可单独降权</small></div>' + (skills || '<div class="mnote-ai-admin-empty">暂无 Skill</div>') + '</div>';
} else if (activePanel === 'mcp') {
@@ -1621,7 +1605,7 @@ const AI_ADMIN_SCRIPT: &str = r#"
'</div>' +
(roots || '<div class="mnote-ai-admin-empty">暂无可单独管理的目录权限</div>') + '</div>';
} else {
body = '<div class="mnote-ai-admin-user-section"><div class="mnote-ai-admin-user-section-title"><h3>模型权限</h3><small>按 provider 分组,参考 OpenHub 用户模型权限</small></div>' + (modelRows || '<div class="mnote-ai-admin-empty">暂无可用模型</div>') +
body = '<div class="mnote-ai-admin-user-section"><div class="mnote-ai-admin-user-section-title"><h3>模型权限</h3><small>按 provider/model policy 分组</small></div>' + (modelRows || '<div class="mnote-ai-admin-empty">暂无可用模型</div>') +
'<label class="mnote-ai-admin-label">默认模型</label><select class="mnote-ai-admin-select" data-user-default-model>' +
catalogModels.filter(function(model) { return allowedIds.has(model.id); }).map(function(model) {
return '<option value="' + escapeHtml(model.id) + '"' + (payload.defaultModel === model.id ? ' selected' : '') + '>' + escapeHtml(model.name || model.id) + '</option>';
@@ -1719,10 +1703,21 @@ const AI_ADMIN_SCRIPT: &str = r#"
var allowedModels = selectedUserSettings && selectedUserSettings.allowedModels
? selectedUserSettings.allowedModels.map(function(model) { return model.id; })
: [];
var catalogModelIds = new Set((selectedUserSettings && selectedUserSettings.catalogModels || []).map(function(model) { return model.id; }).filter(Boolean));
if (catalogModelIds.size) {
allowedModels = allowedModels.filter(function(modelId) { return catalogModelIds.has(modelId); });
}
if (modelInputs.length) {
allowedModels = Array.from(userSettingsEditor.querySelectorAll('[data-user-model]:checked')).map(function(input) { return input.getAttribute('data-user-model'); });
}
var defaultSelect = userSettingsEditor.querySelector('[data-user-default-model]');
var defaultModel = defaultSelect ? defaultSelect.value : ((selectedUserSettings && selectedUserSettings.defaultModel) || '');
if (catalogModelIds.size && defaultModel && !catalogModelIds.has(defaultModel)) {
defaultModel = allowedModels[0] || '';
}
if (defaultModel && allowedModels.indexOf(defaultModel) === -1) {
defaultModel = allowedModels[0] || '';
}
var tools = {};
(selectedUserSettings && selectedUserSettings.tools || []).forEach(function(tool) {
tools[tool.name] = tool.action;
@@ -1752,16 +1747,19 @@ const AI_ADMIN_SCRIPT: &str = r#"
piExtensions[input.getAttribute('data-user-pi-extension')] = input.checked;
});
try {
var payload = {
tools: tools,
skills: skills,
mcpServers: mcpServers,
piExtensions: piExtensions
};
if (modelInputs.length || defaultSelect) {
payload.allowedModels = allowedModels;
payload.defaultModel = defaultModel;
}
await requestJson('/api/ai-admin/users/' + encodeURIComponent(selectedUserId) + '/settings', {
method: 'PUT',
body: JSON.stringify({
allowedModels: allowedModels,
defaultModel: defaultSelect ? defaultSelect.value : ((selectedUserSettings && selectedUserSettings.defaultModel) || ''),
tools: tools,
skills: skills,
mcpServers: mcpServers,
piExtensions: piExtensions
})
body: JSON.stringify(payload)
});
showSaveStatus(userSettingsStatus, 'success', '用户 AI 配置已保存');
await loadUserSettings(selectedUserId);
@@ -1835,7 +1833,7 @@ const AI_ADMIN_SCRIPT: &str = r#"
try {
piStatus = await requestJson('/api/page-ai/pi/status', { method: 'GET' });
} catch (_err) {
// Pi Lab 状态失败不阻塞 OpenHub / 管理页主面板。
// Pi Lab 状态失败不阻塞管理页主面板。
}
renderServicePanels(piStatus, payload);
}
@@ -2518,13 +2516,18 @@ const AI_ADMIN_SCRIPT: &str = r#"
}
var allowedRaw = val('allowedModels');
var failoverRaw = val('failoverChains');
var allowedModels = allowedRaw ? allowedRaw.split(',').map(function(s) { return s.trim(); }).filter(Boolean) : [];
var defaultModel = val('defaultModel');
if (defaultModel && allowedModels.length && allowedModels.indexOf(defaultModel) === -1) {
defaultModel = allowedModels[0];
}
return {
id: val('id'),
name: val('name'),
baseUrl: val('baseUrl'),
secretRef: val('secretRef'),
allowedModels: allowedRaw ? allowedRaw.split(',').map(function(s) { return s.trim(); }).filter(Boolean) : [],
defaultModel: val('defaultModel'),
allowedModels: allowedModels,
defaultModel: defaultModel,
defaultBuildModel: val('defaultBuildModel'),
defaultPlanModel: val('defaultPlanModel'),
defaultTaskModel: val('defaultTaskModel'),
@@ -3029,7 +3032,7 @@ pub fn AiManagementPage(
<div class="mnote-ai-admin-section-header">
<div>
<h2>"用户管理"</h2>
<p class="mnote-ai-admin-section-desc">"参考 OpenHub用户、逐用户模型/工具/目录授权"</p>
<p class="mnote-ai-admin-section-desc">"用户、逐用户模型/工具/目录授权"</p>
</div>
</div>
<div class="mnote-ai-admin-section-body">
@@ -3083,9 +3086,8 @@ pub fn AiManagementPage(
<div class="mnote-ai-admin-section-header">
<div>
<h2>"Pi Rust 服务"</h2>
<p class="mnote-ai-admin-section-desc">"Pi Rust 是默认 Page AI runtimeOpenHub 仅保留为迁移期兼容 / admin 边界"</p>
<p class="mnote-ai-admin-section-desc">"Pi Rust 是默认 Page AI runtime历史 host 已软归档到 recycle 边界"</p>
</div>
<a href="/page-ai/openhub/admin" class="mnote-ai-admin-policy-link">"打开 OpenHub Admin"</a>
</div>
<div class="mnote-ai-admin-section-body">
<div data-ai-admin-service-panel>
@@ -3263,7 +3265,7 @@ pub fn AiManagementPage(
<div class="mnote-ai-admin-section-header">
<div>
<h2>"渠道管理"</h2>
<p class="mnote-ai-admin-section-desc">"OpenHub 的渠道管理在 MNote 中收敛为 provider/model policy"</p>
<p class="mnote-ai-admin-section-desc">"渠道管理在 MNote 中收敛为 provider/model policy"</p>
</div>
</div>
<div class="mnote-ai-admin-section-body">
@@ -3308,7 +3310,7 @@ pub fn AiManagementPage(
<div class="mnote-ai-admin-section-header">
<div>
<h2>"系统监控"</h2>
<p class="mnote-ai-admin-section-desc">"以 AI 相关服务健康为主,不复制 OpenHub 进程管理"</p>
<p class="mnote-ai-admin-section-desc">"以 AI 相关服务健康为主,不复制已退役 host 进程管理"</p>
</div>
</div>
<div class="mnote-ai-admin-section-body">
@@ -2221,71 +2221,10 @@ button.wolai-page-ai-history-main span {
overflow: hidden;
}
.wolai-page-ai-drawer[data-page-ai-openhub-host="true"] .wolai-page-ai-panel {
display: flex;
flex-direction: column;
gap: 0;
padding: 0;
border: 0;
overflow: hidden;
}
.wolai-page-ai-drawer[data-page-ai-openhub-host="true"] .wolai-page-ai-resize-handle {
left: -12px;
}
.wolai-page-ai-opencode-header {
flex: 0 0 auto;
}
.wolai-page-ai-openhub-diagnostics {
flex: 0 0 auto;
max-height: 32px;
overflow: hidden;
border-bottom: 1px solid rgba(27, 28, 28, 0.08);
background: rgba(247, 247, 245, 0.78);
color: rgba(27, 28, 28, 0.58);
font-size: 12px;
}
.wolai-page-ai-openhub-diagnostics[hidden] {
display: none !important;
}
.wolai-page-ai-openhub-diagnostics summary {
min-height: 32px;
display: flex;
align-items: center;
padding: 0 12px;
cursor: pointer;
list-style: none;
}
.wolai-page-ai-openhub-diagnostics summary::-webkit-details-marker {
display: none;
}
.wolai-page-ai-openhub-diagnostics:not([open]) > :not(summary) {
display: none !important;
}
.wolai-page-ai-openhub-diagnostics[open] {
max-height: min(36vh, 220px);
overflow: auto;
padding-bottom: 10px;
}
.wolai-page-ai-openhub-diagnostics[open] summary {
margin-bottom: 6px;
}
.wolai-page-ai-openhub-diagnostics .wolai-page-ai-opencode-row,
.wolai-page-ai-openhub-diagnostics .wolai-page-ai-opencode-empty,
.wolai-page-ai-openhub-diagnostics .wolai-page-ai-opencode-badges {
margin-right: 12px;
margin-left: 12px;
}
.wolai-page-ai-opencode-chrome {
display: flex;
flex: 0 0 auto;