use crate::app::AppState; use crate::context::RequestContext; use crate::error::WebError; use crate::hermes_tools::{ensure_write_authorized, ToolCallInput}; use crate::routes; use axum::http::StatusCode; use serde::Deserialize; use serde_json::{json, Value}; #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] struct IndexSettingsArgs { #[serde(default)] include_paths: Vec, #[serde(default)] schedule_mode: Option, #[serde(default)] schedule_time: Option, #[serde(default)] schedule_date: Option, #[serde(default)] run_on_change: Option, } pub async fn index_status( state: &AppState, context: &RequestContext, input: &ToolCallInput, ) -> Result { let workspace_id = effective_workspace_id(context, input); let root_uri = required_root_uri( context, input, "mnote_index_root_required", "本地索引状态缺少 rootUri", )?; let root_path = routes::ensure_local_workspace_read_access_with_state(state, context, &root_uri) .map_err(|error| error.with_context(context))?; let user_settings = read_user_settings(state, context, &workspace_id, &root_path)?; let effective_settings = routes::effective_local_index_settings_for_root( state.control_plane(), &workspace_id, &root_path, )?; let status = routes::local_index_status_with_settings( &root_path, &root_uri, &workspace_id, &user_settings, &effective_settings, )?; Ok(json!({ "ok": true, "schema": "mnote.index.status_result.v1", "result": status })) } pub async fn index_refresh( state: &AppState, context: &RequestContext, input: &ToolCallInput, ) -> Result { let workspace_id = effective_workspace_id(context, input); let root_uri = required_root_uri( context, input, "mnote_index_root_required", "本地索引刷新缺少 rootUri", )?; let root_path = routes::ensure_local_workspace_read_access_with_state(state, context, &root_uri) .map_err(|error| error.with_context(context))?; let effective_settings = routes::effective_local_index_settings_for_root( state.control_plane(), &workspace_id, &root_path, )?; let refreshed = routes::refresh_local_search_index_with_settings( &root_path, &root_uri, &workspace_id, &effective_settings, )?; Ok(json!({ "ok": true, "schema": "mnote.index.refresh_result.v1", "index": refreshed })) } pub async fn index_update_settings( state: &AppState, context: &RequestContext, input: &ToolCallInput, ) -> Result { ensure_write_authorized(context, input)?; let workspace_id = effective_workspace_id(context, input); let root_uri = required_root_uri( context, input, "mnote_index_root_required", "本地索引设置缺少 rootUri", )?; let args = parse_settings_args(context, input)?; let root_path = routes::ensure_local_workspace_write_access_with_state(state, context, &root_uri) .map_err(|error| error.with_context(context))?; let actor_id = routes::current_actor_id(state, context).ok_or_else(|| { WebError::new( StatusCode::UNAUTHORIZED, "local_index_settings_auth_required", "本地索引设置需要登录用户", ) .with_context(context) })?; if input.dry_run == Some(true) { let planned_settings = routes::preview_user_local_index_settings( state.control_plane(), &actor_id, &workspace_id, &root_path, &args.include_paths, args.schedule_mode.as_deref(), args.schedule_time.as_deref(), args.schedule_date.as_deref(), args.run_on_change, )?; let current_status = index_status(state, context, input).await?; let would_clear_index_files = planned_settings.include_paths.is_empty(); return Ok(json!({ "ok": true, "schema": "mnote.index.update_settings_plan.v1", "dryRun": true, "plannedSettings": planned_settings, "currentStatus": current_status.get("result").cloned().unwrap_or(Value::Null), "wouldRefresh": true, "wouldClearIndexFiles": would_clear_index_files })); } let settings = routes::write_user_local_index_settings( state.control_plane(), &actor_id, &workspace_id, &root_path, &args.include_paths, args.schedule_mode.as_deref(), args.schedule_time.as_deref(), args.schedule_date.as_deref(), args.run_on_change, )?; let effective_settings = routes::effective_local_index_settings_for_root( state.control_plane(), &workspace_id, &root_path, )?; let refreshed = routes::refresh_local_search_index_with_settings( &root_path, &root_uri, &workspace_id, &effective_settings, )?; let status = routes::local_index_status_with_settings( &root_path, &root_uri, &workspace_id, &settings, &effective_settings, )?; Ok(json!({ "ok": true, "schema": "mnote.index.update_settings_result.v1", "settings": settings, "index": refreshed, "result": status })) } fn parse_settings_args( context: &RequestContext, input: &ToolCallInput, ) -> Result { let args = input.args.clone().unwrap_or_else(|| json!({})); serde_json::from_value::(args).map_err(|error| { WebError::bad_request_code( "mnote_index_settings_payload_invalid", format!("本地索引设置参数无效: {error}"), ) .with_context(context) }) } fn read_user_settings( state: &AppState, context: &RequestContext, workspace_id: &str, root_path: &std::path::Path, ) -> Result { if let Some(actor_id) = routes::current_actor_id(state, context) { return routes::read_user_local_index_settings( state.control_plane(), &actor_id, workspace_id, root_path, ); } routes::read_local_index_settings_or_default(root_path) } fn effective_workspace_id(context: &RequestContext, input: &ToolCallInput) -> String { input .effective_workspace_id() .or_else(|| context.workspace.workspace_id.clone()) .unwrap_or_else(|| "default".into()) } fn required_root_uri( context: &RequestContext, input: &ToolCallInput, code: &'static str, message: &'static str, ) -> Result { input .effective_root_uri() .map(|value| value.trim().to_string()) .filter(|value| !value.is_empty()) .ok_or_else(|| WebError::bad_request_code(code, message).with_context(context)) }