集成 OpenHub 与 WeKnora Page AI
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
use crate::app::AppState;
|
||||
use crate::context::RequestContext;
|
||||
use crate::error::WebError;
|
||||
use crate::provider_identity_sync::sync_provider_identities;
|
||||
use crate::routes::local_folder_source::{
|
||||
control_plane_db_path_display, create_default_local_workspace_for_actor,
|
||||
ensure_local_workspace_read_access_with_state, is_local_access_policy_admin_context,
|
||||
@@ -136,7 +137,7 @@ pub async fn auth_api(
|
||||
return Ok(build_sqlite_sign_out_response(&state, &context));
|
||||
}
|
||||
|
||||
handle_sqlite_auth_action(&state, &context, &payload)
|
||||
handle_sqlite_auth_action(&state, &context, &payload).await
|
||||
}
|
||||
|
||||
pub async fn auth_entry(
|
||||
@@ -2069,7 +2070,7 @@ fn has_real_auth_context(state: &AppState, context: &RequestContext) -> bool {
|
||||
|| extract_cookie_value(context, COOKIE_MNOTE_WEB_CONVEX_TOKEN).is_some()
|
||||
}
|
||||
|
||||
fn handle_sqlite_auth_action(
|
||||
async fn handle_sqlite_auth_action(
|
||||
state: &AppState,
|
||||
context: &RequestContext,
|
||||
payload: &serde_json::Value,
|
||||
@@ -2098,6 +2099,7 @@ fn handle_sqlite_auth_action(
|
||||
})?;
|
||||
let session_token = new_session_token();
|
||||
let token_hash = session_token_hash(&session_token);
|
||||
let password_for_provider_sync = password.clone();
|
||||
let resolved = if flow == "signUp" {
|
||||
let email = params
|
||||
.get("email")
|
||||
@@ -2200,14 +2202,46 @@ fn handle_sqlite_auth_action(
|
||||
.unwrap_or_else(|_| "{}".to_string()),
|
||||
});
|
||||
|
||||
Ok(build_sqlite_auth_response(
|
||||
let provider_sync_results = if flow == "signUp" {
|
||||
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(),
|
||||
target_kind: "user".to_string(),
|
||||
target_id: Some(resolved.user.id.clone()),
|
||||
metadata_json: serde_json::to_string(&json!({
|
||||
"providers": results.iter().map(|item| json!({
|
||||
"provider": item.provider,
|
||||
"ok": item.ok,
|
||||
"message": item.message,
|
||||
"providerUserId": item.provider_user_id,
|
||||
})).collect::<Vec<_>>(),
|
||||
}))
|
||||
.unwrap_or_else(|_| "{}".to_string()),
|
||||
});
|
||||
Some(results)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut response = build_sqlite_auth_response(
|
||||
context,
|
||||
&session_token,
|
||||
&resolved.user.id,
|
||||
resolved.user.email.as_deref().unwrap_or_default(),
|
||||
&resolved.user.display_name,
|
||||
&effective_sqlite_auth_actor_type(&resolved.user.id, &resolved.user.role),
|
||||
))
|
||||
);
|
||||
if let Some(results) = provider_sync_results {
|
||||
let all_ok = results.iter().all(|item| item.ok);
|
||||
let header_value = if all_ok { "ok" } else { "partial" };
|
||||
if let Ok(value) = HeaderValue::from_str(header_value) {
|
||||
response
|
||||
.headers_mut()
|
||||
.insert("x-mnote-provider-identity-sync", value);
|
||||
}
|
||||
}
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
fn sqlite_auth_error(
|
||||
|
||||
@@ -368,11 +368,21 @@ pub(crate) async fn execute_mnote_tool_call(
|
||||
Err(WebError::new(
|
||||
StatusCode::GONE,
|
||||
"mnote_evidence_tools_retired",
|
||||
"旧 docs/evidence/LiteParse tools 已退役;请使用 mnote.knowledge_rag.query/open_reference",
|
||||
"旧 docs/evidence/LiteParse tools 已退役;请使用 mnote.weknora.search/open_reference 或兼容 mnote.knowledge_rag.query/open_reference",
|
||||
)
|
||||
.with_context(&context))
|
||||
}
|
||||
"mnote.knowledge_rag.status" => knowledge_rag::status(&state, &context, &input).await,
|
||||
"mnote.weknora.search" => knowledge_rag::search(&state, &context, &input).await,
|
||||
"mnote.weknora.list_sources" => {
|
||||
knowledge_rag::list_sources(&state, &context, &input).await
|
||||
}
|
||||
"mnote.weknora.get_source_status" => {
|
||||
knowledge_rag::get_source_status(&state, &context, &input).await
|
||||
}
|
||||
"mnote.weknora.open_reference" => {
|
||||
knowledge_rag::open_reference(&state, &context, &input).await
|
||||
}
|
||||
"mnote.knowledge_rag.query" => knowledge_rag::query(&state, &context, &input).await,
|
||||
"mnote.knowledge_rag.section_context" => {
|
||||
knowledge_rag::section_context(&state, &context, &input).await
|
||||
@@ -384,7 +394,7 @@ pub(crate) async fn execute_mnote_tool_call(
|
||||
WebError::new(
|
||||
StatusCode::GONE,
|
||||
"mnote_index_tools_retired",
|
||||
"旧本地索引 tools 已退役;资料索引统一由 LightRAG provider 处理",
|
||||
"旧本地索引 tools 已退役;资料索引统一由当前知识库 provider 处理",
|
||||
)
|
||||
.with_context(&context),
|
||||
),
|
||||
@@ -728,6 +738,10 @@ fn is_read_tool(tool_name: &str) -> bool {
|
||||
| "mnote.evidence.read"
|
||||
| "mnote.evidence.open"
|
||||
| "mnote.knowledge_rag.status"
|
||||
| "mnote.weknora.search"
|
||||
| "mnote.weknora.list_sources"
|
||||
| "mnote.weknora.get_source_status"
|
||||
| "mnote.weknora.open_reference"
|
||||
| "mnote.knowledge_rag.query"
|
||||
| "mnote.knowledge_rag.section_context"
|
||||
| "mnote.knowledge_rag.open_reference"
|
||||
@@ -761,6 +775,10 @@ fn is_evidence_receipt_tool(tool_name: &str) -> bool {
|
||||
| "mnote.evidence.read"
|
||||
| "mnote.evidence.open"
|
||||
| "mnote.knowledge_rag.status"
|
||||
| "mnote.weknora.search"
|
||||
| "mnote.weknora.list_sources"
|
||||
| "mnote.weknora.get_source_status"
|
||||
| "mnote.weknora.open_reference"
|
||||
| "mnote.knowledge_rag.query"
|
||||
| "mnote.knowledge_rag.section_context"
|
||||
| "mnote.knowledge_rag.open_reference"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -26,6 +26,7 @@ mod onlyoffice;
|
||||
pub(crate) mod onlyoffice_bridge;
|
||||
mod page_ai_board;
|
||||
mod page_ai_opencode;
|
||||
mod page_ai_openhub;
|
||||
mod page_ai_workflow;
|
||||
mod query_support;
|
||||
mod resource_trash;
|
||||
@@ -91,6 +92,10 @@ pub fn build_router(state: AppState) -> Router {
|
||||
"/api/knowledge-rag/pipeline-events",
|
||||
get(knowledge_rag::pipeline_events),
|
||||
)
|
||||
.route(
|
||||
"/api/knowledge-rag/knowledge-bases",
|
||||
post(knowledge_rag::create_knowledge_base),
|
||||
)
|
||||
.route("/api/knowledge-rag/ingest", post(knowledge_rag::ingest))
|
||||
.route("/api/knowledge-rag/search", post(knowledge_rag::search))
|
||||
.route("/api/knowledge-rag/query", post(knowledge_rag::query_rag))
|
||||
@@ -389,6 +394,68 @@ 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),
|
||||
@@ -452,16 +519,31 @@ pub fn build_router(state: AppState) -> Router {
|
||||
.route("/page-ai/opencode/{*path}", any(page_ai_opencode::proxy))
|
||||
.route("/assets/{*path}", any(page_ai_opencode::proxy_assets))
|
||||
.route("/global/{*path}", any(page_ai_opencode::proxy_assets))
|
||||
.route("/favicon-96x96-v3.png", any(page_ai_opencode::proxy_current_path))
|
||||
.route(
|
||||
"/favicon-96x96-v3.png",
|
||||
any(page_ai_opencode::proxy_current_path),
|
||||
)
|
||||
.route("/favicon-v3.svg", any(page_ai_opencode::proxy_current_path))
|
||||
.route("/favicon-v3.ico", any(page_ai_opencode::proxy_current_path))
|
||||
.route("/apple-touch-icon-v3.png", any(page_ai_opencode::proxy_current_path))
|
||||
.route("/site.webmanifest", any(page_ai_opencode::proxy_current_path))
|
||||
.route("/social-share.png", any(page_ai_opencode::proxy_current_path))
|
||||
.route(
|
||||
"/apple-touch-icon-v3.png",
|
||||
any(page_ai_opencode::proxy_current_path),
|
||||
)
|
||||
.route(
|
||||
"/site.webmanifest",
|
||||
any(page_ai_opencode::proxy_current_path),
|
||||
)
|
||||
.route(
|
||||
"/social-share.png",
|
||||
any(page_ai_opencode::proxy_current_path),
|
||||
)
|
||||
.route("/provider", any(page_ai_opencode::proxy_current_path))
|
||||
.route("/path", any(page_ai_opencode::proxy_current_path))
|
||||
.route("/project", any(page_ai_opencode::proxy_current_path))
|
||||
.route("/project/{*path}", any(page_ai_opencode::proxy_current_path))
|
||||
.route(
|
||||
"/project/{*path}",
|
||||
any(page_ai_opencode::proxy_current_path),
|
||||
)
|
||||
.route("/lsp", any(page_ai_opencode::proxy_current_path))
|
||||
.route("/command", any(page_ai_opencode::proxy_current_path))
|
||||
.route("/mcp", any(page_ai_opencode::proxy_current_path))
|
||||
@@ -472,10 +554,19 @@ pub fn build_router(state: AppState) -> Router {
|
||||
.route("/question", any(page_ai_opencode::proxy_current_path))
|
||||
.route("/event", any(page_ai_opencode::proxy_current_path))
|
||||
.route("/session", any(page_ai_opencode::proxy_current_path))
|
||||
.route("/session/{*path}", any(page_ai_opencode::proxy_current_path))
|
||||
.route(
|
||||
"/session/{*path}",
|
||||
any(page_ai_opencode::proxy_current_path),
|
||||
)
|
||||
.route("/new-session", any(page_ai_opencode::proxy_current_path))
|
||||
.route("/{opencode_dir}/session", any(page_ai_opencode::proxy_current_path))
|
||||
.route("/{opencode_dir}/session/{*path}", any(page_ai_opencode::proxy_current_path))
|
||||
.route(
|
||||
"/{opencode_dir}/session",
|
||||
any(page_ai_opencode::proxy_current_path),
|
||||
)
|
||||
.route(
|
||||
"/{opencode_dir}/session/{*path}",
|
||||
any(page_ai_opencode::proxy_current_path),
|
||||
)
|
||||
.route("/api/page-ai/board/status", get(page_ai_board::status))
|
||||
.route("/api/page-ai/board/workers", get(page_ai_board::workers))
|
||||
.route(
|
||||
|
||||
@@ -142,7 +142,9 @@ fn file_uri_to_path(root_uri: &str) -> Option<String> {
|
||||
Some(percent_decode_path_lossy(&normalized))
|
||||
}
|
||||
|
||||
fn opencode_project_directory_for_request(request: Option<&OpencodeSessionRequest>) -> Option<String> {
|
||||
fn opencode_project_directory_for_request(
|
||||
request: Option<&OpencodeSessionRequest>,
|
||||
) -> Option<String> {
|
||||
if let Some(override_directory) = std::env::var("MNOTE_OPENCODE_PROJECT_DIR")
|
||||
.ok()
|
||||
.map(|value| value.trim().to_string())
|
||||
@@ -1025,6 +1027,23 @@ async fn proxy_to_opencode(
|
||||
body: Body,
|
||||
) -> Result<Response, WebError> {
|
||||
ensure_authenticated(&context)?;
|
||||
if std::env::var("MNOTE_PAGE_AI_OPENCODE_LEGACY_PROXY")
|
||||
.ok()
|
||||
.map(|value| value.trim() == "1")
|
||||
.unwrap_or(false)
|
||||
!= true
|
||||
{
|
||||
return Response::builder()
|
||||
.status(StatusCode::GONE)
|
||||
.header(header::CONTENT_TYPE, "text/plain; charset=utf-8")
|
||||
.body(Body::from("page_ai_opencode_legacy_proxy_disabled"))
|
||||
.map_err(|error| {
|
||||
WebError::internal(format!(
|
||||
"opencode legacy proxy disabled response 构造失败: {error}"
|
||||
))
|
||||
.with_context(&context)
|
||||
});
|
||||
}
|
||||
let client =
|
||||
opencode_client(Duration::from_secs(120)).map_err(|error| error.with_context(&context))?;
|
||||
let url = upstream_url(&path, uri.query()).map_err(|error| error.with_context(&context))?;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user