Skip to content

Commit 3f09da6

Browse files
committed
restructure the backend app
1 parent e4763ac commit 3f09da6

13 files changed

Lines changed: 374 additions & 223 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ node_modules
99
dist
1010
.DS_Store
1111
*temp
12-
*repo_metadata.json
12+
*repo_metadata.json
13+
__pycache__

backend/app/__init__.py

Whitespace-only changes.

backend/app/main.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
from flask import Flask, jsonify, request
2+
from flask_cors import CORS
3+
from app.services.topic_service import TopicService
4+
from app.services.ai_service import AITopicProcessor
5+
6+
app = Flask(__name__)
7+
CORS(app, resources={
8+
r"/*": {
9+
"origins": "*",
10+
"methods": ["GET", "POST", "OPTIONS"],
11+
"allow_headers": ["Content-Type", "Authorization"]
12+
}
13+
})
14+
15+
topic_service = TopicService()
16+
ai_processor = AITopicProcessor()
17+
18+
@app.route('/process-topics', methods=['GET', 'POST'])
19+
def process_topics():
20+
try:
21+
if request.method == 'POST':
22+
data = request.get_json()
23+
search_term = data.get('searchTerm', '')
24+
else:
25+
search_term = request.args.get('searchTerm', '')
26+
result = topic_service.process_topics(search_term)
27+
return jsonify(result)
28+
29+
except Exception as e:
30+
return jsonify({
31+
"success": False,
32+
"error": str(e),
33+
"message": "An error occurred while processing the request"
34+
}), 500
35+
36+
@app.route('/ai-process', methods=['GET', 'POST'])
37+
def ai_process():
38+
try:
39+
if request.method == 'POST':
40+
data = request.get_json()
41+
print(data)
42+
43+
# Extract parameters using frontend names
44+
model = data.get('selectedModel', 'gpt-3.5-turbo')
45+
api_token = data.get('apiKey', '')
46+
prompt = data.get('customPrompt', '')
47+
selected_topics = data.get('selectedTopics', [])
48+
49+
print(f"Selected topics: {selected_topics}")
50+
print(f"Using model: {model}") # Debug log
51+
print(f"Prompt length: {len(prompt)}") # Debug log
52+
53+
# Use the AI processor to analyze the topics
54+
print("About to call AI processor...") # Debug log
55+
ai_result = ai_processor.process_topics(
56+
model=model,
57+
api_token=api_token,
58+
prompt=prompt,
59+
selected_topics=selected_topics
60+
)
61+
print(f"AI processing complete. Result length: {len(str(ai_result))}") # Debug log
62+
63+
return jsonify({
64+
"success": True,
65+
"result": ai_result
66+
})
67+
68+
except Exception as e:
69+
print(f"Error occurred: {str(e)}") # Debug log
70+
return jsonify(["error1","error2","error3"]), 500
71+
72+
@app.route('/')
73+
def home():
74+
return "Hello World!"
75+
76+
if __name__ == '__main__':
77+
print("Starting Flask server...")
78+
port = 5002
79+
print(f"Server running on: http://127.0.0.1:{port}")
80+
app.run(host='127.0.0.1', port=port, debug=True)

backend/app/services/__init__.py

Whitespace-only changes.

