Skip to content

Commit e6a9b07

Browse files
cgc visualize cmd
1 parent 383ab07 commit e6a9b07

7 files changed

Lines changed: 452 additions & 2 deletions

File tree

MANIFEST.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
prune website
22
prune images
3+
recursive-include src/codegraphcontext/viz/dist *
4+
recursive-include vscode-extension *

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ dependencies = [
2424
"python-dotenv>=1.0.0",
2525
"tree-sitter>=0.21.0",
2626
"tree-sitter-language-pack>=0.6.0",
27+
"tree-sitter-c-sharp>=0.21.0",
2728
"pyyaml",
2829
"nbformat",
2930
"nbconvert>=7.16.6",
@@ -38,6 +39,7 @@ dependencies = [
3839
parsing = [
3940
"tree-sitter>=0.21.0",
4041
"tree-sitter-language-pack>=0.6.0",
42+
"tree-sitter-c-sharp>=0.21.0",
4143
]
4244
dev = [
4345
"pytest>=7.4.0",

src/codegraphcontext/cli/cli_helpers.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,88 @@ def cypher_helper_visual(query: str):
342342
db_manager.close_driver()
343343

344344

345+
import uvicorn
346+
import urllib.parse
347+
from ..viz.server import run_server, set_db_manager
348+
349+
def visualize_helper(repo_path: Optional[str] = None, port: int = 8000):
350+
""""Generates an interactive visualization using the Playground UI."""
351+
services = _initialize_services()
352+
if not all(services):
353+
return
354+
355+
db_manager, _, _ = services
356+
357+
# Set the DB manager for the server
358+
set_db_manager(db_manager)
359+
360+
# Determine the static directory (built React app)
361+
# This points to src/codegraphcontext/viz/dist where we build the website
362+
# (relative to src/codegraphcontext/cli/cli_helpers.py)
363+
# Using .resolve() is more robust for path comparison and existence checks
364+
this_file = Path(__file__).resolve()
365+
package_root = this_file.parent.parent
366+
static_dir = package_root / "viz" / "dist"
367+
368+
# Fallback for development if not yet built in viz/dist
369+
if not static_dir.exists():
370+
# Look for website/dist in the project root (3 levels up from cli/cli_helpers.py, 4 parents)
371+
# 1: cli/, 2: codegraphcontext/, 3: src/, 4: project_root/
372+
project_root = this_file.parent.parent.parent.parent
373+
dev_static_dir = project_root / "website" / "dist"
374+
375+
# Also try one level up from package_root just in case of different layouts
376+
alt_dev_dir = package_root.parent.parent / "website" / "dist"
377+
378+
if dev_static_dir.exists():
379+
static_dir = dev_static_dir
380+
elif alt_dev_dir.exists():
381+
static_dir = alt_dev_dir
382+
else:
383+
# Last resort: try current working directory
384+
cwd_static_dir = Path.cwd() / "website" / "dist"
385+
if cwd_static_dir.exists():
386+
static_dir = cwd_static_dir
387+
else:
388+
console.print(f"[yellow]Warning: Visualization assets not found.[/yellow]")
389+
console.print(f"[dim]Checked paths:[/dim]")
390+
console.print(f" [dim]- {static_dir}[/dim]")
391+
console.print(f" [dim]- {dev_static_dir}[/dim]")
392+
console.print(f" [dim]- {alt_dev_dir}[/dim]")
393+
console.print(f" [dim]- {cwd_static_dir}[/dim]")
394+
console.print("[dim]Please run 'cd website && npm run build' first.[/dim]")
395+
# We continue anyway to let the server start (helpful for dev)
396+
397+
# Construct the URL
398+
backend_url = f"http://localhost:{port}"
399+
params = {"backend": backend_url}
400+
if repo_path:
401+
params["repo_path"] = str(Path(repo_path).resolve())
402+
403+
query_string = urllib.parse.urlencode(params)
404+
visualization_url = f"{backend_url}/explore?{query_string}"
405+
406+
console.print(f"[green]Starting visualizer server on {backend_url}...[/green]")
407+
console.print(f"[cyan]Opening Playground UI:[/cyan] {visualization_url}")
408+
409+
# Open browser in a separate thread/process if possible, or just before starting server
410+
def open_browser():
411+
import time
412+
import webbrowser
413+
time.sleep(1.5) # Give the server a moment to start
414+
webbrowser.open(visualization_url)
415+
416+
import threading
417+
threading.Thread(target=open_browser, daemon=True).start()
418+
419+
try:
420+
run_server(host="127.0.0.1", port=port, static_dir=str(static_dir))
421+
except Exception as e:
422+
console.print(f"[bold red]An error occurred while running the server:[/bold red] {e}")
423+
finally:
424+
db_manager.close_driver()
425+
426+
345427
def reindex_helper(path: str):
346428
"""Force re-index by deleting and rebuilding the repository."""
347429
time_start = time.time()
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import urllib.parse
2+
from typing import Optional, List, Dict, Any, Set
3+
from pathlib import Path
4+
from .cli_helpers import visualize_helper
5+
6+
def check_visual_flag(ctx, visual: bool, cypher_query: str = None):
7+
"""
8+
Helper to check the --visual flag and launch the visualizer.
9+
This is called from within analyze/find commands.
10+
"""
11+
if visual and cypher_query:
12+
# We start the visualizer on port 8000
13+
# Passing empty repo handles showing just the query results
14+
port = 8000
15+
encoded_query = urllib.parse.quote(cypher_query)
16+
visualization_url = f"http://localhost:{port}/explore?cypher_query={encoded_query}"
17+
18+
from rich.console import Console
19+
console = Console(stderr=True)
20+
console.print(f"[green]Starting visualizer...[/green]")
21+
console.print(f"[cyan]Visualizing results at:[/cyan] {visualization_url}")
22+
23+
# Start the backend server and open the browser
24+
visualize_helper(repo_path=None, port=port)
25+
return True
26+
return False
27+
28+
def visualize_call_graph(cypher_query: str):
29+
"""Visualize a call graph result."""
30+
visualize_helper(repo_path=None, port=8000)
31+
32+
def visualize_call_chain(cypher_query: str):
33+
"""Visualize a call chain result."""
34+
visualize_helper(repo_path=None, port=8000)
35+
36+
def visualize_dependencies(cypher_query: str):
37+
"""Visualize code dependencies."""
38+
visualize_helper(repo_path=None, port=8000)
39+
40+
def visualize_inheritance_tree(cypher_query: str):
41+
"""Visualize class inheritance tree."""
42+
visualize_helper(repo_path=None, port=8000)
43+
44+
def visualize_overrides(cypher_query: str):
45+
"""Visualize method overrides."""
46+
visualize_helper(repo_path=None, port=8000)
47+
48+
def visualize_search_results(cypher_query: str):
49+
"""Visualize search results."""
50+
visualize_helper(repo_path=None, port=8000)

src/codegraphcontext/tools/handlers/query_handlers.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,25 @@ def execute_cypher_query(db_manager, **args) -> Dict[str, Any]:
6363
"error": "An unexpected error occurred while executing the query.",
6464
"details": str(e)
6565
}
66+
67+
def visualize_graph_query(db_manager, **args) -> Dict[str, Any]:
68+
"""Tool to generate a visualization URL for the local Playground UI."""
69+
cypher_query = args.get("cypher_query")
70+
if not cypher_query:
71+
return {"error": "Cypher query cannot be empty."}
72+
73+
try:
74+
# We point to the local server started by 'cgc visualize'
75+
# By default it runs on port 8000
76+
port = 8000
77+
encoded_query = urllib.parse.quote(cypher_query)
78+
visualization_url = f"http://localhost:{port}/index.html?cypher_query={encoded_query}"
79+
80+
return {
81+
"success": True,
82+
"visualization_url": visualization_url,
83+
"message": "Click the URL to visualize this specific query in the Playground UI. (Ensure 'cgc visualize' is running)"
84+
}
85+
except Exception as e:
86+
debug_log(f"Error generating visualization URL: {str(e)}")
87+
return {"error": f"Failed to generate visualization URL: {str(e)}"}

0 commit comments

Comments
 (0)