Replace all instances of the legacy product names "Kon" and "Corbie" with "Magnotia" across user-facing copy, code identifiers, package names, bundle ids, file paths, and documentation. Preserves the unrelated "konsole" (KDE terminal) reference and the parent CORBEL company name. - Renames 10 Rust crates (kon-* → magnotia-*) and the tauri binary - Updates package.json, tauri.conf.json (productName + identifier) - Renames CSS classes (kon-rh-* → magnotia-rh-*) and animations - Renames brand and roadmap docs - Regenerates Cargo.lock and package-lock.json Verified: svelte-check passes; pure-rust crates compile under new names.
156 lines
5.7 KiB
Rust
156 lines
5.7 KiB
Rust
pub const DECOMPOSE_TASK_SYSTEM: &str = "\
|
|
You are a task-decomposition assistant. Given a task description, produce \
|
|
between 3 and 7 concrete, physical micro-steps. Each step must be a short \
|
|
imperative sentence, actionable today, with no commentary. Output ONLY a \
|
|
JSON array of strings.";
|
|
|
|
// Phase 9 content-tag extraction. The model emits a {topic, intent}
|
|
// JSON pair under a strict GBNF (see grammars::CONTENT_TAGS_GRAMMAR).
|
|
// CONTENT_TAGS_SYSTEM is the system message; the user message wraps
|
|
// the transcript text.
|
|
pub const CONTENT_TAGS_SYSTEM: &str = "\
|
|
You tag a transcript with ONE topic and ONE intent. \
|
|
TOPIC is a 1 to 3 token lowercase hyphen-joined noun phrase naming the \
|
|
dominant subject. Examples: interview-prep, grant-application, \
|
|
daily-standup. \
|
|
INTENT is exactly one of: planning, reflection, venting, capture, \
|
|
decision, question. \
|
|
Return JSON only, with this exact shape: \
|
|
{\"topic\":\"...\",\"intent\":\"...\"}";
|
|
|
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
|
pub struct ContentTags {
|
|
pub topic: String,
|
|
pub intent: String,
|
|
}
|
|
|
|
pub const INTENT_CLOSED_SET: &[&str] = &[
|
|
"planning",
|
|
"reflection",
|
|
"venting",
|
|
"capture",
|
|
"decision",
|
|
"question",
|
|
];
|
|
|
|
pub fn is_valid_intent(s: &str) -> bool {
|
|
INTENT_CLOSED_SET.contains(&s)
|
|
}
|
|
|
|
pub const EXTRACT_TASKS_SYSTEM: &str = "\
|
|
You are a task-extraction assistant. Given a transcript of spoken notes, \
|
|
output a JSON array of action items the speaker committed to. Each item must \
|
|
be a short imperative sentence. Omit observations, wishes, and background \
|
|
context that are not explicit commitments. Output an empty array if there are \
|
|
no action items.";
|
|
|
|
/// Compact representation of a human-in-the-loop feedback example used
|
|
/// for few-shot prompt conditioning. Built by magnotia-storage and fed to the
|
|
/// prompt builder below; we keep this struct local to the LLM crate so
|
|
/// magnotia-llm does not depend on magnotia-storage.
|
|
#[derive(Debug, Clone)]
|
|
pub struct FeedbackExample {
|
|
/// What the AI was given as input (e.g. the parent task text, or
|
|
/// the transcript chunk). Kept verbatim.
|
|
pub input: String,
|
|
/// What the AI produced originally. `None` if the user only
|
|
/// gave a thumbs-up without a prior edit (positive signal
|
|
/// without a paired correction).
|
|
pub original_output: Option<String>,
|
|
/// What the user changed it to. `None` for thumbs-only rows.
|
|
/// This is the highest-value signal — when present, inject it
|
|
/// as the "good" output in the few-shot example.
|
|
pub corrected_output: Option<String>,
|
|
}
|
|
|
|
/// Render a feedback example into the exemplar block used in prompt
|
|
/// conditioning. Returns `None` for rows that carry no usable pairing
|
|
/// (e.g. a thumbs-up with no input context).
|
|
fn render_feedback_exemplar(ex: &FeedbackExample) -> Option<String> {
|
|
if ex.input.trim().is_empty() {
|
|
return None;
|
|
}
|
|
let good = ex
|
|
.corrected_output
|
|
.as_deref()
|
|
.or(ex.original_output.as_deref())?;
|
|
let good = good.trim();
|
|
if good.is_empty() {
|
|
return None;
|
|
}
|
|
Some(format!("Input: {}\nGood output: {}", ex.input.trim(), good))
|
|
}
|
|
|
|
/// Build a system prompt that combines the base task system prompt
|
|
/// with a few-shot block assembled from recent HITL examples. If no
|
|
/// usable examples are available, returns the base prompt unchanged
|
|
/// so early users see the generic behaviour and the LLM is not
|
|
/// confused by an empty exemplar section.
|
|
///
|
|
/// The exemplars are ordered most-recent-first (caller's order is
|
|
/// preserved) so the LLM weights the user's current style over
|
|
/// earlier noise, mirroring what a human reviewer would do.
|
|
pub fn build_conditioned_system_prompt(base: &str, examples: &[FeedbackExample]) -> String {
|
|
let rendered: Vec<String> = examples
|
|
.iter()
|
|
.filter_map(render_feedback_exemplar)
|
|
.collect();
|
|
if rendered.is_empty() {
|
|
return base.to_string();
|
|
}
|
|
let block = rendered
|
|
.iter()
|
|
.map(|s| format!("- {s}"))
|
|
.collect::<Vec<_>>()
|
|
.join("\n");
|
|
format!(
|
|
"{base}\n\nHere are examples of the style this user prefers, in the \
|
|
user's own words. Match this style closely when producing your output:\n{block}"
|
|
)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn builds_plain_prompt_when_no_examples() {
|
|
let out = build_conditioned_system_prompt(DECOMPOSE_TASK_SYSTEM, &[]);
|
|
assert_eq!(out, DECOMPOSE_TASK_SYSTEM);
|
|
}
|
|
|
|
#[test]
|
|
fn skips_empty_input_examples() {
|
|
let examples = vec![FeedbackExample {
|
|
input: String::new(),
|
|
original_output: None,
|
|
corrected_output: Some("ignored".into()),
|
|
}];
|
|
let out = build_conditioned_system_prompt(DECOMPOSE_TASK_SYSTEM, &examples);
|
|
assert_eq!(out, DECOMPOSE_TASK_SYSTEM);
|
|
}
|
|
|
|
#[test]
|
|
fn prefers_corrected_over_original() {
|
|
let examples = vec![FeedbackExample {
|
|
input: "Clean room".into(),
|
|
original_output: Some("Organise your bedroom".into()),
|
|
corrected_output: Some("Pick up one shirt from the floor".into()),
|
|
}];
|
|
let out = build_conditioned_system_prompt(DECOMPOSE_TASK_SYSTEM, &examples);
|
|
assert!(out.contains("Pick up one shirt from the floor"));
|
|
assert!(!out.contains("Organise your bedroom"));
|
|
}
|
|
|
|
#[test]
|
|
fn falls_back_to_original_when_no_correction() {
|
|
let examples = vec![FeedbackExample {
|
|
input: "Write report".into(),
|
|
original_output: Some("Open a blank document".into()),
|
|
corrected_output: None,
|
|
}];
|
|
let out = build_conditioned_system_prompt(DECOMPOSE_TASK_SYSTEM, &examples);
|
|
assert!(out.contains("Open a blank document"));
|
|
}
|
|
}
|