use bevy::prelude::*;
use super::{identity::Identity, ComInbox, ComSay, Notice, SharedCell, Speaker};
pub struct SomaBridgePlugin;
#[derive(Resource, Default)]
pub struct SomaPending(pub Vec<String>);
#[derive(Resource, Default)]
pub struct SomaThread(pub Option<[u8; 32]>);
impl Plugin for SomaBridgePlugin {
fn build(&self, app: &mut App) {
app.init_resource::<SomaPending>();
app.init_resource::<SomaThread>();
app.insert_non_send_resource(soma_kernel::Soma::spawn(
soma_kernel::SomaConfig::default(),
));
app.add_systems(Update, poll_soma);
if let Ok(qs) = std::env::var("SOMA_ASK") {
let questions: Vec<String> = qs
.split(";;")
.map(str::trim)
.filter(|q| !q.is_empty())
.map(str::to_string)
.collect();
if !questions.is_empty() {
app.add_systems(PostStartup, move |world: &mut World| {
for q in &questions {
ask(world, q);
}
});
}
}
}
}
pub fn parse_ask(line: &str) -> Option<&str> {
let t = line.trim();
if let Some(rest) = t.strip_prefix('?') {
let q = rest.trim();
if !q.is_empty() {
return Some(q);
}
}
if let Some(rest) = t.strip_prefix("ask ").or_else(|| t.strip_prefix("ask\t")) {
let q = rest.trim();
if !q.is_empty() {
return Some(q);
}
}
None
}
pub fn ask(world: &mut World, question: &str) {
world
.resource_mut::<ComInbox>()
.say(Speaker::User, question.to_string());
let context = recall(world, question);
let recalled = context.len();
world.resource_mut::<SomaPending>().0.push(question.to_string());
world
.non_send_resource::<soma_kernel::Soma>()
.ask_grounded(question, context);
world.resource_mut::<Notice>().show(if recalled > 0 {
format!("soma: thinking (recalled {recalled})...")
} else {
"soma: thinking...".to_string()
});
}
fn recall(world: &World, question: &str) -> Vec<String> {
const RECALL_MAX: usize = 3;
const CLIP: usize = 400;
let q_concepts: std::collections::HashSet<[u8; 32]> =
soma_kernel::concepts_of(question, "")
.iter()
.map(|c| soma_kernel::particle_of(c))
.collect();
if q_concepts.is_empty() {
return Vec::new();
}
let texts = super::content::load();
let shared = world.resource::<SharedCell>();
let me = world.resource::<Identity>().neuron;
let cell = shared.cell.lock().expect("shared cell poisoned");
let mut hits: Vec<(usize, usize, String)> = Vec::new();
let mut order = 0usize;
for neuron in [super::local_neuron(), me] {
let Some(chain) = cell.graph.chains.get(&neuron) else { continue };
for sig in chain.entries.values() {
let links = &sig.links;
if links.len() < 2 || links[1].from != links[0].to {
continue;
}
order += 1;
let score = links[2..]
.iter()
.filter(|l| q_concepts.contains(&l.to))
.count();
if score == 0 {
continue;
}
let (Some(q), Some(a)) = (texts.get(&links[0].to), texts.get(&links[1].to))
else {
continue;
};
let mut a_clip = a.clone();
if a_clip.len() > CLIP {
let mut end = CLIP;
while !a_clip.is_char_boundary(end) {
end -= 1;
}
a_clip.truncate(end);
a_clip.push_str("...");
}
hits.push((score, order, format!("Q: {q}\nA: {a_clip}")));
}
}
hits.sort_by(|x, y| y.0.cmp(&x.0).then(y.1.cmp(&x.1)));
hits.truncate(RECALL_MAX);
hits.into_iter().map(|(_, _, t)| t).collect()
}
fn poll_soma(
soma: NonSend<soma_kernel::Soma>,
shared: Res<SharedCell>,
who: Res<Identity>,
mut pending: ResMut<SomaPending>,
mut thread: ResMut<SomaThread>,
mut inbox: ResMut<ComInbox>,
mut notice: ResMut<Notice>,
mut status: ResMut<crate::worlds::models::MindStatus>,
) {
while let Some(ev) = soma.poll() {
match ev {
soma_kernel::SomaEvent::Waking => notice.show("soma: waking (loading model)..."),
soma_kernel::SomaEvent::Thinking => {
inbox.0.push(ComSay::StreamStart);
notice.show("soma: thinking...");
}
soma_kernel::SomaEvent::Delta(d) => {
inbox.0.push(ComSay::StreamDelta(d));
}
soma_kernel::SomaEvent::Answer {
question,
answer,
concepts,
tokens,
tok_per_s,
} => {
inbox.finish_stream(answer.clone());
let q = soma_kernel::particle_of(&question);
let a = soma_kernel::particle_of(&answer);
let from = thread.0.unwrap_or_else(soma_kernel::soma_anchor);
let mut links = vec![(from, q), (q, a)];
for c in &concepts {
links.push((a, soma_kernel::particle_of(c)));
}
let n_links = links.len();
let neuron = who.neuron;
let cast = {
let mut cell = shared.cell.lock().expect("shared cell poisoned");
cell.cast(neuron, links)
};
match cast {
Ok(_) => {
shared.bump();
thread.0 = Some(a);
status.last_tok_per_s = Some(tok_per_s);
notice.show(format!(
"soma: answered ({tokens} tok, {tok_per_s:.0} tok/s) - {n_links} links"
));
}
Err(e) => {
inbox.say(Speaker::System, format!("(link failed: {e:?})"));
notice.show("soma: answered, link failed");
}
}
if !pending.0.is_empty() {
pending.0.remove(0);
}
}
soma_kernel::SomaEvent::Error(e) => {
inbox.say(Speaker::System, format!("soma error: {e}"));
notice.show("soma: error");
if !pending.0.is_empty() {
pending.0.remove(0);
}
}
soma_kernel::SomaEvent::ModelChanged(path) => {
status.model = Some(path);
status.last_tok_per_s = None;
}
}
}
}