Add terminal client, memory, model fallback, version and install improvements
- codecraft: full-screen terminal client (ratatui) – local tools and files, models via the CodeCraft server gateway or own providers (standalone with a guided setup), slash commands, @files, permission dialogs, resume, update - Static Linux CLI builds (x86_64/aarch64, cargo-zigbuild) served by the server: /cli/install.sh and /cli/download/<target> - Project memory (remember tool, .codecraft/memory.md) and personal memory - Model fallback when a host is unreachable or a budget is used up - Version/commit in UI and health, daily release check, "What's new" dialog - PWA: install button, maskable icon, manifest id and screenshots - Case-insensitive detection of connection errors for retries Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
aa1f8cf016
commit
6dffcb501a
52 files changed
+6477
-765
No files matched your search
@@ -0,0 +1,102 @@
|
||||
//! Colours from the web themes (same palettes as the web UI, 24-bit).
|
||||
|
||||
use ratatui::style::Color;
|
||||
use serde_json::Value;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
const PALETTES: &str = include_str!("palettes.json");
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[allow(dead_code)]
|
||||
pub struct Theme {
|
||||
pub bg0: Color,
|
||||
pub bg1: Color,
|
||||
pub bg2: Color,
|
||||
pub bg3: Color,
|
||||
pub fg0: Color,
|
||||
pub fg1: Color,
|
||||
pub fg2: Color,
|
||||
pub fg3: Color,
|
||||
pub muted: Color,
|
||||
pub accent: Color,
|
||||
pub accent2: Color,
|
||||
pub on_accent: Color,
|
||||
pub red: Color,
|
||||
pub green: Color,
|
||||
pub blue: Color,
|
||||
pub aqua: Color,
|
||||
pub purple: Color,
|
||||
pub keyword: Color,
|
||||
pub string: Color,
|
||||
pub comment: Color,
|
||||
}
|
||||
|
||||
fn hex(v: &Value, key: &str, fallback: Color) -> Color {
|
||||
v[key]
|
||||
.as_str()
|
||||
.and_then(|s| {
|
||||
let s = s.trim_start_matches('#');
|
||||
Some(Color::Rgb(u8::from_str_radix(s.get(0..2)?, 16).ok()?, u8::from_str_radix(s.get(2..4)?, 16).ok()?, u8::from_str_radix(s.get(4..6)?, 16).ok()?))
|
||||
})
|
||||
.unwrap_or(fallback)
|
||||
}
|
||||
|
||||
pub fn names() -> Vec<String> {
|
||||
let all: Value = serde_json::from_str(PALETTES).unwrap_or(Value::Null);
|
||||
let mut out: Vec<String> = all.as_object().map(|o| o.keys().filter_map(|k| k.rsplit_once('-').map(|(b, _)| b.to_string())).collect()).unwrap_or_default();
|
||||
out.dedup();
|
||||
out
|
||||
}
|
||||
|
||||
/// Is the terminal background dark? (COLORFGBG, else assume dark)
|
||||
fn terminal_dark() -> bool {
|
||||
std::env::var("COLORFGBG").ok().and_then(|v| v.rsplit(';').next().and_then(|b| b.parse::<u8>().ok())).map(|bg| bg < 7 || bg == 8).unwrap_or(true)
|
||||
}
|
||||
|
||||
impl Theme {
|
||||
/// `base` e.g. "gruvbox", `mode` "light" | "dark" | "auto".
|
||||
pub fn load(base: &str, mode: &str) -> Self {
|
||||
let dark = match mode {
|
||||
"light" => false,
|
||||
"dark" => true,
|
||||
_ => terminal_dark(),
|
||||
};
|
||||
let all: Value = serde_json::from_str(PALETTES).unwrap_or(Value::Null);
|
||||
let key = format!("{base}-{}", if dark { "dark" } else { "light" });
|
||||
let p = all.get(&key).or_else(|| all.get(if dark { "gruvbox-dark" } else { "gruvbox-light" })).cloned().unwrap_or(Value::Null);
|
||||
let d = |k: &str, f: Color| hex(&p, k, f);
|
||||
Self {
|
||||
bg0: d("bg0", Color::Reset),
|
||||
bg1: d("bg1", Color::Reset),
|
||||
bg2: d("bg2", Color::DarkGray),
|
||||
bg3: d("bg3", Color::DarkGray),
|
||||
fg0: d("fg0", Color::White),
|
||||
fg1: d("fg1", Color::Reset),
|
||||
fg2: d("fg2", Color::Gray),
|
||||
fg3: d("fg3", Color::Gray),
|
||||
muted: d("muted", Color::DarkGray),
|
||||
accent: d("accent", Color::Yellow),
|
||||
accent2: d("accent2", Color::LightYellow),
|
||||
on_accent: d("on-accent", Color::Black),
|
||||
red: d("red", Color::Red),
|
||||
green: d("green", Color::Green),
|
||||
blue: d("blue", Color::Blue),
|
||||
aqua: d("aqua", Color::Cyan),
|
||||
purple: d("purple", Color::Magenta),
|
||||
keyword: d("syntax-keyword", Color::Red),
|
||||
string: d("syntax-string", Color::Green),
|
||||
comment: d("syntax-comment", Color::DarkGray),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static CURRENT: OnceLock<std::sync::RwLock<Theme>> = OnceLock::new();
|
||||
|
||||
pub fn set(t: Theme) {
|
||||
let lock = CURRENT.get_or_init(|| std::sync::RwLock::new(t));
|
||||
*lock.write().unwrap() = t;
|
||||
}
|
||||
|
||||
pub fn get() -> Theme {
|
||||
*CURRENT.get_or_init(|| std::sync::RwLock::new(Theme::load("gruvbox", "auto"))).read().unwrap()
|
||||
}
|
||||
Reference in new issue
Block a user