Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/rocm/src/bootstrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ pub(crate) fn run(command: Option<BootstrapCommand>) -> Result<()> {

fn run_setup() -> Result<()> {
if interactive_terminal() {
crate::tui::run_bootstrap_setup()
crate::dash::run_bootstrap()
} else {
println!(
"ROCm setup needs an interactive terminal. Run `rocm` from a terminal to choose an install folder and set up ROCm/TheRock."
"ROCm setup needs an interactive terminal. Run `rocm bootstrap setup` from a terminal to set up ROCm/TheRock."
);
Ok(())
}
Expand Down
186 changes: 0 additions & 186 deletions apps/rocm/src/comfyui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,117 +199,21 @@ pub(crate) fn render_status(paths: &AppPaths, config: &RocmCliConfig) -> Result<
Ok(output)
}

pub(crate) fn render_tui_status(paths: &AppPaths, config: &RocmCliConfig) -> Result<String> {
let mut output = String::new();
writeln!(output, "{APP_NAME}")?;
writeln!(output)?;
if let Some(manifest) = load_manifest(paths)? {
writeln!(output, "Installed")?;
writeln!(output, " status: ready")?;
writeln!(
output,
" ROCm install: {}",
therock::runtime_version_display(&manifest.runtime_version)
)?;
writeln!(
output,
" models path: {}",
models_folder_for_manifest(&manifest).display()
)?;
writeln!(
output,
" AMD GPU check: {}",
if manifest.torch_cuda_available {
"ready"
} else {
"needs attention"
}
)?;
} else {
writeln!(output, "Not installed yet")?;
writeln!(output, " Choose Install ComfyUI below.")?;
}

writeln!(output)?;
if let Some(state) = load_state(paths)? {
let run_report = evaluate_running_state(&state);
writeln!(output, "Running")?;
match run_report.state {
ComfyUiRunState::Running => {
writeln!(output, " status: running")?;
writeln!(output, " URL: {}", state.url)?;
}
ComfyUiRunState::Starting => {
writeln!(output, " status: starting")?;
writeln!(output, " URL: {}", state.url)?;
writeln!(output, " Waiting for the browser page to answer.")?;
}
ComfyUiRunState::Stopped => {
writeln!(output, " status: stopped")?;
writeln!(output, " Choose Start ComfyUI below to run it again.")?;
}
}
} else {
writeln!(output, "Running")?;
if let Some(url) = default_unmanaged_running_url() {
writeln!(output, " status: running outside rocm-cli")?;
writeln!(output, " URL: {url}")?;
writeln!(output, " ROCm CLI did not start this ComfyUI process.")?;
} else {
writeln!(output, " status: not started")?;
}
}

if config.active_runtime_key.is_none() && load_manifest(paths)?.is_none() {
writeln!(output)?;
writeln!(output, "ROCm")?;
writeln!(output, " Install ROCm first from Set Up ROCm.")?;
}
Ok(output)
}

pub(crate) fn render_logs(paths: &AppPaths, line_limit: usize) -> Result<String> {
render_logs_with_options(paths, line_limit, true)
}

pub(crate) fn render_tui_logs(
paths: &AppPaths,
line_limit: usize,
show_file_locations: bool,
) -> Result<String> {
render_logs_with_options(paths, line_limit, show_file_locations)
}

pub(crate) fn models_folder(paths: &AppPaths) -> Result<Option<PathBuf>> {
Ok(load_manifest(paths)?.map(|manifest| models_folder_for_manifest(&manifest)))
}

pub(crate) fn is_installed(paths: &AppPaths) -> Result<bool> {
Ok(load_manifest(paths)?.is_some())
}

pub(crate) fn render_models_path(paths: &AppPaths) -> Result<String> {
let Some(path) = models_folder(paths)? else {
bail!("ComfyUI is not installed yet. Run `rocm comfyui install` first.");
};
Ok(format!("{}\n", path.display()))
}

pub(crate) fn running_url(paths: &AppPaths) -> Result<Option<String>> {
let Some(state) = load_state(paths)? else {
return Ok(default_unmanaged_running_url());
};
let report = evaluate_running_state(&state);
if matches!(
report.state,
ComfyUiRunState::Running | ComfyUiRunState::Starting
) {
Ok(Some(state.url))
} else {
Ok(None)
}
}

fn default_unmanaged_running_url() -> Option<String> {
unmanaged_running_url(COMFYUI_DEFAULT_HOST, COMFYUI_DEFAULT_PORT)
}
Expand Down Expand Up @@ -1980,96 +1884,6 @@ mod tests {
Ok(())
}

#[test]
fn tui_status_reports_stale_saved_state_plainly() -> Result<()> {
let paths = test_paths("comfyui-tui-stale-state");
let logs = app_root(&paths).join("logs");
fs::create_dir_all(&logs)?;
let port = unused_local_port()?;
save_state(
&paths,
&ComfyUiState {
app_id: APP_ID.to_owned(),
url: format!("http://127.0.0.1:{port}"),
host: "127.0.0.1".to_owned(),
port,
pid: 0,
source_path: source_path(&paths),
python_executable: paths.data_dir.join("runtimes").join("python.exe"),
log_path: logs.join("start-200.log"),
started_at_unix_ms: 200,
},
)?;

let rendered = render_tui_status(&paths, &RocmCliConfig::default())?;

assert!(rendered.contains("status: stopped"));
assert!(rendered.contains("Choose Start ComfyUI below"));
assert!(!rendered.contains("starting or running"));
Ok(())
}

#[test]
fn tui_status_hides_technical_file_paths() -> Result<()> {
let paths = test_paths("comfyui-tui-status");
let logs = app_root(&paths).join("logs");
fs::create_dir_all(&logs)?;
let install_log = logs.join("install-100.log");
fs::write(&install_log, "install output\n")?;
save_manifest(
&paths,
&ComfyUiManifest {
app_id: APP_ID.to_owned(),
runtime_key: "therock-release:gfx120X-all".to_owned(),
runtime_id: "therock-release".to_owned(),
runtime_version: "7.13.0a20260511".to_owned(),
runtime_root: paths.data_dir.join("runtimes").join("runtime"),
python_executable: paths.data_dir.join("runtimes").join("python.exe"),
source_url: COMFYUI_SOURCE_ARCHIVE_URL.to_owned(),
source_path: source_path(&paths),
requirements_path: source_path(&paths).join("requirements.txt"),
pip_cache_dir: None,
log_path: install_log.clone(),
torch_version: Some("2.10.0".to_owned()),
torch_cuda_available: true,
installed_at_unix_ms: 100,
},
)?;

let rendered = render_tui_status(&paths, &RocmCliConfig::default())?;

assert!(rendered.contains("Installed"));
assert!(rendered.contains("AMD GPU check: ready"));
assert!(!rendered.contains("Use the rows on the left"));
assert!(!rendered.contains("python"));
assert!(!rendered.contains("torch"));
assert!(!rendered.contains("saved file:"));
assert!(!rendered.contains(&install_log.display().to_string()));
Ok(())
}

#[test]
fn tui_logs_hide_file_paths_until_requested() -> Result<()> {
let paths = test_paths("comfyui-tui-logs");
let logs = app_root(&paths).join("logs");
fs::create_dir_all(&logs)?;
let install_log = logs.join("install-100.log");
fs::write(&install_log, "downloaded ComfyUI\ninstalled packages\n")?;

let friendly = render_tui_logs(&paths, 10, false)?;
assert!(friendly.contains("ComfyUI logs"));
assert!(friendly.contains("Install log"));
assert!(friendly.contains("downloaded ComfyUI"));
assert!(friendly.contains("installed packages"));
assert!(!friendly.contains("saved file:"));
assert!(!friendly.contains(&install_log.display().to_string()));

let with_files = render_tui_logs(&paths, 10, true)?;
assert!(with_files.contains("saved file:"));
assert!(with_files.contains(&install_log.display().to_string()));
Ok(())
}

#[test]
fn default_runtime_selection_uses_single_ready_runtime() -> Result<()> {
let paths = test_paths("comfyui-single-ready-runtime");
Expand Down
45 changes: 43 additions & 2 deletions apps/rocm/src/dash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
//! runs the ratatui dashboard TUI.
//!
//! The rest of `rocm` is synchronous; the async daemon/TUI run on a tokio
//! runtime built here. The two ratatui majors (0.29 in `tui.rs`, 0.30 in
//! `rocm-dash-tui`) coexist, each confined to its crate.
//! runtime built here. The TUI lives entirely in the `rocm-dash-tui` crate.

use std::path::PathBuf;
use std::time::Duration;
Expand Down Expand Up @@ -183,6 +182,7 @@ pub fn resolved_args(
theme: t.theme.clone(),
replay: None,
initial_tab,
start_onboarding: false,
chat_url: t.chat_url.clone(),
chat_model: t.chat_model.clone(),
chat_auth_header: t.chat_auth_header.clone(),
Expand Down Expand Up @@ -281,6 +281,26 @@ pub fn run_chat(chat_mock: bool) -> Result<()> {
rt.block_on(run_async(config, paths, args, None, chat_mock))
}

/// Entry point for `rocm bootstrap setup`. Opens the dashboard straight into the
/// first-run onboarding wizard (install ROCm SDK / adopt an existing folder).
/// Same runtime/`run_async` path as [`run`]; the keyring lookup in
/// `resolved_args` runs here on the synchronous thread before the runtime.
pub fn run_bootstrap() -> Result<()> {
let paths = AppPaths::discover()?;
let config = RocmCliConfig::load(&paths)?;
let args = bootstrap_args(&config, &paths);
let rt = build_dashboard_runtime()?;
rt.block_on(run_async(config, paths, args, None, false))
}

/// Resolve the dashboard args for `rocm bootstrap setup`: the standard
/// dashboard args with the first-run onboarding wizard opened on launch.
fn bootstrap_args(config: &RocmCliConfig, paths: &AppPaths) -> ResolvedArgs {
let mut args = resolved_args(config, paths, ActiveTab::Home);
args.start_onboarding = true;
args
}

async fn run_async(
config: RocmCliConfig,
paths: AppPaths,
Expand Down Expand Up @@ -395,6 +415,27 @@ mod tests {
assert!(!opts.enable_docker);
}

#[test]
fn bootstrap_args_open_the_onboarding_wizard() {
// `rocm bootstrap setup` must launch the dashboard straight into the
// first-run onboarding overlay (install ROCm SDK / adopt existing).
let args = bootstrap_args(&cfg(), &paths());
assert!(
args.start_onboarding,
"bootstrap must open the onboarding wizard on launch"
);
assert_eq!(args.initial_tab, ActiveTab::Home);
assert!(!args.chat_mock);
assert!(args.replay.is_none());
}

#[test]
fn resolved_args_default_does_not_open_onboarding() {
// Normal `rocm dash` / `rocm chat` must NOT auto-open onboarding.
let args = resolved_args(&cfg(), &paths(), ActiveTab::Home);
assert!(!args.start_onboarding);
}

#[test]
fn resolved_args_take_connect_and_theme_from_config() {
let c = cfg();
Expand Down
Loading
Loading