A skill server for Rhasspy written in Rust that uses the Hermes MQTT protocol. It is mainly meant for personal use as it currently hardcodes a number of intents, but can also act as an example.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

144 lines
3.3 KiB

#![warn(clippy::all)]
use serde::{Deserialize, Deserializer, Serialize};
use std::collections::HashMap;
// Hermes protocol types
#[derive(Debug, Deserialize)]
struct HermesIntent {
input: String,
#[serde(rename = "rawInput")]
input_raw: String,
intent: HermesIntentClassification,
#[serde(rename = "sessionId")]
session_id: String,
#[serde(rename = "siteId")]
site_id: String,
slots: Vec<HermesSlot>,
}
#[derive(Debug, Deserialize)]
struct HermesIntentClassification {
#[serde(rename = "confidenceScore")]
confidence: f32,
#[serde(rename = "intentName")]
name: String,
}
#[derive(Debug, Deserialize)]
struct HermesSlot {
confidence: f32,
entity: String,
#[serde(rename = "slotName")]
name: String,
value: HermesSlotValue,
#[serde(rename = "rawValue")]
value_raw: String,
}
#[derive(Debug, Deserialize)]
struct HermesSlotValue {
value: String,
}
#[derive(Debug, Serialize)]
struct HermesResponse {
#[serde(rename = "sessionId")]
session_id: String,
text: Option<String>,
}
#[derive(Debug)]
pub struct Intent {
pub name: String,
pub input: String,
pub input_raw: String,
pub confidence: f32,
pub session_id: String,
pub site_id: String,
pub slots: HashMap<String, Slot>,
}
impl From<HermesIntent> for Intent {
fn from(hermes_intent: HermesIntent) -> Self {
let slots = hermes_intent
.slots
.into_iter()
.map(|hermes_slot| (hermes_slot.name.clone(), Slot::from(hermes_slot)))
.collect();
Self {
name: hermes_intent.intent.name,
input: hermes_intent.input,
input_raw: hermes_intent.input_raw,
confidence: hermes_intent.intent.confidence,
session_id: hermes_intent.session_id,
site_id: hermes_intent.site_id,
slots,
}
}
}
#[derive(Debug)]
pub struct Slot {
pub confidence: f32,
pub entity: String,
pub value: String,
pub value_raw: String,
}
impl From<HermesSlot> for Slot {
fn from(slot_data: HermesSlot) -> Self {
Self {
confidence: slot_data.confidence,
entity: slot_data.entity,
value: slot_data.value.value,
value_raw: slot_data.value_raw,
}
}
}
impl<'de> Deserialize<'de> for Intent {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let intent_data = HermesIntent::deserialize(deserializer)?;
Ok(Intent::from(intent_data))
}
}
#[derive(Debug)]
pub struct Response {
pub session_id: String,
pub text: Option<String>,
}
impl Response {
pub fn new(session_id: String) -> Self {
let text = None;
Self { session_id, text }
}
pub fn with_text(mut self, text: impl Into<String>) -> Self {
self.text = Some(text.into());
self
}
}
impl From<Response> for Vec<u8> {
fn from(response: Response) -> Vec<u8> {
let hermes_response = HermesResponse::from(response);
serde_json::to_vec(&hermes_response).unwrap()
}
}
impl From<Response> for HermesResponse {
fn from(response: Response) -> Self {
HermesResponse {
session_id: response.session_id,
text: response.text,
}
}
}