retro mode ui!
This commit is contained in:
211
Cargo.lock
generated
211
Cargo.lock
generated
@@ -229,7 +229,7 @@ dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools",
|
||||
"itertools 0.12.1",
|
||||
"lazy_static",
|
||||
"lazycell",
|
||||
"log",
|
||||
@@ -279,6 +279,21 @@ version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||
|
||||
[[package]]
|
||||
name = "cassowary"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
|
||||
|
||||
[[package]]
|
||||
name = "castaway"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
|
||||
dependencies = [
|
||||
"rustversion",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.35"
|
||||
@@ -408,6 +423,20 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "compact_str"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32"
|
||||
dependencies = [
|
||||
"castaway",
|
||||
"cfg-if",
|
||||
"itoa",
|
||||
"rustversion",
|
||||
"ryu",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "config"
|
||||
version = "0.14.1"
|
||||
@@ -490,7 +519,7 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "980c2afde4af43d6a05c5be738f9eae595cff86dce1f38f88b95058a98c027f3"
|
||||
dependencies = [
|
||||
"crossterm",
|
||||
"crossterm 0.29.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -535,7 +564,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51360853ebbeb3df20c76c82aecf43d387a62860f1a59ba65ab51f00eea85aad"
|
||||
dependencies = [
|
||||
"crokey-proc_macros",
|
||||
"crossterm",
|
||||
"crossterm 0.29.0",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"strict",
|
||||
@@ -547,7 +576,7 @@ version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3bf1a727caeb5ee5e0a0826a97f205a9cf84ee964b0b48239fef5214a00ae439"
|
||||
dependencies = [
|
||||
"crossterm",
|
||||
"crossterm 0.29.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strict",
|
||||
@@ -610,6 +639,22 @@ version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.28.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
|
||||
dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
"crossterm_winapi",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"rustix 0.38.44",
|
||||
"signal-hook",
|
||||
"signal-hook-mio",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.29.0"
|
||||
@@ -653,6 +698,41 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.20.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.20.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "0.99.20"
|
||||
@@ -846,6 +926,12 @@ version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
@@ -975,11 +1061,12 @@ dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"clap",
|
||||
"crossterm",
|
||||
"crossterm 0.29.0",
|
||||
"dirs 5.0.1",
|
||||
"g3-config",
|
||||
"g3-core",
|
||||
"indicatif",
|
||||
"ratatui",
|
||||
"rustyline",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -1147,6 +1234,11 @@ name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
@@ -1430,6 +1522,12 @@ dependencies = [
|
||||
"zerovec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "1.1.0"
|
||||
@@ -1474,6 +1572,25 @@ dependencies = [
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indoc"
|
||||
version = "2.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
|
||||
|
||||
[[package]]
|
||||
name = "instability"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "435d80800b936787d62688c927b6490e887c7ef5ff9ce922c6c6050fca75eb9a"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"indoc",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-uring"
|
||||
version = "0.7.10"
|
||||
@@ -1506,6 +1623,15 @@ dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.15"
|
||||
@@ -1702,6 +1828,15 @@ version = "0.4.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
||||
|
||||
[[package]]
|
||||
name = "lru"
|
||||
version = "0.12.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
|
||||
dependencies = [
|
||||
"hashbrown 0.15.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.2.0"
|
||||
@@ -1991,6 +2126,12 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "pathdiff"
|
||||
version = "0.2.3"
|
||||
@@ -2163,6 +2304,27 @@ dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ratatui"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b"
|
||||
dependencies = [
|
||||
"bitflags 2.9.4",
|
||||
"cassowary",
|
||||
"compact_str",
|
||||
"crossterm 0.28.1",
|
||||
"indoc",
|
||||
"instability",
|
||||
"itertools 0.13.0",
|
||||
"lru",
|
||||
"paste",
|
||||
"strum",
|
||||
"unicode-segmentation",
|
||||
"unicode-truncate",
|
||||
"unicode-width 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.17"
|
||||
@@ -2607,6 +2769,12 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "strict"
|
||||
version = "0.2.0"
|
||||
@@ -2619,6 +2787,28 @@ version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.26.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
|
||||
dependencies = [
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.26.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.106"
|
||||
@@ -2997,6 +3187,17 @@ version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-truncate"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf"
|
||||
dependencies = [
|
||||
"itertools 0.13.0",
|
||||
"unicode-segmentation",
|
||||
"unicode-width 0.1.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.14"
|
||||
|
||||
@@ -20,4 +20,5 @@ tokio-util = "0.7"
|
||||
indicatif = "0.17"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
crossterm = "0.29.0"
|
||||
ratatui = "0.29"
|
||||
termimad = "0.34.0"
|
||||
|
||||
@@ -10,8 +10,10 @@ use tracing::{error, info};
|
||||
|
||||
mod tui;
|
||||
mod ui_writer_impl;
|
||||
mod retro_tui;
|
||||
use tui::SimpleOutput;
|
||||
use ui_writer_impl::ConsoleUiWriter;
|
||||
use ui_writer_impl::{ConsoleUiWriter, RetroTuiWriter};
|
||||
use retro_tui::RetroTui;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "g3")]
|
||||
@@ -48,39 +50,60 @@ pub struct Cli {
|
||||
/// Maximum number of turns in autonomous mode (default: 5)
|
||||
#[arg(long, default_value = "5")]
|
||||
pub max_turns: usize,
|
||||
|
||||
/// Use retro terminal UI (inspired by 80s sci-fi)
|
||||
#[arg(long)]
|
||||
pub retro: bool,
|
||||
}
|
||||
|
||||
pub async fn run() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
// Initialize logging with filtering
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
|
||||
// Only initialize logging if not in retro mode
|
||||
if !cli.retro {
|
||||
// Initialize logging with filtering
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
|
||||
|
||||
// Create a filter that suppresses llama_cpp logs unless in verbose mode
|
||||
let filter = if cli.verbose {
|
||||
EnvFilter::from_default_env()
|
||||
.add_directive(format!("{}=debug", env!("CARGO_PKG_NAME")).parse().unwrap())
|
||||
.add_directive("g3_core=debug".parse().unwrap())
|
||||
.add_directive("g3_cli=debug".parse().unwrap())
|
||||
.add_directive("g3_execution=debug".parse().unwrap())
|
||||
.add_directive("g3_providers=debug".parse().unwrap())
|
||||
// Create a filter that suppresses llama_cpp logs unless in verbose mode
|
||||
let filter = if cli.verbose {
|
||||
EnvFilter::from_default_env()
|
||||
.add_directive(format!("{}=debug", env!("CARGO_PKG_NAME")).parse().unwrap())
|
||||
.add_directive("g3_core=debug".parse().unwrap())
|
||||
.add_directive("g3_cli=debug".parse().unwrap())
|
||||
.add_directive("g3_execution=debug".parse().unwrap())
|
||||
.add_directive("g3_providers=debug".parse().unwrap())
|
||||
} else {
|
||||
EnvFilter::from_default_env()
|
||||
.add_directive(format!("{}=info", env!("CARGO_PKG_NAME")).parse().unwrap())
|
||||
.add_directive("g3_core=info".parse().unwrap())
|
||||
.add_directive("g3_cli=info".parse().unwrap())
|
||||
.add_directive("g3_execution=info".parse().unwrap())
|
||||
.add_directive("g3_providers=info".parse().unwrap())
|
||||
.add_directive("llama_cpp=off".parse().unwrap()) // Suppress all llama_cpp logs
|
||||
.add_directive("llama=off".parse().unwrap()) // Suppress all llama.cpp logs
|
||||
};
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(tracing_subscriber::fmt::layer())
|
||||
.with(filter)
|
||||
.init();
|
||||
} else {
|
||||
EnvFilter::from_default_env()
|
||||
.add_directive(format!("{}=info", env!("CARGO_PKG_NAME")).parse().unwrap())
|
||||
.add_directive("g3_core=info".parse().unwrap())
|
||||
.add_directive("g3_cli=info".parse().unwrap())
|
||||
.add_directive("g3_execution=info".parse().unwrap())
|
||||
.add_directive("g3_providers=info".parse().unwrap())
|
||||
.add_directive("llama_cpp=off".parse().unwrap()) // Suppress all llama_cpp logs
|
||||
.add_directive("llama=off".parse().unwrap()) // Suppress all llama.cpp logs
|
||||
};
|
||||
// In retro mode, we don't want any logging output to interfere with the TUI
|
||||
// We'll use a no-op subscriber
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(tracing_subscriber::fmt::layer())
|
||||
.with(filter)
|
||||
.init();
|
||||
// Create a filter that suppresses ALL logs in retro mode
|
||||
let filter = EnvFilter::from_default_env()
|
||||
.add_directive("off".parse().unwrap()); // Turn off all logging
|
||||
|
||||
info!("Starting G3 AI Coding Agent");
|
||||
tracing_subscriber::registry()
|
||||
.with(filter)
|
||||
.init();
|
||||
}
|
||||
|
||||
if !cli.retro {
|
||||
info!("Starting G3 AI Coding Agent");
|
||||
}
|
||||
|
||||
// Set up workspace directory
|
||||
let workspace_dir = if let Some(ws) = cli.workspace {
|
||||
@@ -104,19 +127,23 @@ pub async fn run() -> Result<()> {
|
||||
project.ensure_workspace_exists()?;
|
||||
project.enter_workspace()?;
|
||||
|
||||
info!("Using workspace: {}", project.workspace().display());
|
||||
if !cli.retro {
|
||||
info!("Using workspace: {}", project.workspace().display());
|
||||
}
|
||||
|
||||
// Load configuration
|
||||
let config = Config::load(cli.config.as_deref())?;
|
||||
|
||||
// Initialize agent
|
||||
let ui_writer = ConsoleUiWriter::new();
|
||||
let mut agent = Agent::new(config, ui_writer).await?;
|
||||
let mut agent = Agent::new(config.clone(), ui_writer).await?;
|
||||
|
||||
// Execute task, autonomous mode, or start interactive mode
|
||||
if cli.autonomous {
|
||||
// Autonomous mode with coach-player feedback loop
|
||||
info!("Starting autonomous mode");
|
||||
if !cli.retro {
|
||||
info!("Starting autonomous mode");
|
||||
}
|
||||
run_autonomous(
|
||||
agent,
|
||||
project,
|
||||
@@ -127,23 +154,187 @@ pub async fn run() -> Result<()> {
|
||||
.await?;
|
||||
} else if let Some(task) = cli.task {
|
||||
// Single-shot mode
|
||||
info!("Executing task: {}", task);
|
||||
if !cli.retro {
|
||||
info!("Executing task: {}", task);
|
||||
}
|
||||
let output = SimpleOutput::new();
|
||||
let result = agent
|
||||
.execute_task_with_timing(&task, None, false, cli.show_prompt, cli.show_code, true)
|
||||
.await?;
|
||||
output.print_markdown(&result);
|
||||
} else {
|
||||
let output = SimpleOutput::new();
|
||||
// Interactive mode (default)
|
||||
info!("Starting interactive mode");
|
||||
output.print(&format!("📁 Workspace: {}", project.workspace().display()));
|
||||
run_interactive(agent, cli.show_prompt, cli.show_code).await?;
|
||||
if !cli.retro {
|
||||
info!("Starting interactive mode");
|
||||
}
|
||||
|
||||
if cli.retro {
|
||||
// Use retro terminal UI
|
||||
run_interactive_retro(config, cli.show_prompt, cli.show_code).await?;
|
||||
} else {
|
||||
// Use standard terminal UI
|
||||
let output = SimpleOutput::new();
|
||||
output.print(&format!("📁 Workspace: {}", project.workspace().display()));
|
||||
run_interactive(agent, cli.show_prompt, cli.show_code).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_interactive_retro(config: Config, show_prompt: bool, show_code: bool) -> Result<()> {
|
||||
use crossterm::event::{self, Event, KeyCode, KeyModifiers};
|
||||
use std::time::Duration;
|
||||
|
||||
// Set environment variable to suppress println in other crates
|
||||
std::env::set_var("G3_RETRO_MODE", "1");
|
||||
|
||||
// Initialize the retro terminal UI
|
||||
let tui = RetroTui::start().await?;
|
||||
|
||||
// Create agent with RetroTuiWriter
|
||||
let ui_writer = RetroTuiWriter::new(tui.clone());
|
||||
let mut agent = Agent::new(config, ui_writer).await?;
|
||||
|
||||
// Display initial system messages
|
||||
tui.output("SYSTEM: G3 AI CODING AGENT ONLINE");
|
||||
tui.output("SYSTEM: READY FOR INPUT");
|
||||
tui.output("");
|
||||
|
||||
// Display provider and model information
|
||||
match agent.get_provider_info() {
|
||||
Ok((provider, model)) => {
|
||||
tui.output(&format!("SYSTEM: PROVIDER: {} | MODEL: {}", provider, model));
|
||||
}
|
||||
Err(e) => {
|
||||
tui.error(&format!("Failed to get provider info: {}", e));
|
||||
}
|
||||
}
|
||||
|
||||
tui.output("");
|
||||
tui.output("Type 'exit' or 'quit' to exit, use Up/Down arrows to scroll output");
|
||||
tui.output("For multiline input: use \\ at the end of a line to continue");
|
||||
tui.output("");
|
||||
|
||||
// Track multiline input
|
||||
let mut multiline_buffer = String::new();
|
||||
let mut in_multiline = false;
|
||||
let mut input_buffer = String::new();
|
||||
|
||||
// Main event loop
|
||||
loop {
|
||||
// Update context window display
|
||||
let context = agent.get_context_window();
|
||||
tui.update_context(context.used_tokens, context.total_tokens, context.percentage_used());
|
||||
|
||||
// Update the displayed input buffer
|
||||
tui.update_input(&input_buffer);
|
||||
|
||||
// Poll for keyboard events
|
||||
if event::poll(Duration::from_millis(50))? {
|
||||
if let Event::Key(key) = event::read()? {
|
||||
match key.code {
|
||||
KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||
tui.exit();
|
||||
break;
|
||||
}
|
||||
KeyCode::Char('d') if key.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||
tui.exit();
|
||||
break;
|
||||
}
|
||||
KeyCode::Enter => {
|
||||
if !input_buffer.is_empty() {
|
||||
let trimmed = input_buffer.trim_end();
|
||||
|
||||
// Check if line ends with backslash for continuation
|
||||
if trimmed.ends_with('\\') {
|
||||
// Remove the backslash and add to buffer
|
||||
let without_backslash = &trimmed[..trimmed.len() - 1];
|
||||
multiline_buffer.push_str(without_backslash);
|
||||
multiline_buffer.push('\n');
|
||||
in_multiline = true;
|
||||
input_buffer.clear();
|
||||
tui.status("MULTILINE INPUT");
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we're in multiline mode and no backslash, this is the final line
|
||||
let final_input = if in_multiline {
|
||||
multiline_buffer.push_str(&input_buffer);
|
||||
in_multiline = false;
|
||||
let result = multiline_buffer.clone();
|
||||
multiline_buffer.clear();
|
||||
tui.status("READY");
|
||||
result
|
||||
} else {
|
||||
input_buffer.clone()
|
||||
};
|
||||
|
||||
input_buffer.clear();
|
||||
|
||||
let input = final_input.trim().to_string();
|
||||
if input.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if input == "exit" || input == "quit" {
|
||||
tui.exit();
|
||||
break;
|
||||
}
|
||||
|
||||
// Execute the task
|
||||
tui.output(&format!("> {}", input));
|
||||
tui.status("PROCESSING");
|
||||
|
||||
match agent.execute_task_with_timing(&input, None, false, show_prompt, show_code, true).await {
|
||||
Ok(response) => {
|
||||
tui.output(&response);
|
||||
tui.status("READY");
|
||||
}
|
||||
Err(e) => {
|
||||
tui.error(&format!("Task execution failed: {}", e));
|
||||
tui.status("ERROR");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
KeyCode::Char(c) => {
|
||||
input_buffer.push(c);
|
||||
}
|
||||
KeyCode::Backspace => {
|
||||
input_buffer.pop();
|
||||
}
|
||||
KeyCode::Up => {
|
||||
tui.scroll_up();
|
||||
}
|
||||
KeyCode::Down => {
|
||||
tui.scroll_down();
|
||||
}
|
||||
KeyCode::PageUp => {
|
||||
tui.scroll_page_up();
|
||||
}
|
||||
KeyCode::PageDown => {
|
||||
tui.scroll_page_down();
|
||||
}
|
||||
KeyCode::Home => {
|
||||
tui.scroll_home();
|
||||
}
|
||||
KeyCode::End => {
|
||||
tui.scroll_end();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Small delay to prevent CPU spinning
|
||||
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||
}
|
||||
|
||||
tui.output("SYSTEM: SHUTDOWN INITIATED");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_interactive<W: UiWriter>(mut agent: Agent<W>, show_prompt: bool, show_code: bool) -> Result<()> {
|
||||
let output = SimpleOutput::new();
|
||||
|
||||
|
||||
448
crates/g3-cli/src/retro_tui.rs
Normal file
448
crates/g3-cli/src/retro_tui.rs
Normal file
@@ -0,0 +1,448 @@
|
||||
use anyhow::Result;
|
||||
use crossterm::{
|
||||
event::{DisableMouseCapture, EnableMouseCapture},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
use ratatui::{
|
||||
backend::CrosstermBackend,
|
||||
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||||
style::{Color, Modifier, Style},
|
||||
text::{Line, Span},
|
||||
widgets::{Block, Borders, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState, Wrap},
|
||||
Frame, Terminal,
|
||||
};
|
||||
use std::io;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
// Retro sci-fi color scheme inspired by Alien terminals
|
||||
const TERMINAL_GREEN: Color = Color::Rgb(0, 255, 65); // Bright phosphor green
|
||||
const TERMINAL_AMBER: Color = Color::Rgb(255, 176, 0); // Amber for warnings
|
||||
const TERMINAL_DIM_GREEN: Color = Color::Rgb(0, 128, 32); // Dimmed green for borders
|
||||
const TERMINAL_BG: Color = Color::Rgb(0, 10, 0); // Very dark green background
|
||||
const TERMINAL_CYAN: Color = Color::Rgb(0, 255, 255); // Cyan for highlights
|
||||
const TERMINAL_RED: Color = Color::Rgb(255, 0, 0); // Red for errors
|
||||
|
||||
/// Message types for communication between threads
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TuiMessage {
|
||||
AgentOutput(String),
|
||||
SystemStatus(String),
|
||||
ContextUpdate {
|
||||
used: u32,
|
||||
total: u32,
|
||||
percentage: f32,
|
||||
},
|
||||
Error(String),
|
||||
Exit,
|
||||
}
|
||||
|
||||
/// Shared state for the retro terminal
|
||||
struct TerminalState {
|
||||
/// Current input buffer
|
||||
input_buffer: String,
|
||||
/// Output history
|
||||
output_history: Vec<String>,
|
||||
/// Scroll position in output
|
||||
scroll_offset: usize,
|
||||
/// Cursor blink state
|
||||
cursor_blink: bool,
|
||||
/// Last cursor blink time
|
||||
last_blink: Instant,
|
||||
/// System status line
|
||||
status_line: String,
|
||||
/// Context window info
|
||||
context_info: (u32, u32, f32),
|
||||
/// Should exit
|
||||
should_exit: bool,
|
||||
}
|
||||
|
||||
impl TerminalState {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
input_buffer: String::new(),
|
||||
output_history: vec![
|
||||
"WEYLAND-YUTANI SYSTEMS".to_string(),
|
||||
"MU/TH/UR 6000 - INTERFACE 2.4.1".to_string(),
|
||||
"".to_string(),
|
||||
"SYSTEM INITIALIZED".to_string(),
|
||||
"AWAITING COMMAND...".to_string(),
|
||||
"".to_string(),
|
||||
],
|
||||
scroll_offset: 0,
|
||||
cursor_blink: true,
|
||||
last_blink: Instant::now(),
|
||||
status_line: "READY".to_string(),
|
||||
context_info: (0, 0, 0.0),
|
||||
should_exit: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add text to output history
|
||||
fn add_output(&mut self, text: &str) {
|
||||
// Split text by newlines and add each line
|
||||
for line in text.lines() {
|
||||
self.output_history.push(line.to_string());
|
||||
}
|
||||
// Auto-scroll to bottom
|
||||
self.scroll_offset = self.output_history.len().saturating_sub(1);
|
||||
}
|
||||
}
|
||||
|
||||
/// Public interface for the retro terminal
|
||||
#[derive(Clone)]
|
||||
pub struct RetroTui {
|
||||
tx: mpsc::UnboundedSender<TuiMessage>,
|
||||
state: Arc<Mutex<TerminalState>>,
|
||||
terminal: Arc<Mutex<Terminal<CrosstermBackend<io::Stdout>>>>,
|
||||
}
|
||||
|
||||
impl RetroTui {
|
||||
/// Create and start the retro terminal UI
|
||||
pub async fn start() -> Result<Self> {
|
||||
// Setup terminal
|
||||
enable_raw_mode()?;
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let terminal = Terminal::new(backend)?;
|
||||
|
||||
// Create message channel
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<TuiMessage>();
|
||||
|
||||
let state = Arc::new(Mutex::new(TerminalState::new()));
|
||||
let terminal = Arc::new(Mutex::new(terminal));
|
||||
|
||||
// Clone for the background task
|
||||
let state_clone = state.clone();
|
||||
let terminal_clone = terminal.clone();
|
||||
|
||||
// Spawn background task to handle messages and redraw
|
||||
tokio::spawn(async move {
|
||||
let mut last_draw = Instant::now();
|
||||
|
||||
loop {
|
||||
// Check for messages
|
||||
while let Ok(msg) = rx.try_recv() {
|
||||
let mut state = state_clone.lock().unwrap();
|
||||
match msg {
|
||||
TuiMessage::AgentOutput(text) => {
|
||||
state.add_output(&text);
|
||||
}
|
||||
TuiMessage::SystemStatus(status) => {
|
||||
state.status_line = status;
|
||||
}
|
||||
TuiMessage::ContextUpdate {
|
||||
used,
|
||||
total,
|
||||
percentage,
|
||||
} => {
|
||||
state.context_info = (used, total, percentage);
|
||||
}
|
||||
TuiMessage::Error(err) => {
|
||||
state.add_output(&format!("ERROR: {}", err));
|
||||
}
|
||||
TuiMessage::Exit => {
|
||||
state.should_exit = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we should exit
|
||||
if state_clone.lock().unwrap().should_exit {
|
||||
break;
|
||||
}
|
||||
|
||||
// Update cursor blink
|
||||
{
|
||||
let mut state = state_clone.lock().unwrap();
|
||||
if state.last_blink.elapsed() > Duration::from_millis(500) {
|
||||
state.cursor_blink = !state.cursor_blink;
|
||||
state.last_blink = Instant::now();
|
||||
}
|
||||
}
|
||||
|
||||
// Redraw at ~60fps
|
||||
if last_draw.elapsed() > Duration::from_millis(16) {
|
||||
let state = state_clone.lock().unwrap();
|
||||
let mut term = terminal_clone.lock().unwrap();
|
||||
let _ = Self::draw(&mut term, &state);
|
||||
last_draw = Instant::now();
|
||||
}
|
||||
|
||||
// Small sleep to prevent busy waiting
|
||||
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||
}
|
||||
});
|
||||
|
||||
// Initial draw
|
||||
{
|
||||
let state = state.lock().unwrap();
|
||||
let mut term = terminal.lock().unwrap();
|
||||
Self::draw(&mut term, &state)?;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
tx,
|
||||
state,
|
||||
terminal,
|
||||
})
|
||||
}
|
||||
|
||||
/// Draw the terminal UI
|
||||
fn draw(
|
||||
terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
|
||||
state: &TerminalState,
|
||||
) -> Result<()> {
|
||||
terminal.draw(|f| {
|
||||
let size = f.area();
|
||||
|
||||
// Create main layout - header, input, output
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([
|
||||
Constraint::Length(5), // Header/input area
|
||||
Constraint::Min(10), // Main output area
|
||||
Constraint::Length(1), // Status bar
|
||||
])
|
||||
.split(size);
|
||||
|
||||
// Draw header/input area
|
||||
Self::draw_input_area(f, chunks[0], &state.input_buffer, state.cursor_blink);
|
||||
|
||||
// Draw main output area
|
||||
Self::draw_output_area(f, chunks[1], &state.output_history, state.scroll_offset);
|
||||
|
||||
// Draw status bar
|
||||
Self::draw_status_bar(f, chunks[2], &state.status_line, state.context_info);
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Draw the input area with prompt
|
||||
fn draw_input_area(f: &mut Frame, area: Rect, input_buffer: &str, cursor_blink: bool) {
|
||||
// Show the actual input buffer content with prompt
|
||||
let input_text = if cursor_blink {
|
||||
format!("g3> {}█", input_buffer)
|
||||
} else {
|
||||
format!("g3> {} ", input_buffer)
|
||||
};
|
||||
|
||||
let input = Paragraph::new(input_text)
|
||||
.style(Style::default().fg(TERMINAL_GREEN))
|
||||
.block(
|
||||
Block::default()
|
||||
.title(" COMMAND INPUT ")
|
||||
.title_alignment(Alignment::Center)
|
||||
.borders(Borders::ALL)
|
||||
.border_style(Style::default().fg(TERMINAL_DIM_GREEN))
|
||||
.style(Style::default().bg(TERMINAL_BG)),
|
||||
);
|
||||
|
||||
f.render_widget(input, area);
|
||||
}
|
||||
|
||||
/// Draw the main output area
|
||||
fn draw_output_area(
|
||||
f: &mut Frame,
|
||||
area: Rect,
|
||||
output_history: &[String],
|
||||
scroll_offset: usize,
|
||||
) {
|
||||
// Calculate visible lines
|
||||
let visible_height = area.height.saturating_sub(2) as usize; // Account for borders
|
||||
let total_lines = output_history.len();
|
||||
|
||||
// Adjust scroll offset to ensure it's valid
|
||||
let max_scroll = total_lines.saturating_sub(visible_height);
|
||||
let scroll = scroll_offset.min(max_scroll);
|
||||
|
||||
// Get visible lines
|
||||
let visible_lines: Vec<Line> = output_history
|
||||
.iter()
|
||||
.skip(scroll)
|
||||
.take(visible_height)
|
||||
.map(|line| {
|
||||
// Apply different colors based on content
|
||||
let style = if line.starts_with("ERROR:") {
|
||||
Style::default()
|
||||
.fg(TERMINAL_RED)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else if line.starts_with('>') {
|
||||
Style::default().fg(TERMINAL_CYAN)
|
||||
} else if line.starts_with("SYSTEM:")
|
||||
|| line.starts_with("WEYLAND")
|
||||
|| line.starts_with("MU/TH/UR")
|
||||
{
|
||||
Style::default()
|
||||
.fg(TERMINAL_AMBER)
|
||||
.add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
Style::default().fg(TERMINAL_GREEN)
|
||||
};
|
||||
|
||||
Line::from(Span::styled(line.clone(), style))
|
||||
})
|
||||
.collect();
|
||||
|
||||
let output = Paragraph::new(visible_lines)
|
||||
.block(
|
||||
Block::default()
|
||||
.title(" SYSTEM OUTPUT ")
|
||||
.title_alignment(Alignment::Center)
|
||||
.borders(Borders::ALL)
|
||||
.border_style(Style::default().fg(TERMINAL_DIM_GREEN))
|
||||
.style(Style::default().bg(TERMINAL_BG)),
|
||||
)
|
||||
.wrap(Wrap { trim: false });
|
||||
|
||||
f.render_widget(output, area);
|
||||
|
||||
// Draw scrollbar if needed
|
||||
if total_lines > visible_height {
|
||||
let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight)
|
||||
.begin_symbol(Some("▲"))
|
||||
.end_symbol(Some("▼"))
|
||||
.track_symbol(Some("│"))
|
||||
.thumb_symbol("█")
|
||||
.style(Style::default().fg(TERMINAL_DIM_GREEN));
|
||||
|
||||
let mut scrollbar_state = ScrollbarState::new(total_lines)
|
||||
.position(scroll)
|
||||
.viewport_content_length(visible_height);
|
||||
|
||||
f.render_stateful_widget(
|
||||
scrollbar,
|
||||
area.inner(ratatui::layout::Margin {
|
||||
vertical: 1,
|
||||
horizontal: 0,
|
||||
}),
|
||||
&mut scrollbar_state,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Draw the status bar
|
||||
fn draw_status_bar(
|
||||
f: &mut Frame,
|
||||
area: Rect,
|
||||
status_line: &str,
|
||||
context_info: (u32, u32, f32),
|
||||
) {
|
||||
let (used, total, percentage) = context_info;
|
||||
|
||||
// Create context meter
|
||||
let bar_width = 10;
|
||||
let filled = ((percentage / 100.0) * bar_width as f32) as usize;
|
||||
let meter = format!("[{}{}]", "█".repeat(filled), "░".repeat(bar_width - filled));
|
||||
|
||||
let status_text = format!(
|
||||
" STATUS: {} | CONTEXT: {} {:.1}% ({}/{} tokens) | ↑↓ SCROLL | CTRL-C EXIT ",
|
||||
status_line, meter, percentage, used, total
|
||||
);
|
||||
|
||||
let status = Paragraph::new(status_text)
|
||||
.style(
|
||||
Style::default()
|
||||
.fg(TERMINAL_AMBER)
|
||||
.bg(TERMINAL_BG)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)
|
||||
.alignment(Alignment::Left);
|
||||
|
||||
f.render_widget(status, area);
|
||||
}
|
||||
|
||||
/// Send output to the terminal
|
||||
pub fn output(&self, text: &str) {
|
||||
let _ = self.tx.send(TuiMessage::AgentOutput(text.to_string()));
|
||||
}
|
||||
|
||||
/// Update system status
|
||||
pub fn status(&self, status: &str) {
|
||||
let _ = self.tx.send(TuiMessage::SystemStatus(status.to_string()));
|
||||
}
|
||||
|
||||
/// Update context window information
|
||||
pub fn update_context(&self, used: u32, total: u32, percentage: f32) {
|
||||
let _ = self.tx.send(TuiMessage::ContextUpdate {
|
||||
used,
|
||||
total,
|
||||
percentage,
|
||||
});
|
||||
}
|
||||
|
||||
/// Send error message
|
||||
pub fn error(&self, error: &str) {
|
||||
let _ = self.tx.send(TuiMessage::Error(error.to_string()));
|
||||
}
|
||||
|
||||
/// Signal exit
|
||||
pub fn exit(&self) {
|
||||
let _ = self.tx.send(TuiMessage::Exit);
|
||||
}
|
||||
|
||||
/// Update input buffer (for display)
|
||||
pub fn update_input(&self, input: &str) {
|
||||
if let Ok(mut state) = self.state.lock() {
|
||||
state.input_buffer = input.to_string();
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle scrolling
|
||||
pub fn scroll_up(&self) {
|
||||
if let Ok(mut state) = self.state.lock() {
|
||||
if state.scroll_offset > 0 {
|
||||
state.scroll_offset -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scroll_down(&self) {
|
||||
if let Ok(mut state) = self.state.lock() {
|
||||
state.scroll_offset += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scroll_page_up(&self) {
|
||||
if let Ok(mut state) = self.state.lock() {
|
||||
state.scroll_offset = state.scroll_offset.saturating_sub(10);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scroll_page_down(&self) {
|
||||
if let Ok(mut state) = self.state.lock() {
|
||||
state.scroll_offset += 10;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scroll_home(&self) {
|
||||
if let Ok(mut state) = self.state.lock() {
|
||||
state.scroll_offset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scroll_end(&self) {
|
||||
if let Ok(mut state) = self.state.lock() {
|
||||
state.scroll_offset = state.output_history.len().saturating_sub(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for RetroTui {
|
||||
fn drop(&mut self) {
|
||||
// Restore terminal
|
||||
let _ = disable_raw_mode();
|
||||
if let Ok(mut term) = self.terminal.lock() {
|
||||
let _ = execute!(
|
||||
term.backend_mut(),
|
||||
LeaveAlternateScreen,
|
||||
DisableMouseCapture
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
use g3_core::ui_writer::UiWriter;
|
||||
use std::io::{self, Write};
|
||||
use crate::retro_tui::RetroTui;
|
||||
|
||||
/// Console implementation of UiWriter that prints to stdout
|
||||
pub struct ConsoleUiWriter;
|
||||
@@ -79,3 +80,84 @@ impl UiWriter for ConsoleUiWriter {
|
||||
let _ = io::stdout().flush();
|
||||
}
|
||||
}
|
||||
|
||||
/// RetroTui implementation of UiWriter that sends output to the TUI
|
||||
pub struct RetroTuiWriter {
|
||||
tui: RetroTui,
|
||||
}
|
||||
|
||||
impl RetroTuiWriter {
|
||||
pub fn new(tui: RetroTui) -> Self {
|
||||
Self { tui }
|
||||
}
|
||||
}
|
||||
|
||||
impl UiWriter for RetroTuiWriter {
|
||||
fn print(&self, message: &str) {
|
||||
self.tui.output(message);
|
||||
}
|
||||
|
||||
fn println(&self, message: &str) {
|
||||
self.tui.output(message);
|
||||
}
|
||||
|
||||
fn print_inline(&self, message: &str) {
|
||||
// For inline printing, we'll just append to the output
|
||||
self.tui.output(message);
|
||||
}
|
||||
|
||||
fn print_system_prompt(&self, prompt: &str) {
|
||||
self.tui.output("🔍 System Prompt:");
|
||||
self.tui.output("================");
|
||||
for line in prompt.lines() {
|
||||
self.tui.output(line);
|
||||
}
|
||||
self.tui.output("================");
|
||||
self.tui.output("");
|
||||
}
|
||||
|
||||
fn print_context_status(&self, message: &str) {
|
||||
self.tui.output(message);
|
||||
}
|
||||
|
||||
fn print_tool_header(&self, tool_name: &str) {
|
||||
self.tui.output(&format!("┌─ {}", tool_name));
|
||||
}
|
||||
|
||||
fn print_tool_arg(&self, key: &str, value: &str) {
|
||||
self.tui.output(&format!("│ {}: {}", key, value));
|
||||
}
|
||||
|
||||
fn print_tool_output_header(&self) {
|
||||
self.tui.output("├─ output:");
|
||||
}
|
||||
|
||||
fn print_tool_output_line(&self, line: &str) {
|
||||
self.tui.output(&format!("│ {}", line));
|
||||
}
|
||||
|
||||
fn print_tool_output_summary(&self, hidden_count: usize) {
|
||||
self.tui.output(&format!(
|
||||
"│ ... ({} more line{} hidden)",
|
||||
hidden_count,
|
||||
if hidden_count == 1 { "" } else { "s" }
|
||||
));
|
||||
}
|
||||
|
||||
fn print_tool_timing(&self, duration_str: &str) {
|
||||
self.tui.output(&format!("└─ ⚡️ {}", duration_str));
|
||||
self.tui.output("");
|
||||
}
|
||||
|
||||
fn print_agent_prompt(&self) {
|
||||
self.tui.output("🤖 ");
|
||||
}
|
||||
|
||||
fn print_agent_response(&self, content: &str) {
|
||||
self.tui.output(content);
|
||||
}
|
||||
|
||||
fn flush(&self) {
|
||||
// No-op for TUI since it handles its own rendering
|
||||
}
|
||||
}
|
||||
@@ -320,7 +320,9 @@ impl OAuthFlow {
|
||||
|
||||
// Open the browser which will redirect with the code to the server
|
||||
let authorization_url = self.get_authorization_url();
|
||||
println!("🔐 Opening browser for Databricks authentication...");
|
||||
if std::env::var("G3_RETRO_MODE").is_err() {
|
||||
println!("🔐 Opening browser for Databricks authentication...");
|
||||
}
|
||||
if webbrowser::open(&authorization_url).is_err() {
|
||||
println!(
|
||||
"Please open this URL in your browser:\n{}",
|
||||
@@ -339,7 +341,9 @@ impl OAuthFlow {
|
||||
// Stop the server
|
||||
server_handle.abort();
|
||||
|
||||
println!("✅ Authentication successful! Exchanging code for token...");
|
||||
if std::env::var("G3_RETRO_MODE").is_err() {
|
||||
println!("✅ Authentication successful! Exchanging code for token...");
|
||||
}
|
||||
|
||||
// Exchange the code for a token
|
||||
self.exchange_code_for_token(&code).await
|
||||
@@ -422,7 +426,9 @@ pub async fn get_oauth_token_async(
|
||||
|
||||
// Cache and return
|
||||
token_cache.save_token(&token)?;
|
||||
println!("🎉 Databricks authentication complete!");
|
||||
if std::env::var("G3_RETRO_MODE").is_err() {
|
||||
println!("🎉 Databricks authentication complete!");
|
||||
}
|
||||
Ok(token.access_token)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user