use crate::{FlowLane, Rgba, ThinkingMode};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct CommandPaletteItem {
pub title: String,
pub detail: String,
pub category: String,
pub shortcut: String,
pub keywords: Vec<String>,
}
impl CommandPaletteItem {
pub fn matches(&self, query: &str) -> bool {
let query = query.trim().to_ascii_lowercase();
if query.is_empty() {
return true;
}
self.title.to_ascii_lowercase().contains(&query)
|| self.detail.to_ascii_lowercase().contains(&query)
|| self.category.to_ascii_lowercase().contains(&query)
|| self
.keywords
.iter()
.any(|keyword| keyword.to_ascii_lowercase().contains(&query))
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct QuickActionItem {
pub key: Option<char>,
pub title: String,
pub detail: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ContextItem {
pub label: String,
pub path_or_url: String,
pub detail: String,
pub selected: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ContextBucket {
pub title: String,
pub items: Vec<ContextItem>,
}
impl ContextBucket {
pub fn selected_count(&self) -> usize {
self.items.iter().filter(|item| item.selected).count()
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct DeckSection {
pub lane: FlowLane,
pub title: String,
pub lines: Vec<String>,
pub accent: Rgba,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ProviderProfileView {
pub name: String,
pub connector: String,
pub model: String,
pub status: String,
pub token_visible: bool,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ThemeEditorModel {
pub active_theme: String,
pub selected_field: String,
pub selected_channel: char,
pub preview_colors: Vec<(String, Rgba)>,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct StatusDeckModel {
pub mode: String,
pub thinking: ThinkingMode,
pub provider: String,
pub stream_enabled: bool,
pub validation: String,
pub sections: Vec<DeckSection>,
}
pub fn default_command_palette() -> Vec<CommandPaletteItem> {
let items: &[(&str, &str, &str, &str, &[&str])] = &[
(
"Quick Actions",
"Open fast toggles for mode, thinking, stream, context, and runtime panes",
"control",
"Ctrl+K",
&["actions", "toggle", "mode"],
),
(
"Context Picker",
"Select prompts, references, Markdown, and attachments for model context",
"context",
"F5",
&["prompt", "reference", "markdown", "attach"],
),
(
"Agent Flow Cockpit",
"Show loop lanes, Kanban, validation profiles, and run history",
"agent",
"F6",
&["kanban", "loop", "runs", "validation"],
),
(
"Provider Setup",
"Configure sidecar/provider settings without exposing secrets",
"provider",
"Ctrl+P",
&["codex", "openai", "huggingface", "oauth"],
),
(
"Theme Editor",
"Tune GPU palette fields and persist desktop visual language",
"visual",
"Alt+Y",
&["shader", "palette", "glsl", "theme"],
),
(
"Run Validation",
"Start the cheapest detected build/test/smoke command",
"runtime",
"Ctrl+Enter",
&["check", "test", "build", "smoke"],
),
];
items
.iter()
.map(
|(title, detail, category, shortcut, keywords)| CommandPaletteItem {
title: title.to_string(),
detail: detail.to_string(),
category: category.to_string(),
shortcut: shortcut.to_string(),
keywords: keywords.iter().map(|keyword| keyword.to_string()).collect(),
},
)
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn palette_search_matches_keywords() {
let matches = default_command_palette()
.into_iter()
.filter(|item| item.matches("oauth"))
.collect::<Vec<_>>();
assert_eq!(matches.len(), 1);
assert_eq!(matches[0].title, "Provider Setup");
}
}