omk 0.5.0

A Rust runtime for Kimi CLI. Turns prompts into proof-backed engineering runs with gates, worktrees, and replay.
Documentation
use ratatui::{
    layout::Rect,
    style::{Modifier, Style},
    text::{Line, Span},
    widgets::Paragraph,
    Frame,
};

use crate::vis::engine::model::PaneModel;
use crate::vis::engine::theme::Theme;

pub fn render(model: &PaneModel, frame: &mut Frame, area: Rect, theme: Theme) {
    let mut lines = vec![];

    if let Some(ref latest) = model.classifier {
        lines.push(Line::from(vec![
            Span::styled("intent: ", Style::default().fg(theme.fg_muted())),
            Span::styled(
                format!("{:?}", latest.intent).to_lowercase(),
                Style::default()
                    .fg(theme.fg_emphasis())
                    .add_modifier(Modifier::BOLD),
            ),
            Span::styled("  conf ", Style::default().fg(theme.fg_muted())),
            Span::styled(
                format!("{:.2}", latest.confidence),
                Style::default().fg(theme.fg_normal()),
            ),
            Span::styled("  ·  ", Style::default().fg(theme.fg_muted())),
            Span::styled(
                format!("{}ms", latest.latency_ms),
                Style::default().fg(theme.fg_normal()),
            ),
        ]));

        let reasoning = &latest.reasoning;
        let preview = if reasoning.chars().count() > 60 {
            format!(
                "{}",
                &reasoning[..reasoning
                    .char_indices()
                    .nth(60)
                    .map(|(i, _)| i)
                    .unwrap_or(reasoning.len())]
            )
        } else {
            reasoning.clone()
        };
        lines.push(Line::from(vec![
            Span::styled("reasoning: ", Style::default().fg(theme.fg_muted())),
            Span::styled(preview, Style::default().fg(theme.fg_normal())),
        ]));
    }

    if !model.recent_classifications.is_empty() {
        lines.push(Line::from(vec![Span::styled(
            "recent:",
            Style::default().fg(theme.fg_muted()),
        )]));
        for c in model.recent_classifications.iter().take(5) {
            let ts = c.ts.format("%H:%M:%S").to_string();
            let intent = format!("{:?}", c.intent).to_lowercase();
            let prefix = if c.reasoning.chars().count() > 20 {
                format!(
                    "{}",
                    &c.reasoning[..c
                        .reasoning
                        .char_indices()
                        .nth(20)
                        .map(|(i, _)| i)
                        .unwrap_or(c.reasoning.len())]
                )
            } else {
                c.reasoning.clone()
            };
            lines.push(Line::from(vec![
                Span::styled(format!("  {}  ", ts), Style::default().fg(theme.fg_muted())),
                Span::styled(
                    format!("{:10}", intent),
                    Style::default().fg(theme.fg_normal()),
                ),
                Span::styled(
                    format!("\"{}\"", prefix),
                    Style::default().fg(theme.fg_muted()),
                ),
            ]));
        }
    }

    if lines.is_empty() {
        return;
    }

    let paragraph = Paragraph::new(lines);
    frame.render_widget(paragraph, area);
}