hololake-system-architecture/product-source/hololake-clean-desktop/src-tauri/src/lib.rs

624 lines
21 KiB
Rust
Raw Normal View History

mod agent_executor;
mod enterprise_entrance;
mod model;
mod persona_runtime;
mod realtime_bridge;
mod storage;
use model::*;
use serde::Serialize;
use std::{fs, path::Path};
use tauri::{AppHandle, Manager, State};
use tauri_plugin_dialog::DialogExt;
const UPDATE_ENDPOINT: &str = "https://guanghulab.com/hololake/releases/latest.json";
const PUBLIC_STATE: &str = "SIGNED_UPDATE_FEED_READY_NO_RELEASE";
fn bundled_modules() -> Result<Vec<ModuleRecord>, String> {
let registry: serde_json::Value =
serde_json::from_str(include_str!("../../registries/module-registry.json"))
.map_err(|error| format!("MODULE_REGISTRY_INVALID: {error}"))?;
serde_json::from_value(registry["modules"].clone())
.map_err(|error| format!("MODULE_REGISTRY_INVALID: {error}"))
}
fn receipt(
app: &AppHandle,
state: &str,
message: &str,
git: Option<String>,
event: TimelineEvent,
) -> Result<MutationReceipt, String> {
storage::append_event(app, &event)?;
Ok(MutationReceipt {
receipt_id: storage::id("HL-RCP"),
state: state.into(),
message: message.into(),
git_commit: git,
event,
})
}
#[tauri::command]
fn system_snapshot(app: AppHandle) -> Result<SystemSnapshot, String> {
Ok(SystemSnapshot {
app_version: env!("CARGO_PKG_VERSION").into(),
channel: storage::channel(&app)?,
timeline: storage::events(&app)?,
documents: storage::list_docs(&app)?,
module_registry_version: "1".into(),
public_update_endpoint: UPDATE_ENDPOINT.into(),
public_distribution_state: PUBLIC_STATE.into(),
external_ai_bridges: storage::bridges(&app)?,
modules: bundled_modules()?,
})
}
#[tauri::command]
fn create_channel(app: AppHandle, name: String) -> Result<Channel, String> {
let name = name.trim();
if name.is_empty() || name.chars().count() > 48 {
return Err("CHANNEL_NAME_INVALID".into());
}
if storage::channel(&app)?.is_some() {
return Err("CHANNEL_ALREADY_EXISTS".into());
}
let repo = storage::private_repo_root(&app)?;
let c = Channel {
channel_id: format!(
"HL-CH-{}",
uuid::Uuid::new_v4().simple().to_string()[..8].to_uppercase()
),
name: name.into(),
created_at: storage::now(),
private_git_path: repo.to_string_lossy().into_owned(),
public_distribution_state: PUBLIC_STATE.into(),
};
storage::save_channel(&app, &c)?;
let e = storage::event(
SourceKind::SystemReceipt,
"频道已建立",
format!("{} 已登记;私人 Git 已初始化。", c.channel_id),
"SUCCEEDED",
);
storage::append_event(&app, &e)?;
storage::git_commit(
&repo,
"chore(channel): initialize personal HoloLake channel",
)?;
Ok(c)
}
#[tauri::command]
fn submit_user_message(
app: AppHandle,
realtime: State<'_, realtime_bridge::RealtimeBridgeState>,
content: String,
) -> Result<MutationReceipt, String> {
let content = content.trim();
if content.is_empty() {
return Err("EMPTY_USER_MESSAGE".into());
}
if storage::channel(&app)?.is_none() {
return Err("CHANNEL_NOT_INITIALIZED".into());
}
let e = storage::event(SourceKind::UserMessage, "用户语言", content, "RECORDED");
storage::append_event(&app, &e)?;
realtime.broadcast_value(serde_json::json!({"type":"user_message","event":e}));
let waiting = storage::event(
SourceKind::SystemReceipt,
"系统回执",
"用户语言已按 USER_MESSAGE 保存并实时发送到已连接的外部 AI系统不伪造人格回应。",
"WAITING",
);
receipt(
&app,
"WAITING_PERSONA",
"用户语言已记录并投递到实时桥。",
None,
waiting,
)
}
fn clean_title(title: &str) -> Result<String, String> {
let t = title.trim();
if t.is_empty() || t.chars().count() > 120 {
return Err("KNOWLEDGE_TITLE_INVALID".into());
}
Ok(t.chars()
.map(|c| if "\\/:*?\"<>|".contains(c) { '-' } else { c })
.collect())
}
#[tauri::command]
fn read_knowledge_document(app: AppHandle, path: String) -> Result<KnowledgeDocument, String> {
storage::read_doc(&app, &path)
}
#[tauri::command]
fn create_knowledge_document(app: AppHandle, title: String) -> Result<MutationReceipt, String> {
let title = clean_title(&title)?;
let root = storage::knowledge_root(&app)?;
let rel = format!("{}.md", title);
let p = root.join("docs").join(&rel);
if p.exists() {
return Err("KNOWLEDGE_DOCUMENT_EXISTS".into());
}
fs::write(&p, format!("# {}\n\n", title)).map_err(|e| e.to_string())?;
let git = storage::git_commit(&root, "feat(knowledge): create document")?;
let e = storage::event(
SourceKind::AgentAction,
"Agent 执行",
format!("已创建知识页 {rel}"),
"SUCCEEDED",
);
receipt(&app, "SUCCEEDED", "知识页已创建。", git, e)
}
#[tauri::command]
fn save_knowledge_document(
app: AppHandle,
path: String,
body: String,
) -> Result<MutationReceipt, String> {
let rel = storage::safe_rel(&path)?;
if rel.extension().and_then(|x| x.to_str()) != Some("md") {
return Err("KNOWLEDGE_DOCUMENT_TYPE_REJECTED".into());
}
let root = storage::knowledge_root(&app)?;
let p = root.join("docs").join(rel);
if !p.exists() {
return Err("KNOWLEDGE_DOCUMENT_NOT_FOUND".into());
}
fs::write(&p, body).map_err(|e| e.to_string())?;
let git = storage::git_commit(&root, "docs(knowledge): save document")?;
let e = storage::event(
SourceKind::SystemReceipt,
"系统回执",
format!("{path} 已写入私人 Git。"),
"SUCCEEDED",
);
receipt(&app, "SUCCEEDED", "保存完成。", git, e)
}
#[tauri::command]
fn delete_knowledge_document(app: AppHandle, path: String) -> Result<MutationReceipt, String> {
let rel = storage::safe_rel(&path)?;
let root = storage::knowledge_root(&app)?;
let from = root.join("docs").join(&rel);
if !from.exists() {
return Err("KNOWLEDGE_DOCUMENT_NOT_FOUND".into());
}
let trash = root.join(".trash").join(format!(
"{}-{}",
storage::id("deleted"),
rel.file_name()
.and_then(|x| x.to_str())
.unwrap_or("document.md")
));
fs::create_dir_all(trash.parent().unwrap()).map_err(|e| e.to_string())?;
fs::rename(from, &trash).map_err(|e| e.to_string())?;
let git = storage::git_commit(&root, "chore(knowledge): move document to trash")?;
let e = storage::event(
SourceKind::SystemReceipt,
"系统回执",
format!("{path} 已移入可恢复回收站。"),
"SUCCEEDED",
);
receipt(&app, "SUCCEEDED", "已移入回收站。", git, e)
}
fn copy_import(src: &Path, dst: &Path, count: &mut usize) -> Result<(), String> {
if *count >= 1000 {
return Err("IMPORT_FILE_LIMIT_EXCEEDED".into());
}
for entry in fs::read_dir(src).map_err(|e| e.to_string())? {
let entry = entry.map_err(|e| e.to_string())?;
let p = entry.path();
if p.file_name()
.and_then(|x| x.to_str())
.map(|x| x.starts_with('.'))
.unwrap_or(false)
{
continue;
}
if p.is_dir() {
let folder_name = clean_title(
p.file_name()
.and_then(|value| value.to_str())
.unwrap_or("未命名分类"),
)?;
let child = dst.join(folder_name);
fs::create_dir_all(&child).map_err(|e| e.to_string())?;
copy_import(&p, &child, count)?;
continue;
}
let ext = p
.extension()
.and_then(|x| x.to_str())
.unwrap_or("")
.to_lowercase();
if !["md", "txt", "markdown"].contains(&ext.as_str()) {
continue;
}
let bytes = fs::read(&p).map_err(|e| e.to_string())?;
if bytes.len() > 10 * 1024 * 1024 {
continue;
}
let body = String::from_utf8_lossy(&bytes);
let title = p.file_stem().and_then(|x| x.to_str()).unwrap_or("导入页面");
let name = format!("{}-{}.md", clean_title(title)?, &storage::sha(&bytes)[..8]);
fs::write(
dst.join(name),
if ext == "md" || ext == "markdown" {
body.into_owned()
} else {
format!("# {}\n\n{}", title, body)
},
)
.map_err(|e| e.to_string())?;
*count += 1
}
Ok(())
}
#[tauri::command]
async fn import_knowledge_folder(app: AppHandle) -> Result<Option<MutationReceipt>, String> {
let Some(folder) = app
.dialog()
.file()
.set_title("导入到光湖知识库")
.blocking_pick_folder()
else {
return Ok(None);
};
let src = folder.into_path().map_err(|e| e.to_string())?;
let root = storage::knowledge_root(&app)?;
let source_name = clean_title(
src.file_name()
.and_then(|value| value.to_str())
.unwrap_or("外部知识库"),
)?;
let dst = root.join("docs").join("导入").join(source_name);
fs::create_dir_all(&dst).map_err(|e| e.to_string())?;
let mut count = 0;
copy_import(&src, &dst, &mut count)?;
let git = storage::git_commit(&root, "feat(knowledge): import external folder")?;
let protocol = storage::event(
SourceKind::ProtocolEvent,
"协议运行",
format!("导入边界校验完成;接受 Markdown/纯文本,共 {count} 页。原目录未改写。"),
"SUCCEEDED",
);
storage::append_event(&app, &protocol)?;
let e = storage::event(
SourceKind::SystemReceipt,
"系统回执",
format!("外部文件夹已转换为 {count} 个光湖知识页。"),
"SUCCEEDED",
);
Ok(Some(receipt(
&app,
"SUCCEEDED",
"文件夹导入完成。",
git,
e,
)?))
}
#[tauri::command]
async fn export_knowledge_document(app: AppHandle, path: String) -> Result<Option<String>, String> {
let doc = storage::read_doc(&app, &path)?;
let Some(target) = app
.dialog()
.file()
.set_title("下载知识页")
.set_file_name(&path)
.blocking_save_file()
else {
return Ok(None);
};
let p = target.into_path().map_err(|e| e.to_string())?;
fs::write(&p, doc.body).map_err(|e| e.to_string())?;
let e = storage::event(
SourceKind::ToolResult,
"工具结果",
format!("知识页已下载到 {}", p.display()),
"SUCCEEDED",
);
storage::append_event(&app, &e)?;
Ok(Some(p.to_string_lossy().into_owned()))
}
#[tauri::command]
fn register_external_ai_bridge(
app: AppHandle,
display_name: String,
) -> Result<ExternalAiBridge, String> {
let name = display_name.trim();
if name.is_empty() || name.chars().count() > 64 {
return Err("EXTERNAL_AI_NAME_INVALID".into());
}
let bridge_id = format!(
"HL-AI-{}",
uuid::Uuid::new_v4().simple().to_string()[..8].to_uppercase()
);
let root = storage::root(&app)?.join("external-ai").join(&bridge_id);
let inbox = root.join("inbox");
let outbox = root.join("outbox");
fs::create_dir_all(&inbox).map_err(|e| e.to_string())?;
fs::create_dir_all(&outbox).map_err(|e| e.to_string())?;
let bridge = ExternalAiBridge {
bridge_id,
display_name: name.into(),
inbox_path: inbox.to_string_lossy().into_owned(),
outbox_path: outbox.to_string_lossy().into_owned(),
state: "GLP_REALTIME_REGISTERED_NO_EXECUTION_AUTHORITY".into(),
};
let mut all = storage::bridges(&app)?;
all.push(bridge.clone());
storage::save_bridges(&app, &all)?;
let e = storage::event(
SourceKind::SystemReceipt,
"外部 AI 入口已建立",
format!(
"{} 仅获得来源明确的语言收发目录,不获得人格与执行权限。",
bridge.bridge_id
),
"SUCCEEDED",
);
storage::append_event(&app, &e)?;
Ok(bridge)
}
fn ensure_default_external_ai_bridge(app: &AppHandle) -> Result<(), String> {
let mut bridges = storage::bridges(app)?;
if bridges
.iter()
.any(|bridge| bridge.bridge_id == "HLP-BRIDGE-LOCAL-DEFAULT")
{
return Ok(());
}
let root = storage::root(app)?
.join("external-ai")
.join("HLP-BRIDGE-LOCAL-DEFAULT");
let inbox = root.join("inbox");
let outbox = root.join("outbox");
fs::create_dir_all(&inbox).map_err(|e| e.to_string())?;
fs::create_dir_all(&outbox).map_err(|e| e.to_string())?;
bridges.push(ExternalAiBridge {
bridge_id: "HLP-BRIDGE-LOCAL-DEFAULT".into(),
display_name: "本机编程 AI".into(),
inbox_path: inbox.to_string_lossy().into_owned(),
outbox_path: outbox.to_string_lossy().into_owned(),
state: "GLP_REALTIME_REGISTERED_NO_EXECUTION_AUTHORITY".into(),
});
storage::save_bridges(app, &bridges)
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct RuntimeOverview {
realtime: realtime_bridge::RealtimeBridgeStatus,
persona: persona_runtime::PersonaRuntimeSnapshot,
proposals: Vec<agent_executor::AgentProposal>,
}
#[tauri::command]
fn get_runtime_overview(
app: AppHandle,
realtime: State<'_, realtime_bridge::RealtimeBridgeState>,
) -> Result<RuntimeOverview, String> {
Ok(RuntimeOverview {
realtime: realtime.status(),
persona: persona_runtime::snapshot(&app)?,
proposals: agent_executor::list(&app)?,
})
}
#[tauri::command]
fn get_realtime_invitation(
app: AppHandle,
realtime: State<'_, realtime_bridge::RealtimeBridgeState>,
) -> Result<realtime_bridge::RealtimeInvitation, String> {
realtime_bridge::invitation(&app, &realtime)
}
#[tauri::command]
fn register_public_persona(
app: AppHandle,
display_name: String,
) -> Result<persona_runtime::PublicPersona, String> {
persona_runtime::register(&app, display_name)
}
#[tauri::command]
fn delete_trial_persona(
app: AppHandle,
persona_id: String,
exact_confirmation: String,
) -> Result<persona_runtime::PersonaRuntimeSnapshot, String> {
persona_runtime::delete_trial(&app, &persona_id, &exact_confirmation)
}
#[tauri::command]
fn compile_tcs_agent_proposal(
app: AppHandle,
request: agent_executor::TcsCompileRequest,
) -> Result<agent_executor::AgentProposal, String> {
agent_executor::queue(&app, request, "HOLOLAKE_LOCAL_UI".into(), None)
}
#[tauri::command]
fn approve_agent_proposal(
app: AppHandle,
realtime: State<'_, realtime_bridge::RealtimeBridgeState>,
proposal_id: String,
) -> Result<agent_executor::AgentReceipt, String> {
let receipt = agent_executor::approve(&app, &proposal_id)?;
realtime.broadcast_value(serde_json::json!({"type":"agent_receipt","receipt":receipt}));
Ok(receipt)
}
#[tauri::command]
fn reject_agent_proposal(
app: AppHandle,
realtime: State<'_, realtime_bridge::RealtimeBridgeState>,
proposal_id: String,
) -> Result<agent_executor::AgentProposal, String> {
let proposal = agent_executor::reject(&app, &proposal_id)?;
realtime
.broadcast_value(serde_json::json!({"type":"proposal_rejected","proposalId":proposal_id}));
Ok(proposal)
}
#[tauri::command]
fn enterprise_entrance_snapshot(
app: AppHandle,
) -> Result<enterprise_entrance::EnterpriseEntranceSnapshot, String> {
enterprise_entrance::snapshot(&app)
}
#[tauri::command]
fn prepare_enterprise_device(
app: AppHandle,
) -> Result<enterprise_entrance::EnterpriseDeviceProof, String> {
enterprise_entrance::prepare_device(&app)
}
#[tauri::command]
fn prepare_enterprise_binding(
app: AppHandle,
domain_id: String,
responsible_human_id: String,
persona_id: String,
) -> Result<enterprise_entrance::EnterpriseBindingRequest, String> {
enterprise_entrance::prepare_binding(&app, &domain_id, &responsible_human_id, &persona_id)
}
#[tauri::command]
fn sign_enterprise_challenge(
app: AppHandle,
request_id: String,
challenge_id: String,
nonce: String,
expires_unix_ms: u128,
) -> Result<enterprise_entrance::EnterpriseChallengeProof, String> {
enterprise_entrance::sign_challenge(&app, &request_id, &challenge_id, &nonce, expires_unix_ms)
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_process::init())
.plugin(tauri_plugin_updater::Builder::new().build())
.setup(|app| {
let diagnostic = storage::root(app.handle())
.map_err(std::io::Error::other)?
.join("startup-runtime-diagnostic.json");
storage::write_json(
&diagnostic,
&serde_json::json!({"state":"SETUP_ENTERED","recordedAt":storage::now()}),
)
.map_err(std::io::Error::other)?;
if std::env::var_os("HOLOLAKE_RUNTIME_TEST_ROOT").is_some() {
if let Ok(channel_name) = std::env::var("HOLOLAKE_TEST_CHANNEL_NAME") {
create_channel(app.handle().clone(), channel_name)
.map_err(std::io::Error::other)?;
}
if let Ok(display_name) = std::env::var("HOLOLAKE_TEST_PERSONA_NAME") {
persona_runtime::register(app.handle(), display_name)
.map_err(std::io::Error::other)?;
}
}
ensure_default_external_ai_bridge(app.handle()).map_err(std::io::Error::other)?;
let state = match realtime_bridge::start(app.handle().clone()) {
Ok(state) => state,
Err(error) => {
let _ = storage::write_json(
&diagnostic,
&serde_json::json!({"state":"LISTENER_FAILED","error":error.clone(),"recordedAt":storage::now()}),
);
return Err(std::io::Error::other(error).into());
}
};
storage::write_json(
&diagnostic,
&serde_json::json!({"state":"LISTENER_BOUND","status":state.status(),"recordedAt":storage::now()}),
)
.map_err(std::io::Error::other)?;
let invitation =
realtime_bridge::invitation(app.handle(), &state).map_err(std::io::Error::other)?;
storage::write_json(
&storage::root(app.handle())
.map_err(std::io::Error::other)?
.join("startup-runtime-receipt.json"),
&serde_json::json!({
"schema":"hololake.startup-runtime-receipt/v1",
"state":"GLP_LISTENING",
"protocol":invitation.protocol,
"endpoint":invitation.endpoint,
"recordedAt":storage::now()
}),
)
.map_err(std::io::Error::other)?;
app.manage(state);
Ok(())
})
.invoke_handler(tauri::generate_handler![
system_snapshot,
create_channel,
submit_user_message,
read_knowledge_document,
create_knowledge_document,
save_knowledge_document,
delete_knowledge_document,
import_knowledge_folder,
export_knowledge_document,
register_external_ai_bridge,
get_runtime_overview,
get_realtime_invitation,
register_public_persona,
delete_trial_persona,
compile_tcs_agent_proposal,
approve_agent_proposal,
reject_agent_proposal
,enterprise_entrance_snapshot
,prepare_enterprise_device
,prepare_enterprise_binding
,sign_enterprise_challenge
])
.run(tauri::generate_context!())
.expect("HoloLake runtime failed")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn title_rejects_empty() {
assert!(clean_title(" ").is_err())
}
#[test]
fn title_sanitizes_paths() {
assert_eq!(clean_title("a/b").unwrap(), "a-b")
}
#[test]
fn folder_import_preserves_classification_tree() {
let source = tempfile::tempdir().unwrap();
let destination = tempfile::tempdir().unwrap();
let category = source.path().join("产品架构");
fs::create_dir_all(&category).unwrap();
fs::write(category.join("决定.txt"), "分类内容").unwrap();
let mut count = 0;
copy_import(source.path(), destination.path(), &mut count).unwrap();
assert_eq!(count, 1);
let imported = fs::read_dir(destination.path().join("产品架构"))
.unwrap()
.collect::<Result<Vec<_>, _>>()
.unwrap();
assert_eq!(imported.len(), 1);
assert_eq!(
imported[0]
.path()
.extension()
.and_then(|value| value.to_str()),
Some("md")
);
}
}