Skip to content

Commit 4f28514

Browse files
author
User
committed
feat(tui): Improve theme support, add light theme
feat(agent): Add user appearance (User Name)
1 parent 715cc8d commit 4f28514

19 files changed

+275
-218
lines changed

Cargo.lock

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ headless_chrome = "1.0.17"
7373

7474
# tui
7575
crossterm = { version = "0.28.1", features = ["event-stream"] }
76-
ratatui = "0.29.0"
76+
ratatui = { version = "0.29.0", features = ["serde"] }
7777
color-eyre = "0.6.3"
7878
tui-textarea = { git = "https://github.com/kosz78/tui-textarea.git", branch = "main" }
7979
tui-tree-widget = "0.23.0"
@@ -83,3 +83,4 @@ throbber-widgets-tui = "0.8.0"
8383
tui-popup = "0.6.0"
8484
format_num = "0.1.0"
8585
termimad = "0.31.3"
86+
ansi_colours = "1.2.3"

huly-coder.yaml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,18 @@ model: anthropic/claude-3.5-sonnet
3434
# provider:
3535
# order: [ "openai", "together"]
3636
# allow_fallbacks: false
37-
37+
38+
#---------------------------------------
39+
# Appearance Configuration
40+
#---------------------------------------
41+
# Supported appearance themes:
42+
# - dark (default)
43+
# - light
44+
# - file path to yaml file
45+
# to develop a new theme, you can run `cargo r -- --autoreload-theme` for autoreload theme on change and specify the theme path in the config file
46+
appearance:
47+
theme: dark
48+
user_name: default_user
3849

3950
#---------------------------------------
4051
# Permission mode

src/agent/mod.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -425,8 +425,7 @@ impl Agent {
425425
self.config.provider,
426426
self.config.model
427427
);
428-
let system_prompt =
429-
prepare_system_prompt(&self.config.workspace, &self.config.user_instructions).await;
428+
let system_prompt = prepare_system_prompt(&self.config).await;
430429
let system_prompt_token_count = count_tokens(&system_prompt);
431430
let mut tools_tokens = 0;
432431

src/agent/utils.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use rig::vector_store::in_memory_store::InMemoryVectorIndex;
1010
use rig::vector_store::VectorStoreIndex;
1111
use tokio::sync::RwLock;
1212

13+
use crate::config::Config;
1314
use crate::templates::{ENV_DETAILS, SYSTEM_PROMPT};
1415
use crate::tools::execute_command::ProcessRegistry;
1516
use crate::tools::memory::{self, Entity};
@@ -27,8 +28,9 @@ fn get_shell_path() -> String {
2728
}
2829
}
2930

