Team Ai
Apppublic

nrrb/code_graph

sourceHugging Facemitupdated 1y agoView on Hugging Face
0likes
app.py267 linesDownload Raw Back to root
1import base642import dash3from dash import html, dcc, dash_table4import dash_cytoscape as cyto5from dash.dependencies import Input, Output, State6import pandas as pd7 8from utility import parse_functions_from_files, get_reachable_functions, get_backtrace_functions, build_nodes_and_edges9from introduction import get_intro_markdown10 11# Load extra layouts12cyto.load_extra_layouts()13 14app = dash.Dash(__name__, suppress_callback_exceptions=True)15server = app.server16 17 18app.layout = html.Div([19    html.H2("Function Dependency Visualizer (AST-Based)", style={20        "textAlign": "center",21        "marginTop": "20px",22        "fontSize": "28px",23        "color": "#333"24    }),25    dcc.Store(id="parsed-functions-store"),26 27    html.Div([28        html.Div([29            html.Label("Upload Files"),30            dcc.Upload(31                id="upload",32                children=html.Div("๐Ÿ“ค Drag and drop or click to upload Python files", style={33                    "border": "2px dashed #ccc",34                    "padding": "10px",35                    "textAlign": "center",36                    "cursor": "pointer",37                    "color": "#555",38                    "fontSize": "14px"39                }),40                multiple=True41            )42        ], style={"width": "24%", "display": "inline-block", "verticalAlign": "top", "marginRight": "1%"}),43 44        html.Div([45            html.Label("Main Function"),46            dcc.Dropdown(id="main-function", style={"width": "100%"})47        ], style={"width": "24%", "display": "inline-block", "verticalAlign": "top", "marginRight": "1%"}),48 49        html.Div([50            html.Label("Layout"),51            dcc.Dropdown(52                id="layout-type",53                options=[54                    {"label": "Preset", "value": "preset"},55                    {"label": "CoSE", "value": "cose"},56                    {"label": "CoSE Bilkent", "value": "cose-bilkent"},57                    {"label": "Cola", "value": "cola"},58                    {"label": "Spread", "value": "spread"},59                    {"label": "Dagre", "value": "dagre"},60                    {"label": "Klay", "value": "klay"}61                ],62                value="preset",63                style={"width": "100%"}64            )65        ], style={"width": "24%", "display": "inline-block", "verticalAlign": "top", "marginRight": "1%"}),66 67        html.Div([68            html.Label("Max Depth"),69            dcc.Dropdown(70                id="max-depth",71                options=[{"label": str(i), "value": i} for i in range(1, 11)],72                value=10,73                clearable=False,74                style={"width": "100%"}75            )76        ], style={"width": "24%", "display": "inline-block", "verticalAlign": "top", "marginRight": "1%"}),77 78        html.Div([79            html.Label("Options"),80            dcc.Checklist(81                id="backtrace-toggle",82                options=[{"label": "Backtrace Mode", "value": "backtrace"}],83                value=[],84                labelStyle={"display": "block", "fontWeight": "normal"}85            ),86            dcc.Checklist(87                id="function-detail-toggle",88                options=[{"label": "Show Function-Level Detail in Summaray tab", "value": "show"}],89                value=[],90                labelStyle={"display": "block", "fontWeight": "normal"},91                style={"marginTop": "5px"}92            )93        ], style={"width": "24%", "display": "inline-block", "verticalAlign": "top"})94    ], style={95        "backgroundColor": "#f9f9f9",96        "padding": "15px",97        "borderRadius": "10px",98        "margin": "20px auto",99        "width": "95%",100        "boxShadow": "0 2px 6px rgba(0,0,0,0.1)"101    }),102 103 104    dcc.Tabs(id="tab-selector", value="intro", children=[105        dcc.Tab(label="๐Ÿ“˜ Introduction", value="intro"),106        dcc.Tab(label="๐Ÿ“Š Graph Explorer", value="graph"),107        dcc.Tab(label="๐Ÿ“ File Summary", value="summary")108    ], style={"margin": "0 20px"}),109 110    html.Div(html.Div(id="intro-tab", style={"margin": "0 20px"}), id="intro-tab-container"),111    html.Div(id="graph-tab-container", style={"margin": "0 20px"}),112    html.Div(id="summary-tab-container", style={"margin": "0 20px"}),113 114 115    html.Footer("Built by Tomas Larsson โ€ข MIT Licensed", style={116        "textAlign": "center",117        "fontSize": "14px",118        "marginTop": "40px",119        "marginBottom": "20px",120        "color": "#888"121    })122])123 124 125@app.callback(126    Output("main-function", "options"),127    Output("main-function", "value"),128    Output("parsed-functions-store", "data"),129    Input("upload", "contents"),130    State("upload", "filename")131)132def handle_upload(contents, filenames):133    print("=== handle_upload triggered ===")134    if not contents:135        return [], None, {}136    uploaded_files = {}137    for content, name in zip(contents, filenames):138        _, content_string = content.split(",")139        uploaded_files[name] = base64.b64decode(content_string).decode("utf-8")140    parsed = parse_functions_from_files(uploaded_files)141 142 143#    options = [{"label": fn, "value": fn} for fn in parsed if parsed[fn]["calls"]]144    options = [{"label": fn, "value": fn} for fn in parsed]145    return options, options[0]["value"] if options else None, parsed146 147@app.callback(148    Output("intro-tab-container", "style"),149    Output("graph-tab-container", "style"),150    Output("summary-tab-container", "style"),151    Input("tab-selector", "value")152)153def toggle_tabs(tab):154    return (155        {"display": "block"} if tab == "intro" else {"display": "none"},156        {"display": "block"} if tab == "graph" else {"display": "none"},157        {"display": "block"} if tab == "summary" else {"display": "none"}158    )159 160@app.callback(Output("intro-tab", "children"), Input("tab-selector", "value"))161def show_intro(_):162    return get_intro_markdown()163 164from collections import deque, defaultdict165 166@app.callback(167    Output("graph-tab-container", "children"),168    Input("main-function", "value"),169    Input("backtrace-toggle", "value"),170    Input("max-depth", "value"),171    Input("layout-type", "value"),172    State("parsed-functions-store", "data")173)174def update_graph(main_func, backtrace_mode, max_depth, layout_type, parsed):175    if not parsed or not main_func:176        return html.Div("Upload files and select a main function.")177 178    graph = {k: v["calls"] for k, v in parsed.items()}179    reachable = get_backtrace_functions(main_func, graph) if "backtrace" in backtrace_mode else get_reachable_functions(main_func, graph)180 181    nodes, edges = build_nodes_and_edges(parsed, main_func, reachable, reverse="backtrace" in backtrace_mode, max_depth=max_depth)182 183 184    return cyto.Cytoscape(185        id="cytoscape-graph",186        layout={"name": layout_type},187        style={"width": "100%", "height": "600px"},188        elements=nodes + edges,189        stylesheet=[190            {"selector": "node", "style": {191                "label": "data(label)",192                "text-wrap": "wrap",193                "text-valign": "bottom",194                "text-halign": "center"195            }},196            {"selector": ".main", "style": {197                "background-color": "blue",198                "line-color": "blue",199                "color": "black",200                "label": "data(label)",201                "text-wrap": "wrap",202                "text-valign": "bottom",203                "text-halign": "center"204            }},205            {"selector": "edge", "style": {206                "label": "data(label)",207                "curve-style": "bezier",208                "target-arrow-shape": "triangle",209                "target-arrow-color": "#888",210                "line-color": "#888",211                "arrow-scale": 2,212                "font-size": "14px",213                "text-margin-y": -10,214                "text-margin-x": 10,215            }}216        ],217        userZoomingEnabled=True,218        userPanningEnabled=True,219        minZoom=0.2,220        maxZoom=2,221        wheelSensitivity=0.1222    )223 224 225@app.callback(226    Output("summary-tab-container", "children"),227    Input("parsed-functions-store", "data"),228    Input("function-detail-toggle", "value")229)230def update_summary(parsed, toggle):231    if not parsed:232        return html.Div("No summary available.")233    reverse_calls = {k: [] for k in parsed}234    for caller, meta in parsed.items():235        for callee in meta["calls"]:236            reverse_calls[callee].append(caller)237 238    if "show" in toggle:239        df = pd.DataFrame([{240            "Function": fn,241            "File": parsed[fn]["filename"],242            "Arguments": ", ".join(parsed[fn]["args"]),243            "Returns": ", ".join(parsed[fn]["returns"]),244            "Reads State": ", ".join(parsed[fn]["reads_state"]),245            "Writes State": ", ".join(parsed[fn]["writes_state"]),246            "Called By": len(reverse_calls[fn])247        } for fn in parsed])248    else:249        file_summary = {}250        for func, meta in parsed.items():251            fname = meta["filename"]252            file_summary.setdefault(fname, {"Total": 0, "Calls Others": 0, "Called By Others": 0, "Unused": 0})253            file_summary[fname]["Total"] += 1254            if meta["calls"]: file_summary[fname]["Calls Others"] += 1255            if reverse_calls[func]: file_summary[fname]["Called By Others"] += 1256            if not meta["calls"] and not reverse_calls[func]: file_summary[fname]["Unused"] += 1257        df = pd.DataFrame([{"File": f, **stats} for f, stats in file_summary.items()])258 259    return dash_table.DataTable(260        data=df.to_dict("records"),261        columns=[{"name": c, "id": c} for c in df.columns],262        style_table={"overflowX": "auto"},263        style_cell={"whiteSpace": "normal", "textAlign": "left", "padding": "5px", "maxWidth": 300}264    )265 266if __name__ == "__main__":267    app.run(host="0.0.0.0", port=7860)