feat: complete page ai hermes control checklist
This commit is contained in:
@@ -9,6 +9,7 @@ use futures_util::TryStreamExt;
|
||||
use serde::Deserialize;
|
||||
use serde_json::{json, Value};
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::process::Command;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -387,8 +388,50 @@ pub async fn list_tools(
|
||||
{
|
||||
"name": "mnote.page.get",
|
||||
"scope": "page.read",
|
||||
"kind": "read",
|
||||
"schemaVersion": "mnote.hermes_tool.v1",
|
||||
"status": "planned_by_task_e"
|
||||
"status": "available",
|
||||
"description": "读取当前页面正文、标题、设置与结构快照"
|
||||
},
|
||||
{
|
||||
"name": "mnote.page.save",
|
||||
"scope": "page.write",
|
||||
"kind": "write",
|
||||
"schemaVersion": "mnote.hermes_tool.v1",
|
||||
"status": "available",
|
||||
"description": "保存当前页面正文"
|
||||
},
|
||||
{
|
||||
"name": "mnote.page.update_title",
|
||||
"scope": "page.write",
|
||||
"kind": "write",
|
||||
"schemaVersion": "mnote.hermes_tool.v1",
|
||||
"status": "available",
|
||||
"description": "更新当前页面标题"
|
||||
},
|
||||
{
|
||||
"name": "mnote.page.update_options",
|
||||
"scope": "page.write",
|
||||
"kind": "write",
|
||||
"schemaVersion": "mnote.hermes_tool.v1",
|
||||
"status": "available",
|
||||
"description": "更新当前页面设置"
|
||||
},
|
||||
{
|
||||
"name": "mnote.artifact.create_summary",
|
||||
"scope": "artifact.write",
|
||||
"kind": "write",
|
||||
"schemaVersion": "mnote.hermes_tool.v1",
|
||||
"status": "available",
|
||||
"description": "为当前页面创建或更新 AI Summary"
|
||||
},
|
||||
{
|
||||
"name": "mnote.artifact.create_ai_note",
|
||||
"scope": "artifact.write",
|
||||
"kind": "write",
|
||||
"schemaVersion": "mnote.hermes_tool.v1",
|
||||
"status": "available",
|
||||
"description": "基于当前页面创建新的 AI Note"
|
||||
}
|
||||
]
|
||||
})),
|
||||
@@ -411,20 +454,61 @@ fn ensure_authenticated(context: &RequestContext) -> Result<(), WebError> {
|
||||
}
|
||||
|
||||
fn configured_upstream() -> Option<String> {
|
||||
std::env::var("MNOTE_WEB_HERMES_UPSTREAM_URL")
|
||||
.ok()
|
||||
.or_else(|| std::env::var("MNOTE_HERMES_UPSTREAM_URL").ok())
|
||||
.map(|value| value.trim().trim_end_matches('/').to_string())
|
||||
.filter(|value| !value.is_empty())
|
||||
[
|
||||
"MNOTE_WEB_HERMES_UPSTREAM_URL",
|
||||
"MNOTE_HERMES_UPSTREAM_URL",
|
||||
"MNOTE_HERMES_API_BASE_URL",
|
||||
]
|
||||
.into_iter()
|
||||
.find_map(env_or_dotenv)
|
||||
.map(|value| value.trim().trim_end_matches('/').to_string())
|
||||
.filter(|value| !value.is_empty())
|
||||
}
|
||||
|
||||
fn configured_api_key() -> Option<String> {
|
||||
std::env::var("MNOTE_WEB_HERMES_API_KEY")
|
||||
.ok()
|
||||
.or_else(|| std::env::var("HERMES_API_SERVER_KEY").ok())
|
||||
.or_else(|| std::env::var("API_SERVER_KEY").ok())
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty())
|
||||
[
|
||||
"MNOTE_WEB_HERMES_API_KEY",
|
||||
"MNOTE_HERMES_API_KEY",
|
||||
"HERMES_API_SERVER_KEY",
|
||||
"API_SERVER_KEY",
|
||||
]
|
||||
.into_iter()
|
||||
.find_map(env_or_dotenv)
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty())
|
||||
}
|
||||
|
||||
fn env_or_dotenv(key: &str) -> Option<String> {
|
||||
if let Ok(value) = std::env::var(key) {
|
||||
let trimmed = value.trim().trim_matches('"').to_string();
|
||||
if !trimmed.is_empty() {
|
||||
return Some(trimmed);
|
||||
}
|
||||
}
|
||||
if cfg!(test) {
|
||||
return None;
|
||||
}
|
||||
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../..")
|
||||
.join(".env.all");
|
||||
let content = fs::read_to_string(root).ok()?;
|
||||
for line in content.lines() {
|
||||
let line = line.trim_end_matches('\r');
|
||||
if line.starts_with('#') || line.trim().is_empty() {
|
||||
continue;
|
||||
}
|
||||
let Some((candidate_key, value)) = line.split_once('=') else {
|
||||
continue;
|
||||
};
|
||||
if candidate_key.trim() != key {
|
||||
continue;
|
||||
}
|
||||
let trimmed = value.trim().trim_matches('"').to_string();
|
||||
if !trimmed.is_empty() {
|
||||
return Some(trimmed);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn build_run_upstream_body(context: &RequestContext, payload: Value) -> Result<Value, WebError> {
|
||||
|
||||
@@ -33,6 +33,7 @@ use bridge_runtime::RuntimeQueryEnvelopeWire;
|
||||
use core_protocol::KernelProjectionKind;
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use std::fs;
|
||||
use std::path::{Component, Path as FsPath, PathBuf};
|
||||
|
||||
const HEADER_MNOTE_WEB_OWNER: &str = "x-mnote-web-owner";
|
||||
@@ -195,6 +196,7 @@ pub async fn document_page_shell(
|
||||
secondary_page_options_json={secondary_page_options_json.unwrap_or_default()}
|
||||
/>
|
||||
});
|
||||
let hermes_settings_config_script = render_hermes_settings_config_script();
|
||||
let html = format!(
|
||||
r#"<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
@@ -212,6 +214,7 @@ pub async fn document_page_shell(
|
||||
{}
|
||||
{}
|
||||
{}
|
||||
{}
|
||||
</body>
|
||||
</html>"#,
|
||||
escape_html(title),
|
||||
@@ -223,6 +226,7 @@ pub async fn document_page_shell(
|
||||
escape_script_json(&snapshot_json),
|
||||
escape_script_json(&bootstrap_json),
|
||||
escape_script_json(&panes_bootstrap_json),
|
||||
hermes_settings_config_script,
|
||||
secondary_snapshot_json
|
||||
.as_ref()
|
||||
.map(|value| format!(r#"<script id="__MNOTE_SECONDARY_PAGE_AGGREGATE__" type="application/json">{}</script>"#, escape_script_json(value)))
|
||||
@@ -256,6 +260,60 @@ pub(crate) fn build_editor_bootstrap_json(
|
||||
)
|
||||
}
|
||||
|
||||
fn render_hermes_settings_config_script() -> String {
|
||||
let Some(base_url) = [
|
||||
"MNOTE_WEB_HERMES_UPSTREAM_URL",
|
||||
"MNOTE_HERMES_UPSTREAM_URL",
|
||||
"MNOTE_HERMES_API_BASE_URL",
|
||||
]
|
||||
.into_iter()
|
||||
.find_map(env_or_dotenv)
|
||||
.map(|value| value.trim().trim_end_matches('/').to_string())
|
||||
.filter(|value| !value.is_empty())
|
||||
else {
|
||||
return String::new();
|
||||
};
|
||||
let settings_url = format!("{base_url}/hermes/settings");
|
||||
let encoded = serde_json::to_string(&settings_url).unwrap_or_else(|_| "\"\"".to_string());
|
||||
format!(
|
||||
r#"<script>window.__mnoteHermesSettingsUrl = {};</script>"#,
|
||||
escape_script_json(&encoded)
|
||||
)
|
||||
}
|
||||
|
||||
fn env_or_dotenv(key: &str) -> Option<String> {
|
||||
if let Ok(value) = std::env::var(key) {
|
||||
let trimmed = value.trim().trim_matches('"').to_string();
|
||||
if !trimmed.is_empty() {
|
||||
return Some(trimmed);
|
||||
}
|
||||
}
|
||||
if cfg!(test) {
|
||||
return None;
|
||||
}
|
||||
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../..")
|
||||
.join(".env.all");
|
||||
let content = fs::read_to_string(root).ok()?;
|
||||
for line in content.lines() {
|
||||
let line = line.trim_end_matches('\r');
|
||||
if line.starts_with('#') || line.trim().is_empty() {
|
||||
continue;
|
||||
}
|
||||
let Some((candidate_key, value)) = line.split_once('=') else {
|
||||
continue;
|
||||
};
|
||||
if candidate_key.trim() != key {
|
||||
continue;
|
||||
}
|
||||
let trimmed = value.trim().trim_matches('"').to_string();
|
||||
if !trimmed.is_empty() {
|
||||
return Some(trimmed);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn build_editor_bootstrap_json_with_ids(
|
||||
aggregate: &PageAggregate,
|
||||
context: &RequestContext,
|
||||
|
||||
Reference in New Issue
Block a user