Files
mnote/rust/crates/mnote-web/src/hermes_tools/index.rs
T

230 lines
7.0 KiB
Rust
Raw Normal View History

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<String>,
#[serde(default)]
schedule_mode: Option<String>,
#[serde(default)]
schedule_time: Option<String>,
#[serde(default)]
schedule_date: Option<String>,
#[serde(default)]
run_on_change: Option<bool>,
}
pub async fn index_status(
state: &AppState,
context: &RequestContext,
input: &ToolCallInput,
) -> Result<Value, WebError> {
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<Value, WebError> {
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<Value, WebError> {
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<IndexSettingsArgs, WebError> {
let args = input.args.clone().unwrap_or_else(|| json!({}));
serde_json::from_value::<IndexSettingsArgs>(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<routes::LocalIndexSettings, WebError> {
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<String, WebError> {
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))
}