backend/app/services/ai_service.py

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import logging
2+
from typing import List
3+
import google.generativeai as genai
4+
from openai import OpenAI
5+
from fastapi import HTTPException
6+
7+
# Configure logging
8+
logging.basicConfig(level=logging.DEBUG)
9+
logger = logging.getLogger(__name__)
10+
11+
class AITopicProcessor:
12+
def __init__(self):
13+
self.openai_client = None
14+
self.gemini_client = None
15+
16+
def initialize_client(self, model: str, api_key: str):
17+
if model.startswith("gpt"):
18+
self.openai_client = OpenAI(api_key=api_key)
19+
elif model.startswith("gemini"):
20+
genai.configure(api_key=api_key)
21+
self.gemini_client = genai.GenerativeModel(model)
22+
23+
async def process_with_openai(
24+
self, prompt: str, topics: List[str], model: str
25+
) -> List[str]:
26+
try:
27+
full_prompt = f"""Current topics: {", ".join(topics)}
28+
29+
{prompt}
30+
31+
Please provide suggestions as a simple list, one per line. Keep each suggestion concise."""
32+
33+
response = self.openai_client.chat.completions.create(
34+
model=model,
35+
messages=[{"role": "user", "content": full_prompt}],
36+
temperature=0.7,
37+
max_tokens=500,
38+
)
39+
40+
suggestions = response.choices[0].message.content.strip().split("\n")
41+
# Clean up suggestions (remove bullet points, numbers, etc.)
42+
suggestions = [
43+
s.lstrip("- ").lstrip("* ").lstrip("1234567890. ") for s in suggestions
44+
]
45+
return [s for s in suggestions if s] # Remove empty strings
46+
47+
except Exception as e:
48+
raise HTTPException(status_code=500, detail=f"OpenAI API error: {str(e)}")
49+
50+
async def process_with_gemini(self, prompt: str, topics: List[str]) -> List[str]:
51+
try:
52+
full_prompt = f"""Current topics: {", ".join(topics)}
53+
54+
{prompt}
55+
56+
Please provide suggestions as a simple list, one per line. Keep each suggestion concise."""
57+
58+
response = self.gemini_client.generate_content(full_prompt)
59+
60+
if response.text:
61+
suggestions = response.text.strip().split("\n")
62+
# Clean up suggestions (remove bullet points, numbers, etc.)
63+
suggestions = [
64+
s.lstrip("- ").lstrip("* ").lstrip("1234567890. ")
65+
for s in suggestions
66+
]
67+
return [s for s in suggestions if s] # Remove empty strings
68+
return []
69+
70+
except Exception as e:
71+
raise HTTPException(status_code=500, detail=f"Gemini API error: {str(e)}")
72+
73+
async def process_topics(
74+
self, model: str, api_key: str, prompt: str, topics: List[str]
75+
) -> List[str]:
76+
"""
77+
Process topics using the specified AI model and return suggestions.
78+
"""
79+
# Enhanced debug logging
80+
logger.debug("\n=== Incoming Request Validation ===")
81+
logger.debug(f"Model: {model if model else 'NOT PROVIDED'}")
82+
logger.debug(f"API Key: {'[PROVIDED]' if api_key else 'NOT PROVIDED'}")
83+
logger.debug(f"Prompt: {prompt if prompt else 'NOT PROVIDED'}")
84+
logger.debug(f"Topics: {topics if topics else 'NOT PROVIDED'}")
85+
logger.debug(f"Topics length: {len(topics) if topics else 0}")
86+
logger.debug("Request data types:")
87+
logger.debug(f"- Model type: {type(model)}")
88+
logger.debug(f"- API Key type: {type(api_key)}")
89+
logger.debug(f"- Prompt type: {type(prompt)}")
90+
logger.debug(f"- Topics type: {type(topics)}")
91+
logger.debug("=" * 50)
92+
93+
# More detailed input validation
94+
validation_errors = []
95+
if not model or not isinstance(model, str):
96+
validation_errors.append("Invalid or missing model")
97+
if not api_key or not isinstance(api_key, str):
98+
validation_errors.append("Invalid or missing API key")
99+
if not isinstance(topics, list):
100+
validation_errors.append("Topics must be a list")
101+
elif len(topics) == 0:
102+
validation_errors.append("Topics list cannot be empty")
103+
if not prompt or not isinstance(prompt, str):
104+
validation_errors.append("Invalid or missing prompt")
105+
106+
if validation_errors:
107+
error_message = "; ".join(validation_errors)
108+
logger.error(f"Validation failed: {error_message}")
109+
raise HTTPException(status_code=400, detail=error_message)
110+
111+
try:
112+
# Initialize the appropriate client
113+
self.initialize_client(model, api_key)
114+
115+
# Validate client initialization
116+
if model.startswith("gpt") and not self.openai_client:
117+
raise HTTPException(
118+
status_code=500, detail="Failed to initialize OpenAI client"
119+
)
120+
if model.startswith("gemini") and not self.gemini_client:
121+
raise HTTPException(
122+
status_code=500, detail="Failed to initialize Gemini client"
123+
)
124+
125+
# Process with appropriate model
126+
if model.startswith("gpt"):
127+
return await self.process_with_openai(prompt, topics, model)
128+
elif model.startswith("gemini"):
129+
return await self.process_with_gemini(prompt, topics)
130+
else:
131+
raise HTTPException(
132+
status_code=400, detail=f"Unsupported model: {model}"
133+
)
134+
135+
except Exception as e:
136+
raise HTTPException(status_code=500, detail=str(e))
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
from collections import Counter
2+
import duckdb
3+
from app.utils.cache import get_cached_topics, save_cached_topics
4+
5+
class TopicService:
6+
def __init__(self):
7+
self.con = duckdb.connect(database=':memory:')
8+
self.con.execute("SET threads TO 16;")
9+
self.con.execute("""
10+
CREATE TEMP TABLE repo AS
11+
SELECT * FROM read_json_auto('../public/data/repo_metadata.json');
12+
""")
13+
14+
def process_topics(self, search_term: str):
15+
try:
16+
search_term = search_term.lower()
17+
18+
# Check cache
19+
cached_result = get_cached_topics(search_term)
20+
if cached_result:
21+
return {
22+
"success": True,
23+
"data": cached_result,
24+
"total": len(cached_result),
25+
"cached": True
26+
}
27+
28+
# Get data from DuckDB
29+
query = "SELECT nameWithOwner, topics FROM repo"
30+
df = self.con.execute(query).fetchdf()
31+
32+
# Process topics
33+
def extract_names(item_ls):
34+
if item_ls is not None and len(item_ls) > 0:
35+
return [item["name"] for item in item_ls if "name" in item]
36+
return []
37+
38+
df["topics"] = df["topics"].apply(extract_names)
39+
filtered_df = df[df["topics"].apply(lambda x: search_term in [t.lower() for t in x])]
40+
41+
# Count topics
42+
all_topics = [topic for topics in filtered_df["topics"] for topic in topics]
43+
topic_counts = Counter(all_topics)
44+
topic_counts.pop(search_term, None)
45+
46+
# Format results
47+
topics = [{"name": name, "count": count} for name, count in topic_counts.items() if count > 2]
48+
topics = sorted(topics, key=lambda x: x["count"], reverse=True)
49+
50+
# Cache results
51+
save_cached_topics(search_term, topics)
52+
53+
return {
54+
"success": True,
55+
"data": topics,
56+
"total": len(topics),
57+
"cached": False
58+
}
59+
60+
except Exception as e:
61+
return {
62+
"success": False,
63+
"error": str(e),
64+
"message": "An error occurred while processing the request"
65+
}

backend/app/utils/__init__.py

Whitespace-only changes.

backend/app/utils/cache.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from pathlib import Path
2+
import json
3+
4+
def get_cached_topics(search_term):
5+
cache_file = Path('../public/data/cached_topics') / f"{search_term}.json"
6+
if cache_file.exists():
7+
with open(cache_file, 'r') as f:
8+
return json.load(f)
9+
return None
10+
11+
def save_cached_topics(search_term, topics_data):
12+
cache_dir = Path('../public/data/cached_topics')
13+
cache_dir.mkdir(exist_ok=True)
14+
cache_file = cache_dir / f"{search_term}.json"
15+
with open(cache_file, 'w') as f:
16+
json.dump(topics_data, f)

backend/requirements.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
flask
2+
flask-cors
3+
duckdb
4+
google-generativeai
5+
openai
6+
fastapi
7+
uvicorn

0 commit comments

Comments
 (0)