22from flask_cors import CORS
33from services .topic_service import TopicService
44from services .ai_service import AITopicProcessor
5- from services .gexy_node_service import GexfNodeGenerator
5+ from services .gexf_node_service import GexfNodeGenerator
6+ from services .edge_generation_service import EdgeGenerationService
67import os
78import asyncio
89import re
10+ import json
911
1012app = Flask (__name__ , static_folder = 'gexf' , static_url_path = '/gexf' )
1113CORS (
2123
2224topic_service = TopicService ()
2325ai_processor = AITopicProcessor ()
24- gexy_node_service = GexfNodeGenerator ()
26+ gexf_node_service = GexfNodeGenerator ()
27+ edge_generation_service = EdgeGenerationService ()
2528
2629
2730@app .route ("/api/process-topics" , methods = ["GET" , "POST" ])
@@ -241,7 +244,7 @@ def suggest_topics():
241244def finalized_node_gexf ():
242245 data = request .get_json ()
243246 topics = data .get ("topics" , [])
244- gexf_path = gexy_node_service .generate_gexf_nodes_for_topics (topics )
247+ gexf_path = gexf_node_service .generate_gexf_nodes_for_topics (topics )
245248 # print(topics)
246249 # Read the GEXF file content
247250 with open (gexf_path , "r" , encoding = "utf-8" ) as f :
@@ -253,6 +256,197 @@ def finalized_node_gexf():
253256 })
254257
255258
259+ @app .route ("/api/generate-graph-with-edges" , methods = ["POST" ])
260+ def generate_graph_with_edges ():
261+ """
262+ Generate a graph with edges based on multiple criteria working in combination.
263+
264+ Expected request body:
265+ {
266+ "topics": ["topic1", "topic2"],
267+ "criteria_config": {
268+ "topic_based_linking": true,
269+ "contributor_overlap_enabled": true,
270+ "contributor_overlap_threshold": 2,
271+ "shared_organization_enabled": false,
272+ "common_stargazers_enabled": true,
273+ "stargazer_overlap_threshold": 3
274+ }
275+ }
276+ """
277+ try :
278+ data = request .get_json ()
279+ topics = data .get ("topics" , [])
280+ criteria_config = data .get ("criteria_config" , {})
281+
282+ if not topics :
283+ return jsonify ({
284+ "success" : False ,
285+ "error" : "No topics provided"
286+ }), 400
287+
288+ # Generate graph with edges based on criteria
289+ G , edge_stats = edge_generation_service .generate_edges_with_criteria (topics , criteria_config )
290+
291+ if not G .nodes ():
292+ return jsonify ({
293+ "success" : False ,
294+ "error" : "No repositories found for the given topics"
295+ }), 404
296+
297+ # Generate unique filename for this graph
298+ import hashlib
299+ from datetime import datetime
300+
301+ # Create hash from topics and criteria
302+ criteria_str = json .dumps (criteria_config , sort_keys = True )
303+ topics_str = "|" .join (sorted (topics ))
304+ combined_str = f"{ topics_str } _{ criteria_str } "
305+ hash_object = hashlib .md5 (combined_str .encode ())
306+ hash_hex = hash_object .hexdigest ()[:12 ]
307+ timestamp = datetime .now ().strftime ("%Y%m%d_%H%M%S" )
308+ filename = f"graph_with_edges_{ hash_hex } _{ timestamp } .gexf"
309+
310+ # Save to gexf directory
311+ gexf_dir = os .path .join (os .path .dirname (__file__ ), "gexf" )
312+ os .makedirs (gexf_dir , exist_ok = True )
313+ gexf_path = os .path .join (gexf_dir , filename )
314+
315+ # Save graph with edges
316+ edge_generation_service .save_graph_with_edges (G , gexf_path )
317+
318+ # Read the GEXF file content
319+ with open (gexf_path , "r" , encoding = "utf-8" ) as f :
320+ gexf_content = f .read ()
321+
322+ # Get comprehensive statistics
323+ graph_stats = edge_generation_service .get_edge_statistics (G )
324+
325+ return jsonify ({
326+ "success" : True ,
327+ "gexfContent" : gexf_content ,
328+ "filename" : filename ,
329+ "edge_statistics" : edge_stats ,
330+ "graph_statistics" : graph_stats
331+ })
332+
333+ except Exception as e :
334+ print (f"Error generating graph with edges: { str (e )} " )
335+ return jsonify ({
336+ "success" : False ,
337+ "error" : str (e ),
338+ "message" : "An error occurred while generating the graph with edges"
339+ }), 500
340+
341+
342+ @app .route ("/api/edge-generation-criteria" , methods = ["GET" ])
343+ def get_edge_generation_criteria ():
344+ """
345+ Get information about available edge generation criteria and their descriptions.
346+ """
347+ criteria_info = {
348+ "topic_based_linking" : {
349+ "description" : "Create edges between repositories that share common topics" ,
350+ "type" : "boolean" ,
351+ "default" : True ,
352+ "category" : "Content-based"
353+ },
354+ "contributor_overlap_enabled" : {
355+ "description" : "Create edges between repositories that have overlapping contributors" ,
356+ "type" : "boolean" ,
357+ "default" : False ,
358+ "category" : "Collaboration-based"
359+ },
360+ "contributor_overlap_threshold" : {
361+ "description" : "Minimum number of shared contributors required to create an edge" ,
362+ "type" : "integer" ,
363+ "default" : 2 ,
364+ "min" : 1 ,
365+ "max" : 100 ,
366+ "category" : "Collaboration-based"
367+ },
368+ "shared_organization_enabled" : {
369+ "description" : "Create edges between repositories owned by the same organization" ,
370+ "type" : "boolean" ,
371+ "default" : False ,
372+ "category" : "Organizational"
373+ },
374+ "common_stargazers_enabled" : {
375+ "description" : "Create edges between repositories that have overlapping stargazers" ,
376+ "type" : "boolean" ,
377+ "default" : False ,
378+ "category" : "Interest-based"
379+ },
380+ "stargazer_overlap_threshold" : {
381+ "description" : "Minimum number of shared stargazers required to create an edge" ,
382+ "type" : "integer" ,
383+ "default" : 2 ,
384+ "min" : 1 ,
385+ "max" : 1000 ,
386+ "category" : "Interest-based"
387+ },
388+ "use_and_logic" : {
389+ "description" : "Use AND logic to require multiple criteria to be satisfied for an edge" ,
390+ "type" : "boolean" ,
391+ "default" : False ,
392+ "category" : "Logic Control"
393+ }
394+ }
395+
396+ return jsonify ({
397+ "success" : True ,
398+ "criteria" : criteria_info ,
399+ "usage_examples" : [
400+ {
401+ "name" : "Topic + Contributor Overlap" ,
402+ "description" : "Connect repositories by both shared topics and contributor overlap" ,
403+ "config" : {
404+ "topic_based_linking" : True ,
405+ "contributor_overlap_enabled" : True ,
406+ "contributor_overlap_threshold" : 2 ,
407+ "shared_organization_enabled" : False ,
408+ "common_stargazers_enabled" : False
409+ }
410+ },
411+ {
412+ "name" : "Organization + Stargazer Overlap" ,
413+ "description" : "Connect repositories by organization ownership and stargazer overlap" ,
414+ "config" : {
415+ "topic_based_linking" : False ,
416+ "contributor_overlap_enabled" : False ,
417+ "shared_organization_enabled" : True ,
418+ "common_stargazers_enabled" : True ,
419+ "stargazer_overlap_threshold" : 3
420+ }
421+ },
422+ {
423+ "name" : "All Criteria Combined" ,
424+ "description" : "Use all available criteria to create comprehensive connections" ,
425+ "config" : {
426+ "topic_based_linking" : True ,
427+ "contributor_overlap_enabled" : True ,
428+ "contributor_overlap_threshold" : 2 ,
429+ "shared_organization_enabled" : True ,
430+ "common_stargazers_enabled" : True ,
431+ "stargazer_overlap_threshold" : 2
432+ }
433+ },
434+ {
435+ "name" : "Contributor + Organization (AND Logic)" ,
436+ "description" : "Only create edges when BOTH contributor overlap AND shared organization criteria are satisfied" ,
437+ "config" : {
438+ "topic_based_linking" : False ,
439+ "contributor_overlap_enabled" : True ,
440+ "contributor_overlap_threshold" : 2 ,
441+ "shared_organization_enabled" : True ,
442+ "common_stargazers_enabled" : False ,
443+ "use_and_logic" : True
444+ }
445+ }
446+ ]
447+ })
448+
449+
256450@app .route ("/api/get-unique-repos" , methods = ["POST" ])
257451def get_unique_repos ():
258452 try :
0 commit comments