feat(hololake): complete public runtime loop
This commit is contained in:
parent
71cece74b0
commit
92cce27973
26 changed files with 2626 additions and 79 deletions
|
|
@ -1,9 +1,13 @@
|
|||
mod agent_executor;
|
||||
mod model;
|
||||
mod persona_runtime;
|
||||
mod realtime_bridge;
|
||||
mod storage;
|
||||
|
||||
use model::*;
|
||||
use serde::Serialize;
|
||||
use std::{fs, path::Path};
|
||||
use tauri::AppHandle;
|
||||
use tauri::{AppHandle, Manager, State};
|
||||
use tauri_plugin_dialog::DialogExt;
|
||||
|
||||
const UPDATE_ENDPOINT: &str = "https://guanghulab.com/hololake/releases/latest.json";
|
||||
|
|
@ -76,7 +80,11 @@ fn create_channel(app: AppHandle, name: String) -> Result<Channel, String> {
|
|||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn submit_user_message(app: AppHandle, content: String) -> Result<MutationReceipt, String> {
|
||||
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());
|
||||
|
|
@ -86,16 +94,17 @@ fn submit_user_message(app: AppHandle, content: String) -> Result<MutationReceip
|
|||
}
|
||||
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 保存;当前未绑定人格模型,不伪造人格回应。",
|
||||
"用户语言已按 USER_MESSAGE 保存并实时发送到已连接的外部 AI;系统不伪造人格回应。",
|
||||
"WAITING",
|
||||
);
|
||||
receipt(
|
||||
&app,
|
||||
"WAITING_PERSONA",
|
||||
"用户语言已记录,等待人格体接入。",
|
||||
"用户语言已记录并投递到实时桥。",
|
||||
None,
|
||||
waiting,
|
||||
)
|
||||
|
|
@ -328,7 +337,7 @@ fn register_external_ai_bridge(
|
|||
display_name: name.into(),
|
||||
inbox_path: inbox.to_string_lossy().into_owned(),
|
||||
outbox_path: outbox.to_string_lossy().into_owned(),
|
||||
state: "LOCAL_EXPRESSION_ONLY_NO_EXECUTION_AUTHORITY".into(),
|
||||
state: "GLP_REALTIME_REGISTERED_NO_EXECUTION_AUTHORITY".into(),
|
||||
};
|
||||
let mut all = storage::bridges(&app)?;
|
||||
all.push(bridge.clone());
|
||||
|
|
@ -346,12 +355,166 @@ fn register_external_ai_bridge(
|
|||
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)
|
||||
}
|
||||
|
||||
#[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,
|
||||
|
|
@ -362,7 +525,14 @@ pub fn run() {
|
|||
delete_knowledge_document,
|
||||
import_knowledge_folder,
|
||||
export_knowledge_document,
|
||||
register_external_ai_bridge
|
||||
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
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("HoloLake runtime failed")
|
||||
|
|
|
|||
Loading…
Reference in a new issue