prompthive 0.2.8

Open source prompt manager for developers. Terminal-native, sub-15ms operations, works with any AI tool.
Documentation
use anyhow::Result;
use std::io::Write;
use std::process::Command;
use std::time::Instant;

use crate::Storage;

pub fn handle_web(
    storage: &Storage,
    page: Option<&str>,
    _port: u16,
    no_browser: bool,
    start: Instant,
) -> Result<()> {
    // Generate HTML dashboard
    let html_content = generate_dashboard_html(storage, page)?;

    // Create temporary HTML file
    let temp_dir = std::env::temp_dir();
    let html_file = temp_dir.join("prompthive-dashboard.html");
    let mut file = std::fs::File::create(&html_file)?;
    file.write_all(html_content.as_bytes())?;
    file.flush()?;

    println!("✓ Generated dashboard at: {}", html_file.display());

    // Open in browser unless --no-browser flag is set
    if !no_browser {
        let url = format!("file://{}", html_file.display());
        println!("🌐 Opening dashboard in browser...");

        #[cfg(target_os = "macos")]
        let browser_result = Command::new("open").arg(&url).status();

        #[cfg(target_os = "linux")]
        let browser_result = Command::new("xdg-open").arg(&url).status();

        #[cfg(target_os = "windows")]
        let browser_result = Command::new("start").arg(&url).status();

        match browser_result {
            Ok(_) => println!("✓ Dashboard opened successfully"),
            Err(e) => {
                println!("⚠️  Could not open browser automatically: {}", e);
                println!("📄 Open this file manually: {}", html_file.display());
            }
        }
    } else {
        println!("📄 Dashboard file: {}", html_file.display());
    }

    println!(
        "⏱️  Dashboard generated ({}ms)",
        start.elapsed().as_millis()
    );
    Ok(())
}

fn generate_dashboard_html(storage: &Storage, page: Option<&str>) -> Result<String> {
    let prompts = storage.list_prompts()?;
    let prompt_count = prompts.len();

    let page_title = match page {
        Some("stats") => "Statistics",
        Some("prompts") => "Prompts",
        _ => "Overview",
    };

    let content = match page {
        Some("stats") => format!(
            "<h2>📈 Statistics</h2><p>Total prompts: {}</p>",
            prompt_count
        ),
        Some("prompts") => generate_prompts_html(&prompts)?,
        _ => format!(
            "<h2>📊 Overview</h2><p>PromptHive Dashboard</p><p>Total prompts: {}</p>",
            prompt_count
        ),
    };

    let mut html = String::new();
    html.push_str("<!DOCTYPE html>\n");
    html.push_str("<html lang=\"en\">\n");
    html.push_str("<head>\n");
    html.push_str("    <meta charset=\"UTF-8\">\n");
    html.push_str(
        "    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n",
    );
    html.push_str(&format!(
        "    <title>PromptHive Dashboard - {}</title>\n",
        page_title
    ));
    html.push_str("    <style>\n");
    html.push_str(
        "        body { font-family: Arial, sans-serif; margin: 40px; background: #f5f5f5; }\n",
    );
    html.push_str("        .container { max-width: 1200px; margin: 0 auto; background: white; padding: 30px; border-radius: 10px; }\n");
    html.push_str("        .nav { margin-bottom: 30px; }\n");
    html.push_str(
        "        .nav a { margin-right: 20px; text-decoration: none; color: #0066cc; }\n",
    );
    html.push_str("        .nav a.active { font-weight: bold; }\n");
    html.push_str("        h1 { color: #333; }\n");
    html.push_str("        .prompt-item { background: #f9f9f9; padding: 15px; margin: 10px 0; border-radius: 5px; }\n");
    html.push_str("    </style>\n");
    html.push_str("</head>\n");
    html.push_str("<body>\n");
    html.push_str("    <div class=\"container\">\n");
    html.push_str("        <h1>🚀 PromptHive Dashboard</h1>\n");
    html.push_str("        <div class=\"nav\">\n");

    // Navigation links
    let overview_class = if page.is_none() { "active" } else { "" };
    let stats_class = if page == Some("stats") { "active" } else { "" };
    let prompts_class = if page == Some("prompts") {
        "active"
    } else {
        ""
    };

    html.push_str(&format!(
        "            <a href=\"#\" class=\"{}\">📊 Overview</a>\n",
        overview_class
    ));
    html.push_str(&format!(
        "            <a href=\"#\" class=\"{}\">📈 Statistics</a>\n",
        stats_class
    ));
    html.push_str(&format!(
        "            <a href=\"#\" class=\"{}\">📝 Prompts</a>\n",
        prompts_class
    ));

    html.push_str("        </div>\n");
    html.push_str("        <div>\n");
    html.push_str(&content);
    html.push_str("        </div>\n");
    html.push_str(
        "        <footer style=\"margin-top: 40px; text-align: center; color: #666;\">\n",
    );
    html.push_str("            <p>Generated by PromptHive • <a href=\"https://prompthive.sh\">prompthive.sh</a></p>\n");
    html.push_str("        </footer>\n");
    html.push_str("    </div>\n");
    html.push_str("</body>\n");
    html.push_str("</html>");

    Ok(html)
}

fn generate_prompts_html(prompts: &[String]) -> Result<String> {
    let mut html = String::new();
    html.push_str("<h2>📝 Your Prompts</h2>");

    if prompts.is_empty() {
        html.push_str("<p>No prompts found. Create your first prompt with: <code>ph new my-first-prompt</code></p>");
    } else {
        for prompt_name in prompts.iter().take(50) {
            html.push_str("<div class=\"prompt-item\">📄 ");
            html.push_str(prompt_name);
            html.push_str("</div>");
        }
    }

    Ok(html)
}