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",
|
"bitflags 2.9.4",
|
||||||
"cexpr",
|
"cexpr",
|
||||||
"clang-sys",
|
"clang-sys",
|
||||||
"itertools",
|
"itertools 0.12.1",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"lazycell",
|
"lazycell",
|
||||||
"log",
|
"log",
|
||||||
@@ -279,6 +279,21 @@ version = "1.10.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
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]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.35"
|
version = "1.2.35"
|
||||||
@@ -408,6 +423,20 @@ dependencies = [
|
|||||||
"memchr",
|
"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]]
|
[[package]]
|
||||||
name = "config"
|
name = "config"
|
||||||
version = "0.14.1"
|
version = "0.14.1"
|
||||||
@@ -490,7 +519,7 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "980c2afde4af43d6a05c5be738f9eae595cff86dce1f38f88b95058a98c027f3"
|
checksum = "980c2afde4af43d6a05c5be738f9eae595cff86dce1f38f88b95058a98c027f3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossterm",
|
"crossterm 0.29.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -535,7 +564,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "51360853ebbeb3df20c76c82aecf43d387a62860f1a59ba65ab51f00eea85aad"
|
checksum = "51360853ebbeb3df20c76c82aecf43d387a62860f1a59ba65ab51f00eea85aad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crokey-proc_macros",
|
"crokey-proc_macros",
|
||||||
"crossterm",
|
"crossterm 0.29.0",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"serde",
|
"serde",
|
||||||
"strict",
|
"strict",
|
||||||
@@ -547,7 +576,7 @@ version = "1.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3bf1a727caeb5ee5e0a0826a97f205a9cf84ee964b0b48239fef5214a00ae439"
|
checksum = "3bf1a727caeb5ee5e0a0826a97f205a9cf84ee964b0b48239fef5214a00ae439"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossterm",
|
"crossterm 0.29.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"strict",
|
"strict",
|
||||||
@@ -610,6 +639,22 @@ version = "0.8.21"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
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]]
|
[[package]]
|
||||||
name = "crossterm"
|
name = "crossterm"
|
||||||
version = "0.29.0"
|
version = "0.29.0"
|
||||||
@@ -653,6 +698,41 @@ dependencies = [
|
|||||||
"typenum",
|
"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]]
|
[[package]]
|
||||||
name = "derive_more"
|
name = "derive_more"
|
||||||
version = "0.99.20"
|
version = "0.99.20"
|
||||||
@@ -846,6 +926,12 @@ version = "1.0.7"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "foldhash"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foreign-types"
|
name = "foreign-types"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@@ -975,11 +1061,12 @@ dependencies = [
|
|||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"crossterm",
|
"crossterm 0.29.0",
|
||||||
"dirs 5.0.1",
|
"dirs 5.0.1",
|
||||||
"g3-config",
|
"g3-config",
|
||||||
"g3-core",
|
"g3-core",
|
||||||
"indicatif",
|
"indicatif",
|
||||||
|
"ratatui",
|
||||||
"rustyline",
|
"rustyline",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -1147,6 +1234,11 @@ name = "hashbrown"
|
|||||||
version = "0.15.5"
|
version = "0.15.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||||
|
dependencies = [
|
||||||
|
"allocator-api2",
|
||||||
|
"equivalent",
|
||||||
|
"foldhash",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashlink"
|
name = "hashlink"
|
||||||
@@ -1430,6 +1522,12 @@ dependencies = [
|
|||||||
"zerovec",
|
"zerovec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ident_case"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
@@ -1474,6 +1572,25 @@ dependencies = [
|
|||||||
"web-time",
|
"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]]
|
[[package]]
|
||||||
name = "io-uring"
|
name = "io-uring"
|
||||||
version = "0.7.10"
|
version = "0.7.10"
|
||||||
@@ -1506,6 +1623,15 @@ dependencies = [
|
|||||||
"either",
|
"either",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.15"
|
version = "1.0.15"
|
||||||
@@ -1702,6 +1828,15 @@ version = "0.4.28"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
|
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]]
|
[[package]]
|
||||||
name = "matchers"
|
name = "matchers"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -1991,6 +2126,12 @@ dependencies = [
|
|||||||
"windows-targets 0.52.6",
|
"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]]
|
[[package]]
|
||||||
name = "pathdiff"
|
name = "pathdiff"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
@@ -2163,6 +2304,27 @@ dependencies = [
|
|||||||
"getrandom 0.2.16",
|
"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]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.5.17"
|
version = "0.5.17"
|
||||||
@@ -2607,6 +2769,12 @@ version = "1.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "static_assertions"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strict"
|
name = "strict"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -2619,6 +2787,28 @@ version = "0.11.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
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]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.106"
|
version = "2.0.106"
|
||||||
@@ -2997,6 +3187,17 @@ version = "1.12.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
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]]
|
[[package]]
|
||||||
name = "unicode-width"
|
name = "unicode-width"
|
||||||
version = "0.1.14"
|
version = "0.1.14"
|
||||||
|
|||||||
@@ -20,4 +20,5 @@ tokio-util = "0.7"
|
|||||||
indicatif = "0.17"
|
indicatif = "0.17"
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
crossterm = "0.29.0"
|
crossterm = "0.29.0"
|
||||||
|
ratatui = "0.29"
|
||||||
termimad = "0.34.0"
|
termimad = "0.34.0"
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ use tracing::{error, info};
|
|||||||
|
|
||||||
mod tui;
|
mod tui;
|
||||||
mod ui_writer_impl;
|
mod ui_writer_impl;
|
||||||
|
mod retro_tui;
|
||||||
use tui::SimpleOutput;
|
use tui::SimpleOutput;
|
||||||
use ui_writer_impl::ConsoleUiWriter;
|
use ui_writer_impl::{ConsoleUiWriter, RetroTuiWriter};
|
||||||
|
use retro_tui::RetroTui;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(name = "g3")]
|
#[command(name = "g3")]
|
||||||
@@ -48,39 +50,60 @@ pub struct Cli {
|
|||||||
/// Maximum number of turns in autonomous mode (default: 5)
|
/// Maximum number of turns in autonomous mode (default: 5)
|
||||||
#[arg(long, default_value = "5")]
|
#[arg(long, default_value = "5")]
|
||||||
pub max_turns: usize,
|
pub max_turns: usize,
|
||||||
|
|
||||||
|
/// Use retro terminal UI (inspired by 80s sci-fi)
|
||||||
|
#[arg(long)]
|
||||||
|
pub retro: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run() -> Result<()> {
|
pub async fn run() -> Result<()> {
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
|
|
||||||
// Initialize logging with filtering
|
// Only initialize logging if not in retro mode
|
||||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
|
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
|
// Create a filter that suppresses llama_cpp logs unless in verbose mode
|
||||||
let filter = if cli.verbose {
|
let filter = if cli.verbose {
|
||||||
EnvFilter::from_default_env()
|
EnvFilter::from_default_env()
|
||||||
.add_directive(format!("{}=debug", env!("CARGO_PKG_NAME")).parse().unwrap())
|
.add_directive(format!("{}=debug", env!("CARGO_PKG_NAME")).parse().unwrap())
|
||||||
.add_directive("g3_core=debug".parse().unwrap())
|
.add_directive("g3_core=debug".parse().unwrap())
|
||||||
.add_directive("g3_cli=debug".parse().unwrap())
|
.add_directive("g3_cli=debug".parse().unwrap())
|
||||||
.add_directive("g3_execution=debug".parse().unwrap())
|
.add_directive("g3_execution=debug".parse().unwrap())
|
||||||
.add_directive("g3_providers=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 {
|
} else {
|
||||||
EnvFilter::from_default_env()
|
// In retro mode, we don't want any logging output to interfere with the TUI
|
||||||
.add_directive(format!("{}=info", env!("CARGO_PKG_NAME")).parse().unwrap())
|
// We'll use a no-op subscriber
|
||||||
.add_directive("g3_core=info".parse().unwrap())
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
|
||||||
.add_directive("g3_cli=info".parse().unwrap())
|
|
||||||
.add_directive("g3_execution=info".parse().unwrap())
|
// Create a filter that suppresses ALL logs in retro mode
|
||||||
.add_directive("g3_providers=info".parse().unwrap())
|
let filter = EnvFilter::from_default_env()
|
||||||
.add_directive("llama_cpp=off".parse().unwrap()) // Suppress all llama_cpp logs
|
.add_directive("off".parse().unwrap()); // Turn off all logging
|
||||||
.add_directive("llama=off".parse().unwrap()) // Suppress all llama.cpp logs
|
|
||||||
};
|
tracing_subscriber::registry()
|
||||||
|
.with(filter)
|
||||||
|
.init();
|
||||||
|
}
|
||||||
|
|
||||||
tracing_subscriber::registry()
|
if !cli.retro {
|
||||||
.with(tracing_subscriber::fmt::layer())
|
info!("Starting G3 AI Coding Agent");
|
||||||
.with(filter)
|
}
|
||||||
.init();
|
|
||||||
|
|
||||||
info!("Starting G3 AI Coding Agent");
|
|
||||||
|
|
||||||
// Set up workspace directory
|
// Set up workspace directory
|
||||||
let workspace_dir = if let Some(ws) = cli.workspace {
|
let workspace_dir = if let Some(ws) = cli.workspace {
|
||||||
@@ -104,19 +127,23 @@ pub async fn run() -> Result<()> {
|
|||||||
project.ensure_workspace_exists()?;
|
project.ensure_workspace_exists()?;
|
||||||
project.enter_workspace()?;
|
project.enter_workspace()?;
|
||||||
|
|
||||||
info!("Using workspace: {}", project.workspace().display());
|
if !cli.retro {
|
||||||
|
info!("Using workspace: {}", project.workspace().display());
|
||||||
|
}
|
||||||
|
|
||||||
// Load configuration
|
// Load configuration
|
||||||
let config = Config::load(cli.config.as_deref())?;
|
let config = Config::load(cli.config.as_deref())?;
|
||||||
|
|
||||||
// Initialize agent
|
// Initialize agent
|
||||||
let ui_writer = ConsoleUiWriter::new();
|
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
|
// Execute task, autonomous mode, or start interactive mode
|
||||||
if cli.autonomous {
|
if cli.autonomous {
|
||||||
// Autonomous mode with coach-player feedback loop
|
// Autonomous mode with coach-player feedback loop
|
||||||
info!("Starting autonomous mode");
|
if !cli.retro {
|
||||||
|
info!("Starting autonomous mode");
|
||||||
|
}
|
||||||
run_autonomous(
|
run_autonomous(
|
||||||
agent,
|
agent,
|
||||||
project,
|
project,
|
||||||
@@ -127,23 +154,187 @@ pub async fn run() -> Result<()> {
|
|||||||
.await?;
|
.await?;
|
||||||
} else if let Some(task) = cli.task {
|
} else if let Some(task) = cli.task {
|
||||||
// Single-shot mode
|
// Single-shot mode
|
||||||
info!("Executing task: {}", task);
|
if !cli.retro {
|
||||||
|
info!("Executing task: {}", task);
|
||||||
|
}
|
||||||
let output = SimpleOutput::new();
|
let output = SimpleOutput::new();
|
||||||
let result = agent
|
let result = agent
|
||||||
.execute_task_with_timing(&task, None, false, cli.show_prompt, cli.show_code, true)
|
.execute_task_with_timing(&task, None, false, cli.show_prompt, cli.show_code, true)
|
||||||
.await?;
|
.await?;
|
||||||
output.print_markdown(&result);
|
output.print_markdown(&result);
|
||||||
} else {
|
} else {
|
||||||
let output = SimpleOutput::new();
|
|
||||||
// Interactive mode (default)
|
// Interactive mode (default)
|
||||||
info!("Starting interactive mode");
|
if !cli.retro {
|
||||||
output.print(&format!("📁 Workspace: {}", project.workspace().display()));
|
info!("Starting interactive mode");
|
||||||
run_interactive(agent, cli.show_prompt, cli.show_code).await?;
|
}
|
||||||
|
|
||||||
|
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(())
|
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<()> {
|
async fn run_interactive<W: UiWriter>(mut agent: Agent<W>, show_prompt: bool, show_code: bool) -> Result<()> {
|
||||||
let output = SimpleOutput::new();
|
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 g3_core::ui_writer::UiWriter;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
|
use crate::retro_tui::RetroTui;
|
||||||
|
|
||||||
/// Console implementation of UiWriter that prints to stdout
|
/// Console implementation of UiWriter that prints to stdout
|
||||||
pub struct ConsoleUiWriter;
|
pub struct ConsoleUiWriter;
|
||||||
@@ -78,4 +79,85 @@ impl UiWriter for ConsoleUiWriter {
|
|||||||
fn flush(&self) {
|
fn flush(&self) {
|
||||||
let _ = io::stdout().flush();
|
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
|
// Open the browser which will redirect with the code to the server
|
||||||
let authorization_url = self.get_authorization_url();
|
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() {
|
if webbrowser::open(&authorization_url).is_err() {
|
||||||
println!(
|
println!(
|
||||||
"Please open this URL in your browser:\n{}",
|
"Please open this URL in your browser:\n{}",
|
||||||
@@ -339,7 +341,9 @@ impl OAuthFlow {
|
|||||||
// Stop the server
|
// Stop the server
|
||||||
server_handle.abort();
|
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
|
// Exchange the code for a token
|
||||||
self.exchange_code_for_token(&code).await
|
self.exchange_code_for_token(&code).await
|
||||||
@@ -422,7 +426,9 @@ pub async fn get_oauth_token_async(
|
|||||||
|
|
||||||
// Cache and return
|
// Cache and return
|
||||||
token_cache.save_token(&token)?;
|
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)
|
Ok(token.access_token)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user