Team Ai
Apppublic

Tom-Dev-space/code_graph

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