Update app.py
Browse files
app.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1 |
-
from flask import Flask, request, jsonify
|
|
|
2 |
import os
|
3 |
from dotenv import load_dotenv
|
4 |
import requests
|
@@ -7,13 +8,34 @@ from csv_handler import CSVHandler
|
|
7 |
import ssl
|
8 |
import logging
|
9 |
from web_scraper import research_topic
|
|
|
|
|
10 |
|
11 |
-
#
|
12 |
-
|
13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
|
15 |
app = Flask(__name__)
|
16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
|
18 |
# Configuration
|
19 |
OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
|
@@ -75,16 +97,16 @@ Create the preliminary plan."""
|
|
75 |
},
|
76 |
timeout=60
|
77 |
)
|
78 |
-
|
79 |
logger.info(f"OpenRouter API Response: {response.text}")
|
80 |
-
|
81 |
if response.status_code != 200:
|
82 |
raise Exception(f"OpenRouter API error: {response.text}")
|
83 |
-
|
84 |
response_data = response.json()
|
85 |
if 'choices' not in response_data:
|
86 |
raise Exception(f"Unexpected API response format: {response_data}")
|
87 |
-
|
88 |
return response_data['choices'][0]['message']['content']
|
89 |
except Exception as e:
|
90 |
logger.error(f"Error in generate_preliminary_plan: {e}")
|
@@ -95,46 +117,46 @@ def do_research(plan, openrouter_key):
|
|
95 |
try:
|
96 |
# Extract key points from plan to create search queries
|
97 |
plan_lines = [line.strip('* -').strip() for line in plan.split('\n') if line.strip()]
|
98 |
-
|
99 |
# Take only the first 3 points
|
100 |
plan_lines = plan_lines[:3]
|
101 |
logger.info(f"Researching top 3 points: {plan_lines}")
|
102 |
-
|
103 |
all_research = []
|
104 |
-
|
105 |
# Research each main point in the plan (limited to 3 points)
|
106 |
for point in plan_lines:
|
107 |
if not point: # Skip empty lines
|
108 |
continue
|
109 |
-
|
110 |
# Get research results including both web content and AI analysis
|
111 |
# Using 5 sites per point for more comprehensive research
|
112 |
results = research_topic(point, num_sites=5, openrouter_key=openrouter_key)
|
113 |
-
|
114 |
if results['success']:
|
115 |
all_research.append({
|
116 |
'topic': point,
|
117 |
'analysis': results['analysis'],
|
118 |
'sources': results['sources']
|
119 |
})
|
120 |
-
|
121 |
# Format all research into a comprehensive markdown document
|
122 |
formatted_research = "# Research Results\n\n"
|
123 |
-
|
124 |
for research in all_research:
|
125 |
formatted_research += f"## {research['topic']}\n\n"
|
126 |
formatted_research += f"{research['analysis']}\n\n"
|
127 |
formatted_research += "### Sources Referenced\n\n"
|
128 |
-
|
129 |
for source in research['sources']:
|
130 |
formatted_research += f"- [{source['title']}]({source['source']})\n"
|
131 |
if source['meta_info']['description']:
|
132 |
formatted_research += f" {source['meta_info']['description']}\n"
|
133 |
-
|
134 |
formatted_research += "\n---\n\n"
|
135 |
-
|
136 |
return formatted_research
|
137 |
-
|
138 |
except Exception as e:
|
139 |
logger.error(f"Error in do_research: {e}")
|
140 |
raise
|
@@ -143,20 +165,20 @@ def do_research(plan, openrouter_key):
|
|
143 |
def generate_blog():
|
144 |
try:
|
145 |
logger.info("Starting blog generation process for multiple clusters...")
|
146 |
-
|
147 |
# Initialize handlers
|
148 |
blog_gen = BlogGenerator(OPENAI_API_KEY, OPENROUTER_API_KEY)
|
149 |
csv_handler = CSVHandler()
|
150 |
|
151 |
generated_blogs = []
|
152 |
-
|
153 |
# Get all available clusters
|
154 |
all_clusters = csv_handler.get_all_clusters()
|
155 |
-
|
156 |
for cluster_data in all_clusters:
|
157 |
try:
|
158 |
logger.info(f"Processing cluster with primary keyword: {cluster_data['Primary Keyword']}")
|
159 |
-
|
160 |
# 2. Generate preliminary plan
|
161 |
logger.info("Generating preliminary plan...")
|
162 |
plan = generate_preliminary_plan(cluster_data)
|
@@ -266,57 +288,57 @@ def generate_from_csv():
|
|
266 |
try:
|
267 |
if 'file' not in request.files:
|
268 |
return jsonify({'error': 'No file uploaded'}), 400
|
269 |
-
|
270 |
file = request.files['file']
|
271 |
if file.filename == '':
|
272 |
return jsonify({'error': 'No file selected'}), 400
|
273 |
-
|
274 |
# Read and decode the CSV content
|
275 |
csv_content = file.read().decode('utf-8')
|
276 |
-
|
277 |
# Initialize handlers
|
278 |
blog_gen = BlogGenerator(OPENAI_API_KEY, OPENROUTER_API_KEY)
|
279 |
csv_handler = CSVHandler()
|
280 |
-
|
281 |
# Process the uploaded CSV
|
282 |
clusters = csv_handler.process_uploaded_csv(csv_content)
|
283 |
-
|
284 |
if not clusters:
|
285 |
return jsonify({'error': 'No valid clusters found in CSV'}), 400
|
286 |
-
|
287 |
generated_blogs = []
|
288 |
-
|
289 |
# Process each cluster
|
290 |
for cluster_data in clusters:
|
291 |
try:
|
292 |
logger.info(f"Processing cluster with primary keyword: {cluster_data['Primary Keyword']}")
|
293 |
-
|
294 |
# Generate preliminary plan
|
295 |
plan = generate_preliminary_plan(cluster_data)
|
296 |
-
|
297 |
# Do research
|
298 |
research = do_research(plan, OPENROUTER_API_KEY)
|
299 |
-
|
300 |
# Create detailed plan
|
301 |
detailed_plan = blog_gen.create_detailed_plan(cluster_data, plan, research)
|
302 |
-
|
303 |
# Write blog post
|
304 |
blog_content = blog_gen.write_blog_post(detailed_plan, cluster_data)
|
305 |
-
|
306 |
# Add internal links
|
307 |
previous_posts = csv_handler.get_previous_posts()
|
308 |
blog_content = blog_gen.add_internal_links(blog_content, previous_posts)
|
309 |
-
|
310 |
# Convert to HTML
|
311 |
cover_image_url = blog_gen.get_cover_image(cluster_data['Primary Keyword'])
|
312 |
html_content = blog_gen.convert_to_html(blog_content, cover_image_url)
|
313 |
-
|
314 |
# Generate metadata
|
315 |
metadata = blog_gen.generate_metadata(blog_content, cluster_data['Primary Keyword'], cluster_data)
|
316 |
-
|
317 |
# Get cover image
|
318 |
cover_image_url = blog_gen.get_cover_image(metadata['title'])
|
319 |
-
|
320 |
# Create blog post data
|
321 |
blog_post_data = {
|
322 |
'title': metadata['title'],
|
@@ -329,13 +351,13 @@ def generate_from_csv():
|
|
329 |
'research': research,
|
330 |
'detailed_plan': detailed_plan
|
331 |
}
|
332 |
-
|
333 |
generated_blogs.append({
|
334 |
'status': 'success',
|
335 |
'message': f"Blog post generated successfully for {cluster_data['Primary Keyword']}",
|
336 |
'data': blog_post_data
|
337 |
})
|
338 |
-
|
339 |
except Exception as e:
|
340 |
logger.error(f"Error processing cluster {cluster_data['Primary Keyword']}: {e}")
|
341 |
generated_blogs.append({
|
@@ -343,53 +365,53 @@ def generate_from_csv():
|
|
343 |
'message': f"Failed to generate blog post for {cluster_data['Primary Keyword']}",
|
344 |
'error': str(e)
|
345 |
})
|
346 |
-
|
347 |
return jsonify({
|
348 |
'status': 'success',
|
349 |
'message': f'Generated {len(generated_blogs)} blog posts from uploaded CSV',
|
350 |
'blogs': generated_blogs
|
351 |
})
|
352 |
-
|
353 |
except Exception as e:
|
354 |
logger.error(f"Error in generate_from_csv: {e}")
|
355 |
return jsonify({'error': str(e)}), 500
|
356 |
-
|
357 |
|
358 |
@app.route('/generate-from-csv-text', methods=['POST'])
|
359 |
def generate_from_csv_text():
|
360 |
try:
|
361 |
logger.info("Starting blog generation process for multiple clusters...")
|
362 |
-
|
363 |
# Get CSV content and OpenRouter API key from request JSON
|
364 |
data = request.get_json()
|
365 |
if not data or 'csv_content' not in data:
|
366 |
return jsonify({'error': 'No CSV content provided'}), 400
|
367 |
if 'openrouter_key' not in data:
|
368 |
return jsonify({'error': 'OpenRouter API key is required'}), 400
|
369 |
-
|
370 |
csv_content = data['csv_content']
|
371 |
openrouter_key = data['openrouter_key']
|
372 |
-
|
373 |
# Initialize handlers with the provided OpenRouter key
|
374 |
blog_gen = BlogGenerator(OPENAI_API_KEY, openrouter_key)
|
375 |
csv_handler = CSVHandler()
|
376 |
-
|
377 |
# Process the CSV text
|
378 |
clusters = csv_handler.process_csv_text(csv_content)
|
379 |
-
|
380 |
if not clusters:
|
381 |
return jsonify({'error': 'No valid clusters found in CSV'}), 400
|
382 |
-
|
383 |
generated_blogs = []
|
384 |
-
|
385 |
# Process each cluster
|
386 |
for cluster_data in clusters:
|
387 |
try:
|
388 |
logger.info(f"Processing cluster with primary keyword: {cluster_data['Primary Keyword']}")
|
389 |
-
|
390 |
# Add OpenRouter key to cluster_data for use in functions
|
391 |
cluster_data['openrouter_key'] = openrouter_key
|
392 |
-
|
393 |
# Generate preliminary plan
|
394 |
logger.info("Generating preliminary plan...")
|
395 |
plan = generate_preliminary_plan(cluster_data)
|
@@ -464,5 +486,26 @@ def generate_from_csv_text():
|
|
464 |
logger.error(f"Error in generate_from_csv_text: {e}")
|
465 |
return jsonify({'error': str(e)}), 500
|
466 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
467 |
if __name__ == '__main__':
|
468 |
-
|
|
|
|
1 |
+
from flask import Flask, request, jsonify, Response, stream_with_context
|
2 |
+
from flask_cors import CORS
|
3 |
import os
|
4 |
from dotenv import load_dotenv
|
5 |
import requests
|
|
|
8 |
import ssl
|
9 |
import logging
|
10 |
from web_scraper import research_topic
|
11 |
+
import queue
|
12 |
+
import threading
|
13 |
|
14 |
+
# Create a queue for log messages
|
15 |
+
log_queue = queue.Queue()
|
16 |
+
|
17 |
+
# Custom log handler that puts messages in the queue
|
18 |
+
class QueueHandler(logging.Handler):
|
19 |
+
def emit(self, record):
|
20 |
+
log_entry = self.format(record)
|
21 |
+
log_queue.put(log_entry)
|
22 |
+
|
23 |
+
# Set up logging with the custom handler
|
24 |
+
logger = logging.getLogger()
|
25 |
+
queue_handler = QueueHandler()
|
26 |
+
queue_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
|
27 |
+
logger.addHandler(queue_handler)
|
28 |
+
logger.setLevel(logging.INFO)
|
29 |
|
30 |
app = Flask(__name__)
|
31 |
+
# Enable CORS with specific settings
|
32 |
+
CORS(app, resources={
|
33 |
+
r"/*": {
|
34 |
+
"origins": "*",
|
35 |
+
"methods": ["GET", "POST", "OPTIONS"],
|
36 |
+
"allow_headers": ["Content-Type", "Authorization"]
|
37 |
+
}
|
38 |
+
})
|
39 |
|
40 |
# Configuration
|
41 |
OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
|
|
|
97 |
},
|
98 |
timeout=60
|
99 |
)
|
100 |
+
|
101 |
logger.info(f"OpenRouter API Response: {response.text}")
|
102 |
+
|
103 |
if response.status_code != 200:
|
104 |
raise Exception(f"OpenRouter API error: {response.text}")
|
105 |
+
|
106 |
response_data = response.json()
|
107 |
if 'choices' not in response_data:
|
108 |
raise Exception(f"Unexpected API response format: {response_data}")
|
109 |
+
|
110 |
return response_data['choices'][0]['message']['content']
|
111 |
except Exception as e:
|
112 |
logger.error(f"Error in generate_preliminary_plan: {e}")
|
|
|
117 |
try:
|
118 |
# Extract key points from plan to create search queries
|
119 |
plan_lines = [line.strip('* -').strip() for line in plan.split('\n') if line.strip()]
|
120 |
+
|
121 |
# Take only the first 3 points
|
122 |
plan_lines = plan_lines[:3]
|
123 |
logger.info(f"Researching top 3 points: {plan_lines}")
|
124 |
+
|
125 |
all_research = []
|
126 |
+
|
127 |
# Research each main point in the plan (limited to 3 points)
|
128 |
for point in plan_lines:
|
129 |
if not point: # Skip empty lines
|
130 |
continue
|
131 |
+
|
132 |
# Get research results including both web content and AI analysis
|
133 |
# Using 5 sites per point for more comprehensive research
|
134 |
results = research_topic(point, num_sites=5, openrouter_key=openrouter_key)
|
135 |
+
|
136 |
if results['success']:
|
137 |
all_research.append({
|
138 |
'topic': point,
|
139 |
'analysis': results['analysis'],
|
140 |
'sources': results['sources']
|
141 |
})
|
142 |
+
|
143 |
# Format all research into a comprehensive markdown document
|
144 |
formatted_research = "# Research Results\n\n"
|
145 |
+
|
146 |
for research in all_research:
|
147 |
formatted_research += f"## {research['topic']}\n\n"
|
148 |
formatted_research += f"{research['analysis']}\n\n"
|
149 |
formatted_research += "### Sources Referenced\n\n"
|
150 |
+
|
151 |
for source in research['sources']:
|
152 |
formatted_research += f"- [{source['title']}]({source['source']})\n"
|
153 |
if source['meta_info']['description']:
|
154 |
formatted_research += f" {source['meta_info']['description']}\n"
|
155 |
+
|
156 |
formatted_research += "\n---\n\n"
|
157 |
+
|
158 |
return formatted_research
|
159 |
+
|
160 |
except Exception as e:
|
161 |
logger.error(f"Error in do_research: {e}")
|
162 |
raise
|
|
|
165 |
def generate_blog():
|
166 |
try:
|
167 |
logger.info("Starting blog generation process for multiple clusters...")
|
168 |
+
|
169 |
# Initialize handlers
|
170 |
blog_gen = BlogGenerator(OPENAI_API_KEY, OPENROUTER_API_KEY)
|
171 |
csv_handler = CSVHandler()
|
172 |
|
173 |
generated_blogs = []
|
174 |
+
|
175 |
# Get all available clusters
|
176 |
all_clusters = csv_handler.get_all_clusters()
|
177 |
+
|
178 |
for cluster_data in all_clusters:
|
179 |
try:
|
180 |
logger.info(f"Processing cluster with primary keyword: {cluster_data['Primary Keyword']}")
|
181 |
+
|
182 |
# 2. Generate preliminary plan
|
183 |
logger.info("Generating preliminary plan...")
|
184 |
plan = generate_preliminary_plan(cluster_data)
|
|
|
288 |
try:
|
289 |
if 'file' not in request.files:
|
290 |
return jsonify({'error': 'No file uploaded'}), 400
|
291 |
+
|
292 |
file = request.files['file']
|
293 |
if file.filename == '':
|
294 |
return jsonify({'error': 'No file selected'}), 400
|
295 |
+
|
296 |
# Read and decode the CSV content
|
297 |
csv_content = file.read().decode('utf-8')
|
298 |
+
|
299 |
# Initialize handlers
|
300 |
blog_gen = BlogGenerator(OPENAI_API_KEY, OPENROUTER_API_KEY)
|
301 |
csv_handler = CSVHandler()
|
302 |
+
|
303 |
# Process the uploaded CSV
|
304 |
clusters = csv_handler.process_uploaded_csv(csv_content)
|
305 |
+
|
306 |
if not clusters:
|
307 |
return jsonify({'error': 'No valid clusters found in CSV'}), 400
|
308 |
+
|
309 |
generated_blogs = []
|
310 |
+
|
311 |
# Process each cluster
|
312 |
for cluster_data in clusters:
|
313 |
try:
|
314 |
logger.info(f"Processing cluster with primary keyword: {cluster_data['Primary Keyword']}")
|
315 |
+
|
316 |
# Generate preliminary plan
|
317 |
plan = generate_preliminary_plan(cluster_data)
|
318 |
+
|
319 |
# Do research
|
320 |
research = do_research(plan, OPENROUTER_API_KEY)
|
321 |
+
|
322 |
# Create detailed plan
|
323 |
detailed_plan = blog_gen.create_detailed_plan(cluster_data, plan, research)
|
324 |
+
|
325 |
# Write blog post
|
326 |
blog_content = blog_gen.write_blog_post(detailed_plan, cluster_data)
|
327 |
+
|
328 |
# Add internal links
|
329 |
previous_posts = csv_handler.get_previous_posts()
|
330 |
blog_content = blog_gen.add_internal_links(blog_content, previous_posts)
|
331 |
+
|
332 |
# Convert to HTML
|
333 |
cover_image_url = blog_gen.get_cover_image(cluster_data['Primary Keyword'])
|
334 |
html_content = blog_gen.convert_to_html(blog_content, cover_image_url)
|
335 |
+
|
336 |
# Generate metadata
|
337 |
metadata = blog_gen.generate_metadata(blog_content, cluster_data['Primary Keyword'], cluster_data)
|
338 |
+
|
339 |
# Get cover image
|
340 |
cover_image_url = blog_gen.get_cover_image(metadata['title'])
|
341 |
+
|
342 |
# Create blog post data
|
343 |
blog_post_data = {
|
344 |
'title': metadata['title'],
|
|
|
351 |
'research': research,
|
352 |
'detailed_plan': detailed_plan
|
353 |
}
|
354 |
+
|
355 |
generated_blogs.append({
|
356 |
'status': 'success',
|
357 |
'message': f"Blog post generated successfully for {cluster_data['Primary Keyword']}",
|
358 |
'data': blog_post_data
|
359 |
})
|
360 |
+
|
361 |
except Exception as e:
|
362 |
logger.error(f"Error processing cluster {cluster_data['Primary Keyword']}: {e}")
|
363 |
generated_blogs.append({
|
|
|
365 |
'message': f"Failed to generate blog post for {cluster_data['Primary Keyword']}",
|
366 |
'error': str(e)
|
367 |
})
|
368 |
+
|
369 |
return jsonify({
|
370 |
'status': 'success',
|
371 |
'message': f'Generated {len(generated_blogs)} blog posts from uploaded CSV',
|
372 |
'blogs': generated_blogs
|
373 |
})
|
374 |
+
|
375 |
except Exception as e:
|
376 |
logger.error(f"Error in generate_from_csv: {e}")
|
377 |
return jsonify({'error': str(e)}), 500
|
378 |
+
|
379 |
|
380 |
@app.route('/generate-from-csv-text', methods=['POST'])
|
381 |
def generate_from_csv_text():
|
382 |
try:
|
383 |
logger.info("Starting blog generation process for multiple clusters...")
|
384 |
+
|
385 |
# Get CSV content and OpenRouter API key from request JSON
|
386 |
data = request.get_json()
|
387 |
if not data or 'csv_content' not in data:
|
388 |
return jsonify({'error': 'No CSV content provided'}), 400
|
389 |
if 'openrouter_key' not in data:
|
390 |
return jsonify({'error': 'OpenRouter API key is required'}), 400
|
391 |
+
|
392 |
csv_content = data['csv_content']
|
393 |
openrouter_key = data['openrouter_key']
|
394 |
+
|
395 |
# Initialize handlers with the provided OpenRouter key
|
396 |
blog_gen = BlogGenerator(OPENAI_API_KEY, openrouter_key)
|
397 |
csv_handler = CSVHandler()
|
398 |
+
|
399 |
# Process the CSV text
|
400 |
clusters = csv_handler.process_csv_text(csv_content)
|
401 |
+
|
402 |
if not clusters:
|
403 |
return jsonify({'error': 'No valid clusters found in CSV'}), 400
|
404 |
+
|
405 |
generated_blogs = []
|
406 |
+
|
407 |
# Process each cluster
|
408 |
for cluster_data in clusters:
|
409 |
try:
|
410 |
logger.info(f"Processing cluster with primary keyword: {cluster_data['Primary Keyword']}")
|
411 |
+
|
412 |
# Add OpenRouter key to cluster_data for use in functions
|
413 |
cluster_data['openrouter_key'] = openrouter_key
|
414 |
+
|
415 |
# Generate preliminary plan
|
416 |
logger.info("Generating preliminary plan...")
|
417 |
plan = generate_preliminary_plan(cluster_data)
|
|
|
486 |
logger.error(f"Error in generate_from_csv_text: {e}")
|
487 |
return jsonify({'error': str(e)}), 500
|
488 |
|
489 |
+
@app.route('/logs/stream')
|
490 |
+
def stream_logs():
|
491 |
+
def generate():
|
492 |
+
while True:
|
493 |
+
try:
|
494 |
+
# Get log message from queue, timeout after 1 second
|
495 |
+
log_message = log_queue.get(timeout=1)
|
496 |
+
yield f"data: {log_message}\n\n"
|
497 |
+
except queue.Empty:
|
498 |
+
# Send a heartbeat to keep the connection alive
|
499 |
+
yield "data: heartbeat\n\n"
|
500 |
+
except GeneratorExit:
|
501 |
+
break
|
502 |
+
|
503 |
+
response = Response(stream_with_context(generate()), mimetype='text/event-stream')
|
504 |
+
response.headers['Cache-Control'] = 'no-cache'
|
505 |
+
response.headers['Connection'] = 'keep-alive'
|
506 |
+
response.headers['Access-Control-Allow-Origin'] = '*'
|
507 |
+
return response
|
508 |
+
|
509 |
if __name__ == '__main__':
|
510 |
+
logger.info("Starting Flask API server...")
|
511 |
+
app.run(host='127.0.0.1', port=5001, debug=True, threaded=True)
|