Skip to content
Open
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
2 changes: 1 addition & 1 deletion crates/forge_app/src/operation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2335,7 +2335,7 @@ mod tests {
let long_content = format!(
"{}{}",
"A".repeat(config.max_fetch_chars),
&truncated_content
truncated_content
);
let fixture = ToolOperation::NetFetch {
input: forge_domain::NetFetch {
Expand Down
2 changes: 1 addition & 1 deletion crates/forge_infra/src/mcp_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -617,7 +617,7 @@ fn resolve_http_templates(
let template_data = serde_json::json!({"env": env_vars});

// Resolve templates in headers
for (_, value) in http.headers.iter_mut() {
for value in http.headers.values_mut() {
// Try to render the template, but keep original value if it fails
if let Ok(resolved) = handlebars.render_template(value, &template_data) {
*value = resolved;
Expand Down
2 changes: 1 addition & 1 deletion crates/forge_main/src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1618,7 +1618,7 @@ impl<A: API + ConsoleWriter + 'static, F: Fn(ForgeConfig) -> A + Send + Sync> UI
// Show failed MCP servers
if !porcelain && !all_tools.mcp.get_failures().is_empty() {
self.writeln("MCP FAILURES\n".dimmed().bold())?;
for (_, error) in all_tools.mcp.get_failures().iter() {
for error in all_tools.mcp.get_failures().values() {
let error = style(error).red();
self.writeln(error)?;
}
Expand Down
118 changes: 118 additions & 0 deletions crates/forge_markdown_stream/examples/mermaid-streaming-demo.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
//! Mermaid streaming demo - shows top-down and left-right diagrams while
//! streaming. Run with: cargo run --example mermaid-streaming-demo

use std::time::Duration;
use std::{io, thread};

use forge_markdown_stream::StreamdownRenderer;

fn stream_chunks(renderer: &mut StreamdownRenderer<io::Stdout>, chunks: &[&str]) -> io::Result<()> {
for chunk in chunks {
renderer.push(chunk)?;
thread::sleep(Duration::from_millis(150));
}
Ok(())
}

fn main() -> io::Result<()> {
println!("\n{}", "═".repeat(70));
println!(" MERMAID STREAMING DEMO: Top-Down & Left-Right");
println!("{}", "═".repeat(70));

// ── Demo 1: Simple Top-Down (chunked streaming) ──
println!("\n{}", "─".repeat(70));
println!(" 1. Simple Top-Down Flowchart (streamed chunk by chunk)");
println!("{}", "─".repeat(70));
let chunks_td = [
"```mermaid\n",
"graph TD\n",
" A[Start] --> B{Is it working?}\n",
" B -->|Yes| C[Great!]\n",
" B -->|No| D[Fix it]\n",
" D --> B\n",
"```\n",
];
let mut r1 = StreamdownRenderer::new(io::stdout(), 80);
stream_chunks(&mut r1, &chunks_td)?;
r1.finish()?;
println!();

// ── Demo 2: Left-to-Right (chunked streaming) ──
println!("\n{}", "─".repeat(70));
println!(" 2. Left-to-Right Flowchart (streamed chunk by chunk)");
println!("{}", "─".repeat(70));
let chunks_lr = [
"```mermaid\n",
"graph LR\n",
" A[Input Data] --> B{Valid?}\n",
" B -->|Yes| C[Process]\n",
" B -->|No| D[Reject]\n",
" C --> E[Output]\n",
"```\n",
];
let mut r2 = StreamdownRenderer::new(io::stdout(), 80);
stream_chunks(&mut r2, &chunks_lr)?;
r2.finish()?;
println!();

// ── Demo 3: Sequence Diagram (chunked streaming) ──
println!("\n{}", "─".repeat(70));
println!(" 3. Sequence Diagram (streamed chunk by chunk)");
println!("{}", "─".repeat(70));
let chunks_seq = [
"```mermaid\n",
"sequenceDiagram\n",
" Alice->>Bob: Hello Bob\n",
" Bob-->>Alice: Hi Alice\n",
" Alice->>Bob: How are you?\n",
" Bob-->>Alice: Doing great!\n",
"```\n",
];
let mut r3 = StreamdownRenderer::new(io::stdout(), 80);
stream_chunks(&mut r3, &chunks_seq)?;
r3.finish()?;
println!();

// ── Demo 4: Both orientations side by side concept ──
println!("\n{}", "─".repeat(70));
println!(" 4. Nested decision tree (Top-Down)");
println!("{}", "─".repeat(70));
let chunks_nested = [
"```mermaid\n",
"graph TD\n",
" A[User Request] --> B{Authenticated?}\n",
" B -->|Yes| C{Authorized?}\n",
" B -->|No| D[Login Page]\n",
" C -->|Yes| E[Process Request]\n",
" C -->|No| F[Forbidden]\n",
" E --> G[Return Result]\n",
"```\n",
];
let mut r4 = StreamdownRenderer::new(io::stdout(), 80);
stream_chunks(&mut r4, &chunks_nested)?;
r4.finish()?;
println!();

// ── Demo 5: LR with shapes ──
println!("\n{}", "─".repeat(70));
println!(" 5. Left-to-Right with different node shapes");
println!("{}", "─".repeat(70));
let chunks_shapes = [
"```mermaid\n",
"graph LR\n",
" A[Rectangle] --> B(Round)\n",
" B --> C{Diamond}\n",
" C --> D([Stadium])\n",
"```\n",
];
let mut r5 = StreamdownRenderer::new(io::stdout(), 80);
stream_chunks(&mut r5, &chunks_shapes)?;
r5.finish()?;
println!();

println!("\n{}", "═".repeat(70));
println!(" Demo complete!");
println!("{}\n", "═".repeat(70));

Ok(())
}
96 changes: 96 additions & 0 deletions crates/forge_markdown_stream/examples/mermaid-test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
//! Mermaid streaming test - renders diagrams to terminal using tmux.
//! Run with: cargo run --example mermaid-test

use std::io;

use forge_markdown_stream::StreamdownRenderer;

fn main() -> io::Result<()> {
// Test 1: Simple flowchart TD
println!("{}", "─".repeat(60));
println!(" Flowchart TD: A --> B");
println!("{}", "─".repeat(60));
let mut r1 = StreamdownRenderer::new(io::stdout(), 80);
r1.push("```mermaid\n")?;
r1.push("graph TD\n")?;
r1.push(" A[Start] --> B[End]\n")?;
r1.push("```\n")?;
r1.finish()?;
println!();

// Test 2: Flowchart LR
println!("{}", "─".repeat(60));
println!(" Flowchart LR: A --> B --> C");
println!("{}", "─".repeat(60));
let mut r2 = StreamdownRenderer::new(io::stdout(), 80);
r2.push("```mermaid\n")?;
r2.push("graph LR\n")?;
r2.push(" A[Alpha] --> B[Beta]\n")?;
r2.push(" B[Beta] --> C[Gamma]\n")?;
r2.push("```\n")?;
r2.finish()?;
println!();

// Test 3: Sequence diagram
println!("{}", "─".repeat(60));
println!(" Sequence Diagram");
println!("{}", "─".repeat(60));
let mut r3 = StreamdownRenderer::new(io::stdout(), 80);
r3.push("```mermaid\n")?;
r3.push("sequenceDiagram\n")?;
r3.push(" Alice->>Bob: Hello Bob\n")?;
r3.push(" Bob-->>Alice: Hi Alice\n")?;
r3.push("```\n")?;
r3.finish()?;
println!();

// Test 4: Fallback to raw code for unsupported diagram types
println!("{}", "─".repeat(60));
println!(" Unsupported type (falls back to code):");
println!("{}", "─".repeat(60));
let mut r4 = StreamdownRenderer::new(io::stdout(), 80);
r4.push("```mermaid\n")?;
r4.push("classDiagram\n")?;
r4.push(" class Animal\n")?;
r4.push("```\n")?;
r4.finish()?;
println!();

// Test 5: Complex flowchart
println!("{}", "─".repeat(60));
println!(" Complex Flowchart TD");
println!("{}", "─".repeat(60));
let mut r5 = StreamdownRenderer::new(io::stdout(), 80);
r5.push("```mermaid\n")?;
r5.push("graph TD\n")?;
r5.push(" A[Start] --> B{Is it working?}\n")?;
r5.push(" B -->|Yes| C[Great!]\n")?;
r5.push(" B -->|No| D[Fix it]\n")?;
r5.push(" D --> B\n")?;
r5.push("```\n")?;
r5.finish()?;
println!();

// Test 6: Streaming chunk by chunk (simulating real LLM output)
println!("{}", "─".repeat(60));
println!(" Streaming chunk by chunk:");
println!("{}", "─".repeat(60));
let chunks = [
"```mermaid\n",
"graph LR\n",
" A[Input] --> B",
"[Process]\n",
" B --> C",
"[Output]\n",
"```\n",
];
let mut r6 = StreamdownRenderer::new(io::stdout(), 80);
for chunk in &chunks {
r6.push(chunk)?;
std::thread::sleep(std::time::Duration::from_millis(100));
}
r6.finish()?;
println!();

Ok(())
}
1 change: 1 addition & 0 deletions crates/forge_markdown_stream/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ mod code;
mod heading;
mod inline;
mod list;
mod mermaid;
mod renderer;
mod repair;
mod style;
Expand Down
Loading
Loading