feat: 开放小爱音箱接入 MiGPT-Next 源代码

This commit is contained in:
Del Wang
2025-04-09 21:59:38 +08:00
parent 6237c46441
commit f65f34d115
17 changed files with 2644 additions and 3 deletions
+47
View File
@@ -0,0 +1,47 @@
use neon::prelude::*;
use node::NodeManager;
use open_xiaoai::services::connect::{message::MessageManager, rpc::RPC};
use runtime::runtime;
use serde_json::json;
use server::AppServer;
mod node;
mod runtime;
mod server;
#[neon::export]
async fn start() -> () {
let _ = AppServer::run().await;
}
#[neon::export]
async fn run_shell(script: String, timeout_millis: f64) -> String {
let res = RPC::instance()
.call_remote(
"run_shell",
Some(json!(script)),
Some(timeout_millis as u64),
)
.await;
match res {
Err(e) => format!("run_shell error: {}", e),
Ok(res) => serde_json::to_string(&res.data.unwrap()).unwrap(),
}
}
#[neon::export]
async fn on_output_data(bytes: Vec<u8>) -> bool {
MessageManager::instance()
.send_stream("play", bytes, None)
.await
.is_ok()
}
#[neon::main]
fn main(mut cx: ModuleContext) -> NeonResult<()> {
let _ = neon::set_global_executor(&mut cx, runtime());
neon::registered().export(&mut cx)?;
NodeManager::instance().init(cx);
Ok(())
}
+93
View File
@@ -0,0 +1,93 @@
use neon::prelude::*;
use std::sync::{Arc, LazyLock, Mutex};
use tokio::sync::oneshot;
use crate::runtime::run_async;
pub struct NodeManager {
channel: Arc<Mutex<Option<Channel>>>,
}
static INSTANCE: LazyLock<NodeManager> = LazyLock::new(NodeManager::new);
impl NodeManager {
pub fn new() -> Self {
Self {
channel: Arc::new(Mutex::new(None)),
}
}
pub fn instance() -> &'static Self {
&INSTANCE
}
pub fn init(&self, mut cx: ModuleContext) {
let channel = cx.channel();
*self.channel.lock().unwrap() = Some(channel);
}
pub async fn call_fn<R, F, F2>(&self, key: &str, map_arg: F, map_res: F2) -> Result<R, String>
where
R: Send + 'static,
F: for<'a> Fn(&mut TaskContext<'a>) -> Handle<'a, JsValue> + Send + 'static,
F2: Fn(&mut TaskContext<'_>, Handle<'_, JsValue>) -> Result<R, String>
+ Send
+ Sync
+ 'static,
{
let channel = match self.channel.lock().unwrap().as_ref() {
Some(channel) => channel.clone(),
None => return Err("NodeManager 尚未初始化".into()),
};
let key = key.to_string();
let (tx, rx) = oneshot::channel::<Result<R, String>>();
channel.send(move |mut cx| {
let Ok(callbacks) = cx.global::<JsObject>("RUST_CALLBACKS") else {
let _ = tx.send(Err("无法获取 RUST_CALLBACKS 对象".into()));
return Ok(());
};
let Ok(callback) = callbacks.get::<JsFunction, _, _>(&mut cx, key.as_str()) else {
let _ = tx.send(Err("找不到函数".into()));
return Ok(());
};
let arg = map_arg(&mut cx);
let this = cx.undefined();
let Ok(res) = callback.call(&mut cx, this, [arg]) else {
let _ = tx.send(Err("函数调用失败".into()));
return Ok(());
};
if res.is_a::<JsPromise, _>(&mut cx) {
let future = res
.downcast::<JsPromise, _>(&mut cx)
.unwrap()
.to_future(&mut cx, move |mut cx, res| match res {
Ok(res) => Ok(map_res(&mut cx, res)),
Err(err) => Ok(map_res(&mut cx, err)),
})
.unwrap();
run_async(async move {
let res = future.await.unwrap();
let _ = tx.send(res);
});
} else {
let _ = tx.send(map_res(&mut cx, res));
}
Ok(())
});
let res = rx.await;
match res {
Ok(res) => res,
Err(_) => Err("接收数据失败".into()),
}
}
}
+18
View File
@@ -0,0 +1,18 @@
use std::future::Future;
use once_cell::sync::OnceCell;
use tokio::runtime::Runtime;
static RUNTIME: OnceCell<Runtime> = OnceCell::new();
pub fn runtime() -> &'static Runtime {
RUNTIME.get_or_try_init(Runtime::new).unwrap()
}
pub fn run_async<F>(future: F)
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
runtime().spawn(future);
}
+120
View File
@@ -0,0 +1,120 @@
use neon::prelude::Context;
use neon::types::JsUint8Array;
use open_xiaoai::base::{AppError, VERSION};
use open_xiaoai::services::connect::data::{Event, Request, Response, Stream};
use open_xiaoai::services::connect::handler::MessageHandler;
use open_xiaoai::services::connect::message::{MessageManager, WsStream};
use open_xiaoai::services::connect::rpc::RPC;
use open_xiaoai::services::speaker::SpeakerManager;
use open_xiaoai::utils::task::TaskManager;
use serde_json::json;
use tokio::net::{TcpListener, TcpStream};
use tokio_tungstenite::accept_async;
use crate::node::NodeManager;
pub struct AppServer;
async fn test() -> Result<(), AppError> {
SpeakerManager::play_text("已连接").await?;
// let _ = RPC::instance()
// .call_remote("start_recording", None, None)
// .await;
// let _ = RPC::instance().call_remote("start_play", None, None).await;
Ok(())
}
impl AppServer {
pub async fn connect(stream: TcpStream) -> Result<WsStream, AppError> {
let ws_stream = accept_async(stream).await?;
Ok(WsStream::Server(ws_stream))
}
pub async fn run() {
let addr = "0.0.0.0:4399";
let listener = TcpListener::bind(&addr)
.await
.expect(format!("❌ 绑定地址失败: {}", &addr).as_str());
println!("✅ 已启动: {:?}", addr);
while let Ok((stream, addr)) = listener.accept().await {
// 同一时刻只处理一个连接
AppServer::handle_connection(stream, addr).await;
}
}
async fn handle_connection(stream: TcpStream, addr: std::net::SocketAddr) {
let Ok(ws_stream) = AppServer::connect(stream).await else {
println!("❌ 连接异常: {}", addr);
return;
};
println!("✅ 已连接: {:?}", addr);
AppServer::init(ws_stream).await;
if let Err(e) = MessageManager::instance().process_messages().await {
println!("❌ 消息处理异常: {}", e);
}
AppServer::dispose().await;
println!("❌ 已断开连接");
}
async fn init(ws_stream: WsStream) {
MessageManager::instance().init(ws_stream).await;
MessageHandler::<Event>::instance()
.set_handler(on_event)
.await;
MessageHandler::<Stream>::instance()
.set_handler(on_stream)
.await;
let rpc = RPC::instance();
rpc.add_command("get_version", get_version).await;
let test = tokio::spawn(async move {
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
let _ = test().await;
});
TaskManager::instance().add("test", test).await;
}
async fn dispose() {
MessageManager::instance().dispose().await;
TaskManager::instance().dispose("test").await;
}
}
async fn get_version(_: Request) -> Result<Response, AppError> {
let data = json!(VERSION.to_string());
Ok(Response::from_data(data))
}
async fn on_stream(stream: Stream) -> Result<(), AppError> {
let Stream { tag, bytes, .. } = stream;
match tag.as_str() {
"record" => {
NodeManager::instance()
.call_fn::<(), _, _>(
"on_input_data",
move |cx| JsUint8Array::from_slice(cx, &bytes).unwrap().upcast(),
|_, _| Ok(()),
)
.await?;
}
_ => {}
}
Ok(())
}
async fn on_event(event: Event) -> Result<(), AppError> {
let event_json = serde_json::to_string(&event)?;
NodeManager::instance()
.call_fn::<(), _, _>(
"on_event",
move |cx| cx.string(&event_json).upcast(),
|_, _| Ok(()),
)
.await?;
Ok(())
}