diff --git a/assets/Dashboard_example.png b/assets/Dashboard_example.png deleted file mode 100644 index 594186c58..000000000 Binary files a/assets/Dashboard_example.png and /dev/null differ diff --git a/assets/cognee-logo.png b/assets/cognee-logo.png index d02a831fb..744ba2028 100644 Binary files a/assets/cognee-logo.png and b/assets/cognee-logo.png differ diff --git a/assets/topoteretes_logo.png b/assets/topoteretes_logo.png deleted file mode 100644 index ebdbc9a20..000000000 Binary files a/assets/topoteretes_logo.png and /dev/null differ diff --git a/assets/vscode-debug-config.png b/assets/vscode-debug-config.png deleted file mode 100644 index 21f54400a..000000000 Binary files a/assets/vscode-debug-config.png and /dev/null differ diff --git a/cognee/shared/utils.py b/cognee/shared/utils.py index b75076e55..0436bab4a 100644 --- a/cognee/shared/utils.py +++ b/cognee/shared/utils.py @@ -12,6 +12,12 @@ import pandas as pd import matplotlib.pyplot as plt import tiktoken import nltk +import base64 +import networkx as nx +from bokeh.io import output_file, save +from bokeh.plotting import figure, from_networkx +from bokeh.models import Circle, MultiLine, HoverTool, ColumnDataSource, Range1d + from cognee.base_config import get_base_config from cognee.infrastructure.databases.graph import get_graph_engine @@ -219,9 +225,9 @@ async def render_graph(graph, include_nodes=False, include_color=False, include_ return url -def sanitize_df(df): - """Replace NaNs and infinities in a DataFrame with None, making it JSON compliant.""" - return df.replace([np.inf, -np.inf, np.nan], None) +# def sanitize_df(df): +# """Replace NaNs and infinities in a DataFrame with None, making it JSON compliant.""" +# return df.replace([np.inf, -np.inf, np.nan], None) def get_entities(tagged_tokens): @@ -250,41 +256,231 @@ def extract_pos_tags(sentence): return pos_tags -def extract_named_entities(sentence): - """Extract Named Entities from a sentence.""" - # Tokenize the sentence into words - tagged_tokens = extract_pos_tags(sentence) +import networkx as nx +from bokeh.plotting import figure, output_file, show +from bokeh.models import Circle, MultiLine, HoverTool, Range1d +from bokeh.io import output_notebook +from bokeh.embed import file_html +from bokeh.resources import CDN +from bokeh.plotting import figure, from_networkx +import base64 +import cairosvg +import logging - # Perform Named Entity Recognition (NER) on the tagged tokens - entities = get_entities(tagged_tokens) +logging.basicConfig(level=logging.INFO) - return entities - - -def extract_sentiment_vader(text): +def convert_to_serializable_graph(G): """ - Analyzes the sentiment of a given text using the VADER Sentiment Intensity Analyzer. - - Parameters: - text (str): The text to analyze. - - Returns: - dict: A dictionary containing the polarity scores for the text. + Convert a graph into a serializable format with stringified node and edge attributes. """ - from nltk.sentiment import SentimentIntensityAnalyzer - nltk.download("vader_lexicon", quiet=True) + (nodes, edges) = G + networkx_graph = nx.MultiDiGraph() - # Initialize the VADER Sentiment Intensity Analyzer - sia = SentimentIntensityAnalyzer() + networkx_graph.add_nodes_from(nodes) + networkx_graph.add_edges_from(edges) - # Obtain the polarity scores for the text - polarity_scores = sia.polarity_scores(text) + new_G = nx.MultiDiGraph() if isinstance(G, nx.MultiDiGraph) else nx.Graph() + print(new_G) + for node, data in new_G.nodes(data=True): + serializable_data = {k: str(v) for k, v in data.items()} + new_G.add_node(str(node), **serializable_data) + for u, v, data in new_G.edges(data=True): + serializable_data = {k: str(v) for k, v in data.items()} + new_G.add_edge(str(u), str(v), **serializable_data) + return new_G - return polarity_scores +def generate_layout_positions(G, layout_func, layout_scale): + """ + Generate layout positions for the graph using the specified layout function. + """ + positions = layout_func(G) + return {str(node): (x * layout_scale, y * layout_scale) for node, (x, y) in positions.items()} + +def assign_node_colors(G, node_attribute, palette): + """ + Assign colors to nodes based on a specified attribute and a given palette. + """ + unique_attrs = set(G.nodes[node].get(node_attribute, "Unknown") for node in G.nodes) + color_map = {attr: palette[i % len(palette)] for i, attr in enumerate(unique_attrs)} + return [color_map[G.nodes[node].get(node_attribute, "Unknown")] for node in G.nodes], color_map + +def embed_logo(p, layout_scale, logo_alpha): + """ + Embed a logo into the graph visualization as a watermark. + """ + svg_logo=""" + + + + + + + """ # Add your SVG content here + png_data = cairosvg.svg2png(bytestring=svg_logo.encode("utf-8")) + logo_base64 = base64.b64encode(png_data).decode("utf-8") + logo_url = f"data:image/png;base64,{logo_base64}" + p.image_url( + url=[logo_url], + x=-layout_scale * 0.5, + y=layout_scale * 0.5, + w=layout_scale, + h=layout_scale, + anchor="center", + global_alpha=logo_alpha, + ) + +def style_and_render_graph(p, G, layout_positions, node_attribute, node_colors, centrality): + """ + Apply styling and render the graph into the plot. + """ + graph_renderer = from_networkx(G, layout_positions) + node_radii = [0.02 + 0.1 * centrality[node] for node in G.nodes()] + graph_renderer.node_renderer.data_source.data["radius"] = node_radii + graph_renderer.node_renderer.data_source.data["fill_color"] = node_colors + graph_renderer.node_renderer.glyph = Circle( + radius="radius", + fill_color="fill_color", + fill_alpha=0.9, + line_color="#000000", + line_width=1.5, + ) + graph_renderer.edge_renderer.glyph = MultiLine( + line_color="#000000", + line_alpha=0.3, + line_width=1.5, + ) + p.renderers.append(graph_renderer) + return graph_renderer + +def create_cognee_style_network_with_logo( + G, + output_filename="cognee_network_with_logo.html", + title="Cognee-Style Network", + node_attribute="group", + layout_func=nx.spring_layout, + layout_scale=3.0, + logo_alpha=0.1, +): + """ + Create a Cognee-inspired network visualization with an embedded logo. + """ + logging.info("Converting graph to serializable format...") + G = convert_to_serializable_graph(G) + + logging.info("Generating layout positions...") + layout_positions = generate_layout_positions(G, layout_func, layout_scale) + + logging.info("Assigning node colors...") + palette = ["#6510F4", "#0DFF00", "#FFFFFF"] + node_colors, color_map = assign_node_colors(G, node_attribute, palette) + + logging.info("Calculating centrality...") + centrality = nx.degree_centrality(G) + + logging.info("Preparing Bokeh output...") + output_file(output_filename) + p = figure( + title=title, + tools="pan,wheel_zoom,save,reset,hover", + active_scroll="wheel_zoom", + width=1200, + height=900, + background_fill_color="#F4F4F4", + x_range=Range1d(-layout_scale, layout_scale), + y_range=Range1d(-layout_scale, layout_scale), + ) + p.toolbar.logo = None + p.axis.visible = False + p.grid.visible = False + + logging.info("Embedding logo into visualization...") + embed_logo(p, layout_scale, logo_alpha) + + logging.info("Styling and rendering graph...") + style_and_render_graph(p, G, layout_positions, node_attribute, node_colors, centrality) + + logging.info("Adding hover tool...") + hover_tool = HoverTool( + tooltips=[ + ("Node", "@index"), + (node_attribute.capitalize(), f"@{node_attribute}"), + ("Centrality", "@radius{0.00}"), + ], + ) + p.add_tools(hover_tool) + + logging.info(f"Saving visualization to {output_filename}...") + html_content = file_html(p, CDN, title) + with open(output_filename, "w") as f: + f.write(html_content) + + logging.info("Visualization complete.") + return html_content +def graph_to_tuple(graph): + """ + Converts a networkx graph to a tuple of (nodes, edges). + + :param graph: A networkx graph. + :return: A tuple (nodes, edges). + """ + nodes = list(graph.nodes(data=True)) # Get nodes with attributes + edges = list(graph.edges(data=True)) # Get edges with attributes + return (nodes, edges) + + +# ---------------- Example Usage ---------------- if __name__ == "__main__": - sample_text = "I love sunny days, but I hate the rain." - sentiment_scores = extract_sentiment_vader(sample_text) - print("Sentiment analysis results:", sentiment_scores) + import networkx as nx + + # Create a sample graph + nodes = [ + (1, {"group": "A"}), + (2, {"group": "A"}), + (3, {"group": "B"}), + (4, {"group": "B"}), + (5, {"group": "C"}) + ] + edges = [ + (1, 2), + (2, 3), + (3, 4), + (4, 5), + (5, 1) + ] + + # Create a NetworkX graph + G = nx.Graph() + G.add_nodes_from(nodes) + G.add_edges_from(edges) + + # Call the function + output_html = create_cognee_style_network_with_logo( + G=G, + output_filename="example_network.html", + title="Example Cognee Network", + node_attribute="group", # Attribute to use for coloring nodes + layout_func=nx.spring_layout, # Layout function + layout_scale=3.0, # Scale for the layout + logo_alpha=0.2 # Transparency of the logo + ) + + # Print the output filename + print("Network visualization saved as example_network.html") + +# # Create a random geometric graph +# G = nx.random_geometric_graph(50, 0.3) +# # Assign random group attributes for coloring +# for i, node in enumerate(G.nodes()): +# G.nodes[node]["group"] = f"Group {i % 3 + 1}" +# +# create_cognee_graph( +# G, +# output_filename="cognee_style_network_with_logo.html", +# title="Cognee-Graph Network", +# node_attribute="group", +# layout_func=nx.spring_layout, +# layout_scale=3.0, # Replace with your logo file path +# ) diff --git a/cognee/tests/unit/processing/utils/utils_test.py b/cognee/tests/unit/processing/utils/utils_test.py new file mode 100644 index 000000000..e69de29bb