Skip to content

Commit f675b8d

Browse files
Merge pull request #1391 from MervinPraison/claude/phase2-3-langflow-converter-20260416-0646
feat: Phase 2+3 YAML ↔ Langflow Converter + Trace Correlation Integration
2 parents 0876a25 + 36c727f commit f675b8d

8 files changed

Lines changed: 1766 additions & 23 deletions

File tree

src/praisonai/praisonai/cli/app.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ def register_commands():
242242
from .commands.langfuse import app as langfuse_app
243243
from .commands.port import app as port_app
244244
from .commands.managed import app as managed_app
245+
from .commands.up import app as up_app
245246

246247
# Import TUI and queue commands
247248
from .features.tui.debug import create_debug_app as create_tui_debug_app
@@ -426,6 +427,7 @@ def app_cmd(
426427
app.add_typer(unified_app, name="dashboard", help="🌟 Unified Dashboard (Flow + Claw + UI)")
427428
app.add_typer(langfuse_app, name="langfuse", help="🔍 Langfuse observability platform")
428429
app.add_typer(port_app, name="port", help="🔌 Manage port usage and resolve conflicts")
430+
app.add_typer(up_app, name="up", help="🚀 Start unified PraisonAI stack (Langfuse + Langflow)")
429431

430432
# Register standardise command
431433
try:

src/praisonai/praisonai/cli/commands/flow.py

Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,292 @@ def flow_start(
126126
raise typer.Abort()
127127

128128

129+
@app.command("import")
130+
def flow_import(
131+
yaml_path: str = typer.Argument(..., help="Path to YAML workflow file"),
132+
langflow_url: str = typer.Option(
133+
"http://localhost:7860",
134+
"--url",
135+
help="Langflow server URL"
136+
),
137+
dry_run: bool = typer.Option(
138+
False,
139+
"--dry-run",
140+
help="Preview JSON without uploading"
141+
),
142+
open_browser: bool = typer.Option(
143+
False,
144+
"--open",
145+
help="Open imported flow in browser"
146+
),
147+
output: str = typer.Option(
148+
None,
149+
"--output",
150+
"-o",
151+
help="Save JSON to file instead of uploading"
152+
),
153+
):
154+
"""Import YAML workflow into Langflow.
155+
156+
Converts PraisonAI YAML workflow to Langflow JSON format and uploads
157+
to a running Langflow instance for visual editing.
158+
159+
Examples:
160+
praisonai flow import workflow.yaml
161+
praisonai flow import workflow.yaml --dry-run
162+
praisonai flow import workflow.yaml --output flow.json
163+
praisonai flow import workflow.yaml --url http://localhost:8080
164+
"""
165+
from pathlib import Path
166+
from rich.console import Console
167+
from rich.json import JSON
168+
169+
console = Console()
170+
171+
# Validate input file
172+
yaml_file = Path(yaml_path)
173+
if not yaml_file.exists():
174+
console.print(f"[red]Error: File not found: {yaml_path}[/red]")
175+
raise typer.Abort()
176+
177+
try:
178+
from praisonai.flow.converter import yaml_to_langflow_json
179+
180+
console.print(f"[cyan]Converting {yaml_path} to Langflow format...[/cyan]")
181+
182+
# Convert YAML to Langflow JSON
183+
langflow_json = yaml_to_langflow_json(str(yaml_file))
184+
185+
# Dry run: just show the JSON
186+
if dry_run:
187+
console.print("\n[bold green]✅ Conversion Preview[/bold green]")
188+
console.print(JSON.from_data(langflow_json, indent=2))
189+
return
190+
191+
# Save to file mode
192+
if output:
193+
output_path = Path(output)
194+
output_path.parent.mkdir(parents=True, exist_ok=True)
195+
196+
import json
197+
with open(output_path, 'w') as f:
198+
json.dump(langflow_json, f, indent=2)
199+
200+
console.print(f"[green]✅ Flow saved to {output_path}[/green]")
201+
return
202+
203+
# Upload to Langflow
204+
from praisonai.flow.client import create_client
205+
206+
console.print(f"[cyan]Connecting to Langflow at {langflow_url}...[/cyan]")
207+
client = create_client(langflow_url)
208+
209+
# Check server health
210+
health = client.health_check()
211+
if health["status"] != "healthy":
212+
console.print(f"[red]Error: Langflow server not accessible at {langflow_url}[/red]")
213+
console.print("[yellow]Make sure Langflow is running: praisonai flow[/yellow]")
214+
raise typer.Abort()
215+
216+
# Upload flow
217+
console.print("[cyan]Uploading flow to Langflow...[/cyan]")
218+
response = client.upload_flow(langflow_json)
219+
220+
flow_id = response.get("id", response.get("flow_id", ""))
221+
flow_name = langflow_json.get("name", "Imported Flow")
222+
223+
console.print(f"[green]✅ Flow '{flow_name}' imported successfully![/green]")
224+
console.print(f"[dim]Flow ID: {flow_id}[/dim]")
225+
226+
# Generate flow URL
227+
if flow_id:
228+
flow_url = f"{langflow_url}/flow/{flow_id}"
229+
console.print(f"[blue]View: {flow_url}[/blue]")
230+
231+
# Open browser if requested
232+
if open_browser:
233+
import webbrowser
234+
webbrowser.open(flow_url)
235+
console.print("[dim]Opening in browser...[/dim]")
236+
237+
except ImportError as e:
238+
console.print(f"[red]Error: {e}[/red]")
239+
console.print("[yellow]Install with: pip install 'praisonai[flow]'[/yellow]")
240+
raise typer.Abort()
241+
except Exception as e:
242+
console.print(f"[red]Error importing flow: {e}[/red]")
243+
raise typer.Abort()
244+
245+
246+
@app.command("export")
247+
def flow_export(
248+
flow_id: str = typer.Argument(..., help="Flow ID to export"),
249+
output: str = typer.Option(
250+
None,
251+
"--output",
252+
"-o",
253+
help="Output YAML file path (default: flow_id.yaml)"
254+
),
255+
langflow_url: str = typer.Option(
256+
"http://localhost:7860",
257+
"--url",
258+
help="Langflow server URL"
259+
),
260+
format: str = typer.Option(
261+
"yaml",
262+
"--format",
263+
help="Output format (yaml, json)",
264+
type=typer.Choice(["yaml", "json"])
265+
),
266+
):
267+
"""Export Langflow flow to YAML format.
268+
269+
Downloads a flow from Langflow and converts it back to PraisonAI
270+
YAML format for use with the CLI.
271+
272+
Examples:
273+
praisonai flow export abc-123-def
274+
praisonai flow export abc-123-def --output my_workflow.yaml
275+
praisonai flow export abc-123-def --format json
276+
"""
277+
from pathlib import Path
278+
from rich.console import Console
279+
280+
console = Console()
281+
282+
try:
283+
from praisonai.flow.client import create_client
284+
285+
console.print(f"[cyan]Connecting to Langflow at {langflow_url}...[/cyan]")
286+
client = create_client(langflow_url)
287+
288+
# Check server health
289+
health = client.health_check()
290+
if health["status"] != "healthy":
291+
console.print(f"[red]Error: Langflow server not accessible at {langflow_url}[/red]")
292+
raise typer.Abort()
293+
294+
# Download flow
295+
console.print(f"[cyan]Downloading flow {flow_id}...[/cyan]")
296+
flow_data = client.get_flow(flow_id)
297+
298+
# Determine output file
299+
if not output:
300+
flow_name = flow_data.get("name", flow_id)
301+
safe_name = "".join(c for c in flow_name if c.isalnum() or c in (' ', '-', '_')).rstrip()
302+
output = f"{safe_name}.{format}"
303+
304+
output_path = Path(output)
305+
output_path.parent.mkdir(parents=True, exist_ok=True)
306+
307+
if format == "json":
308+
# Save as JSON
309+
import json
310+
with open(output_path, 'w') as f:
311+
json.dump(flow_data, f, indent=2)
312+
else:
313+
# Convert to YAML
314+
from praisonai.flow.converter import langflow_json_to_yaml
315+
316+
console.print("[cyan]Converting to YAML format...[/cyan]")
317+
yaml_content = langflow_json_to_yaml(flow_data)
318+
319+
with open(output_path, 'w') as f:
320+
f.write(yaml_content)
321+
322+
console.print(f"[green]✅ Flow exported to {output_path}[/green]")
323+
324+
except ImportError as e:
325+
console.print(f"[red]Error: {e}[/red]")
326+
console.print("[yellow]Install with: pip install 'praisonai[flow]'[/yellow]")
327+
raise typer.Abort()
328+
except Exception as e:
329+
console.print(f"[red]Error exporting flow: {e}[/red]")
330+
raise typer.Abort()
331+
332+
333+
@app.command("list")
334+
def flow_list(
335+
langflow_url: str = typer.Option(
336+
"http://localhost:7860",
337+
"--url",
338+
help="Langflow server URL"
339+
),
340+
search: str = typer.Option(
341+
None,
342+
"--search",
343+
"-s",
344+
help="Search flows by name or description"
345+
),
346+
):
347+
"""List flows in Langflow server.
348+
349+
Shows all flows with their IDs, names, and descriptions for
350+
easy identification and export.
351+
352+
Examples:
353+
praisonai flow list
354+
praisonai flow list --search research
355+
praisonai flow list --url http://localhost:8080
356+
"""
357+
from rich.console import Console
358+
from rich.table import Table
359+
360+
console = Console()
361+
362+
try:
363+
from praisonai.flow.client import create_client
364+
365+
console.print(f"[cyan]Connecting to Langflow at {langflow_url}...[/cyan]")
366+
client = create_client(langflow_url)
367+
368+
# Check server health
369+
health = client.health_check()
370+
if health["status"] != "healthy":
371+
console.print(f"[red]Error: Langflow server not accessible at {langflow_url}[/red]")
372+
raise typer.Abort()
373+
374+
# Get flows
375+
if search:
376+
console.print(f"[cyan]Searching for flows matching '{search}'...[/cyan]")
377+
flows = client.search_flows(search)
378+
else:
379+
console.print("[cyan]Loading flows...[/cyan]")
380+
flows = client.list_flows()
381+
382+
if not flows:
383+
if search:
384+
console.print(f"[yellow]No flows found matching '{search}'[/yellow]")
385+
else:
386+
console.print("[yellow]No flows found[/yellow]")
387+
return
388+
389+
# Create table
390+
table = Table(title=f"Langflow Flows ({len(flows)} found)")
391+
table.add_column("ID", style="cyan", min_width=20)
392+
table.add_column("Name", style="green")
393+
table.add_column("Description", style="dim")
394+
table.add_column("Created", style="blue")
395+
396+
for flow in flows:
397+
flow_id = flow.get("id", "")[:20] + "..." if len(flow.get("id", "")) > 20 else flow.get("id", "")
398+
name = flow.get("name", "Unnamed")
399+
description = flow.get("description", "")[:50] + "..." if len(flow.get("description", "")) > 50 else flow.get("description", "")
400+
created = flow.get("created_at", "")[:10] if flow.get("created_at") else ""
401+
402+
table.add_row(flow_id, name, description, created)
403+
404+
console.print(table)
405+
406+
except ImportError as e:
407+
console.print(f"[red]Error: {e}[/red]")
408+
console.print("[yellow]Install with: pip install 'praisonai[flow]'[/yellow]")
409+
raise typer.Abort()
410+
except Exception as e:
411+
console.print(f"[red]Error listing flows: {e}[/red]")
412+
raise typer.Abort()
413+
414+
129415
@app.command("version")
130416
def flow_version():
131417
"""Show Langflow version information."""

0 commit comments

Comments
 (0)