use crate::tui::theme::VerbColor;
const VERBS: &[(&str, VerbColor)] = &[
("invoke", VerbColor::Invoke),
("infer", VerbColor::Infer),
("fetch", VerbColor::Fetch),
("exec", VerbColor::Exec),
("agent", VerbColor::Agent),
];
pub fn detect_verb_in_input(input: &str) -> Option<(usize, VerbColor, bool, &'static str)> {
if !input.starts_with('/') {
return None;
}
let rest = &input[1..];
let verb_end = rest.find(|c: char| c.is_whitespace()).unwrap_or(rest.len());
let verb_word = rest[..verb_end].to_lowercase();
for (name, color) in VERBS {
if verb_word == *name {
return Some((1 + verb_end, *color, true, name));
}
}
if verb_word.len() >= 2 {
for (name, color) in VERBS {
if name.starts_with(&verb_word) {
return Some((1 + verb_end, *color, false, name));
}
}
}
None
}
const INVOKE_HINTS: &[&str] = &[
"novanet:describe {\"entity\": \"qr-code\"}",
"novanet:generate {\"locale\": \"fr-FR\", \"entity\": \"landing\"}",
"novanet:traverse {\"start\": \"entity:qr\", \"depth\": 2}",
"novanet:search {\"query\": \"pricing page\"}",
"novanet:atoms {\"entity\": \"qr-code\", \"forms\": [\"title\", \"text\"]}",
"filesystem:read_file {\"path\": \"./README.md\"}",
"filesystem:list_directory {\"path\": \"./src\"}",
"browser:screenshot {\"url\": \"https://example.com\"}",
"database:query {\"sql\": \"SELECT * FROM users LIMIT 5\"}",
"github:search_repos {\"query\": \"language:rust stars:>1000\"}",
];
const INFER_HINTS: &[&str] = &[
"Generate a landing page headline for a SaaS product",
"Summarize this article in 3 bullet points",
"Translate to French: Hello, how are you today?",
"Explain this Rust code like I'm a beginner",
"Write unit tests for this function using pytest",
"Convert this SQL query to a TypeScript Prisma query",
"Rewrite this paragraph in a more professional tone",
"Generate 5 creative names for a coffee shop",
"Create a regex to match email addresses",
"Write a haiku about programming",
"Debug this error: 'cannot borrow as mutable'",
"Suggest 3 improvements for this API endpoint",
"Write a commit message for these changes",
"Create a product description for wireless earbuds",
"Explain the difference between async and sync",
];
const EXEC_HINTS: &[&str] = &[
"npm run build",
"cargo test --release",
"git status",
"git log --oneline -10",
"git diff --staged",
"git commit -am \"fix: resolve issue\"",
"ls -la ./src | head -20",
"find . -name \"*.rs\" | wc -l",
"grep -r \"TODO\" ./src",
"docker ps -a",
"docker-compose up -d",
"npm outdated",
"cargo clippy -- -D warnings",
"python -m pytest -v",
"curl -I https://example.com",
"du -sh ./target",
"cat package.json | jq '.dependencies'",
"ps aux | grep node",
"netstat -an | grep LISTEN",
"tree -L 2 ./src",
];
const FETCH_HINTS: &[&str] = &[
"https://catfact.ninja/fact",
"https://api.open-meteo.com/v1/forecast?latitude=48.8566&longitude=2.3522¤t_weather=true",
"https://httpbin.org/get",
"https://dog.ceo/api/breeds/image/random",
"https://api.chucknorris.io/jokes/random",
"https://uselessfacts.jsph.pl/random.json?language=en",
"https://api.coindesk.com/v1/bpi/currentprice.json",
"https://api.github.com/zen",
"https://official-joke-api.appspot.com/random_joke",
"https://api.adviceslip.com/advice",
"https://randomfox.ca/floof/",
"https://xkcd.com/info.0.json",
"https://api.ipify.org?format=json",
"https://worldtimeapi.org/api/ip",
"https://opentdb.com/api.php?amount=1",
"http://api.open-notify.org/iss-now.json",
"https://api.agify.io?name=thibaut",
];
const AGENT_HINTS: &[&str] = &[
"Research competitors and write a market analysis report",
"Analyze this codebase and suggest 5 improvements",
"Build a landing page for QR Code AI with SEO",
"Debug this error and propose a fix with tests",
"Create a full REST API spec for a todo app",
"Review this PR and provide detailed feedback",
"Refactor this module to use async/await",
"Generate documentation for all public functions",
"Find and fix all security vulnerabilities",
"Create a migration plan from v1 to v2 API",
"Write a technical blog post about this feature",
"Set up CI/CD pipeline with GitHub Actions",
"Optimize database queries for better performance",
"Create test fixtures for integration tests",
"Design a caching strategy for this endpoint",
];
const SPAWN_HINTS: &[&str] = &[
"Delegate: research this topic in depth",
"Spawn: handle this subtask independently",
"Delegate: write tests for this module",
"Spawn: analyze and summarize these files",
"Delegate: generate documentation for API",
"Spawn: refactor this function for clarity",
"Delegate: investigate this bug in isolation",
"Spawn: create sample data for testing",
];
pub fn verb_placeholder(verb_color: &VerbColor, frame: u8) -> &'static str {
let idx = (frame / 60) as usize; match verb_color {
VerbColor::Invoke => INVOKE_HINTS[idx % INVOKE_HINTS.len()],
VerbColor::Infer => INFER_HINTS[idx % INFER_HINTS.len()],
VerbColor::Exec => EXEC_HINTS[idx % EXEC_HINTS.len()],
VerbColor::Fetch => FETCH_HINTS[idx % FETCH_HINTS.len()],
VerbColor::Agent => AGENT_HINTS[idx % AGENT_HINTS.len()],
VerbColor::Spawn => SPAWN_HINTS[idx % SPAWN_HINTS.len()],
VerbColor::User => "Type your message...",
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detect_verb_exact_match() {
let result = detect_verb_in_input("/invoke novanet:search");
assert!(result.is_some());
let (len, color, is_complete, name) = result.unwrap();
assert_eq!(len, 7); assert_eq!(color, VerbColor::Invoke);
assert!(is_complete);
assert_eq!(name, "invoke");
}
#[test]
fn test_detect_verb_partial_match() {
let result = detect_verb_in_input("/inv");
assert!(result.is_some());
let (_, color, is_complete, name) = result.unwrap();
assert_eq!(color, VerbColor::Invoke);
assert!(!is_complete);
assert_eq!(name, "invoke");
}
#[test]
fn test_detect_verb_no_match() {
assert!(detect_verb_in_input("invoke").is_none()); assert!(detect_verb_in_input("/x").is_none()); assert!(detect_verb_in_input("/unknown").is_none()); }
#[test]
fn test_detect_all_verbs() {
for (name, expected_color) in VERBS {
let input = format!("/{} args", name);
let result = detect_verb_in_input(&input);
assert!(result.is_some(), "Should detect verb: {}", name);
let (_, color, is_complete, detected_name) = result.unwrap();
assert_eq!(color, *expected_color);
assert!(is_complete);
assert_eq!(detected_name, *name);
}
}
#[test]
fn test_verb_placeholder_rotates() {
let hint0 = verb_placeholder(&VerbColor::Infer, 0);
let hint60 = verb_placeholder(&VerbColor::Infer, 60);
let hint120 = verb_placeholder(&VerbColor::Infer, 120);
assert!(!hint0.is_empty());
assert!(!hint60.is_empty());
assert!(!hint120.is_empty());
assert_ne!(hint0, hint60);
}
#[test]
fn test_verb_placeholder_user() {
assert_eq!(
verb_placeholder(&VerbColor::User, 0),
"Type your message..."
);
assert_eq!(
verb_placeholder(&VerbColor::User, 100),
"Type your message..."
);
}
#[test]
fn test_hint_arrays_not_empty() {
assert!(!INVOKE_HINTS.is_empty());
assert!(!INFER_HINTS.is_empty());
assert!(!EXEC_HINTS.is_empty());
assert!(!FETCH_HINTS.is_empty());
assert!(!AGENT_HINTS.is_empty());
assert!(!SPAWN_HINTS.is_empty());
}
}