30-
pub async fn prepare_system_prompt(workspace_dir: &Path, user_instructions: &str) -> String {
31-
let workspace_dir = workspace_dir
31+
pub async fn prepare_system_prompt(config: &Config) -> String {
32+
let workspace_dir = config
33+
.workspace
3234
.as_os_str()
3335
.to_str()
3436
.unwrap()
@@ -37,10 +39,11 @@ pub async fn prepare_system_prompt(workspace_dir: &Path, user_instructions: &str
3739
SYSTEM_PROMPT,
3840
&HashMap::from([
3941
("WORKSPACE_DIR", workspace_dir.as_str()),
42+
("USER_NAME", &config.appearance.user_name),
4043
("OS_NAME", std::env::consts::OS),
4144
("OS_SHELL_EXECUTABLE", &get_shell_path()),
4245
("USER_HOME_DIR", dirs::home_dir().unwrap().to_str().unwrap()),
43-
("USER_INSTRUCTION", user_instructions),
46+
("USER_INSTRUCTION", &config.user_instructions),
4447
]),
4548
)
4649
.unwrap()

src/config.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,13 +82,24 @@ pub enum PermissionMode {
8282
DenyAll,
8383
}
8484

85+
fn default_user() -> String {
86+
"default_user".to_string()
87+
}
88+
#[derive(Debug, Deserialize, Clone)]
89+
pub struct Appearance {
90+
pub theme: String,
91+
#[serde(default = "default_user")]
92+
pub user_name: String,
93+
}
94+
8595
#[derive(Debug, Deserialize, Clone)]
8696
pub struct Config {
8797
pub provider: ProviderKind,
8898
pub provider_api_key: Option<String>,
8999
pub provider_base_url: Option<String>,
90100
pub provider_config: Option<serde_json::Value>,
91101
pub model: String,
102+
pub appearance: Appearance,
92103
pub permission_mode: PermissionMode,
93104
pub workspace: PathBuf,
94105
pub user_instructions: String,

src/main.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ struct Args {
4646
/// Path to config file
4747
#[arg(short, long, default_value = "huly-coder-local.yaml")]
4848
config: String,
49+
/// For theme development, auto reload theme file on changes
50+
#[arg(long, default_value = "false")]
51+
autoreload_theme: bool,
4952
}
5053

5154
fn init_logger(data_dir: &str) {
@@ -161,6 +164,7 @@ async fn main() -> color_eyre::Result<()> {
161164
control_sender,
162165
output_receiver,
163166
history,
167+
args.autoreload_theme,
164168
)
165169
.run(terminal)
166170
.await;

src/templates/system_prompt.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ You are equipped with a persistent memory system utilizing a local knowledge gra
4040
- open_nodes: Open and examine specific nodes
4141

4242
## User Identification:
43-
- You should assume that you are interacting with default_user
44-
- If you have not identified default_user, proactively try to do so
43+
- You should assume that you are interacting with ${USER_NAME}
44+
- If you have not identified ${USER_NAME}, proactively try to do so
4545
- Always refer to your knowledge graph as your "memory"
4646

4747
## Memory Processing Guidelines:
@@ -206,6 +206,7 @@ Operating System: ${OS_NAME}
206206
Default Shell: ${OS_SHELL_EXECUTABLE}
207207
Home Directory: ${USER_HOME_DIR}
208208
Current Working Directory: ${WORKSPACE_DIR}
209+
User Name: ${USER_NAME}
209210

210211
====
211212

src/tui/app.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
// Copyright © 2025 Huly Labs. Use of this source code is governed by the MIT license.
22
use std::collections::{HashMap, HashSet};
33
use std::io::Write;
4-
use std::path::PathBuf;
4+
use std::path::{Path, PathBuf};
5+
use std::time::SystemTime;
56
use std::{fs, vec};
67

78
use crate::agent::event::{AgentCommandStatus, AgentState, ConfirmToolResponse};
@@ -105,6 +106,7 @@ pub struct App<'a> {
105106
pub theme: Theme,
106107
pub model: ModelState,
107108
pub ui: UiState<'a>,
109+
pub autoreload_theme: bool,
108110
}
109111

110112
impl UiState<'_> {
@@ -144,16 +146,19 @@ impl App<'_> {
144146
sender: mpsc::UnboundedSender<agent::AgentControlEvent>,
145147
receiver: mpsc::UnboundedReceiver<agent::AgentOutputEvent>,
146148
messages: Vec<Message>,
149+
autoreload_theme: bool,
147150
) -> Self {
151+
let theme = Theme::load(&config.appearance.theme).unwrap();
148152
Self {
149153
ui: UiState::new(config.workspace.clone()),
150154
config,
151155
data_dir,
152156
running: true,
153157
events: UiEventMultiplexer::new(receiver),
154158
agent_sender: sender,
155-
theme: Theme::default(),
159+
theme,
156160
model: ModelState::new(messages, model_info),
161+
autoreload_theme,
157162
}
158163
}
159164

@@ -223,7 +228,20 @@ impl App<'_> {
223228
if !self.model.messages.is_empty() {
224229
self.ui.history_follow_last = true;
225230
}
231+
let autoload_theme =
232+
self.autoreload_theme && Path::new(&self.config.appearance.theme).exists();
233+
let mut last_theme_modified = SystemTime::now();
226234
while self.running {
235+
if autoload_theme {
236+
let current_theme_modified =
237+
std::fs::metadata(&self.config.appearance.theme)?.modified()?;
238+
if current_theme_modified != last_theme_modified {
239+
if let Ok(theme) = Theme::load(&self.config.appearance.theme) {
240+
self.theme = theme;
241+
}
242+
last_theme_modified = current_theme_modified;
243+
}
244+
}
227245
terminal.draw(|frame| frame.render_widget(&mut self, frame.area()))?;
228246

229247
match self.events.next().await? {

0 commit comments

Comments
 (0)