yashgori20 commited on
Commit
96dcaeb
Β·
1 Parent(s): 780dd63
Files changed (5) hide show
  1. .gitignore +158 -0
  2. app.py +110 -126
  3. requirements.txt +3 -2
  4. templates/index.html +350 -0
  5. templates/report.html +212 -0
.gitignore ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ pip-wheel-metadata/
24
+ share/python-wheels/
25
+ *.egg-info/
26
+ .installed.cfg
27
+ *.egg
28
+ MANIFEST
29
+
30
+ # PyInstaller
31
+ *.manifest
32
+ *.spec
33
+
34
+ # Installer logs
35
+ pip-log.txt
36
+ pip-delete-this-directory.txt
37
+
38
+ # Unit test / coverage reports
39
+ htmlcov/
40
+ .tox/
41
+ .nox/
42
+ .coverage
43
+ .coverage.*
44
+ .cache
45
+ nosetests.xml
46
+ coverage.xml
47
+ *.cover
48
+ *.py,cover
49
+ .hypothesis/
50
+ .pytest_cache/
51
+
52
+ # Translations
53
+ *.mo
54
+ *.pot
55
+
56
+ # Django stuff:
57
+ *.log
58
+ local_settings.py
59
+ db.sqlite3
60
+ db.sqlite3-journal
61
+
62
+ # Flask stuff:
63
+ instance/
64
+ .webassets-cache
65
+
66
+ # Scrapy stuff:
67
+ .scrapy
68
+
69
+ # Sphinx documentation
70
+ docs/_build/
71
+
72
+ # PyBuilder
73
+ target/
74
+
75
+ # Jupyter Notebook
76
+ .ipynb_checkpoints
77
+
78
+ # IPython
79
+ profile_default/
80
+ ipython_config.py
81
+
82
+ # pyenv
83
+ .python-version
84
+
85
+ # pipenv
86
+ Pipfile.lock
87
+
88
+ # PEP 582
89
+ __pypackages__/
90
+
91
+ # Celery stuff
92
+ celerybeat-schedule
93
+ celerybeat.pid
94
+
95
+ # SageMath parsed files
96
+ *.sage.py
97
+
98
+ # Environments
99
+ .env
100
+ .venv
101
+ env/
102
+ venv/
103
+ ENV/
104
+ env.bak/
105
+ venv.bak/
106
+
107
+ # Spyder project settings
108
+ .spyderproject
109
+ .spyproject
110
+
111
+ # Rope project settings
112
+ .ropeproject
113
+
114
+ # mkdocs documentation
115
+ /site
116
+
117
+ # mypy
118
+ .mypy_cache/
119
+ .dmypy.json
120
+ dmypy.json
121
+
122
+ # Pyre type checker
123
+ .pyre/
124
+
125
+ # Claude Code specific
126
+ .claude/
127
+ claude.md
128
+ CLAUDE.md
129
+ *.claude.md
130
+
131
+ # OS specific
132
+ .DS_Store
133
+ .DS_Store?
134
+ ._*
135
+ .Spotlight-V100
136
+ .Trashes
137
+ ehthumbs.db
138
+ Thumbs.db
139
+
140
+ # IDE specific
141
+ .vscode/
142
+ .idea/
143
+ *.swp
144
+ *.swo
145
+ *~
146
+
147
+ # Temporary files
148
+ *.tmp
149
+ *.temp
150
+ temp/
151
+ tmp/
152
+
153
+ # Log files
154
+ *.log
155
+
156
+ # Local configuration files
157
+ config.local.py
158
+ settings.local.py
app.py CHANGED
@@ -1,161 +1,145 @@
1
- import streamlit as st
2
  import validators
 
 
 
3
  from modules.technical_seo import TechnicalSEOModule
4
  from modules.content_audit import ContentAuditModule
5
  from report_generator import ReportGenerator
 
6
 
7
- # Try to import PDF generator, fallback if not available
8
- try:
9
- from simple_pdf_generator import SimplePDFGenerator, create_browser_pdf_instructions
10
- pdf_gen = SimplePDFGenerator()
11
- PDF_AVAILABLE = pdf_gen.available
12
- if not PDF_AVAILABLE:
13
- browser_instructions = create_browser_pdf_instructions()
14
- except ImportError as e:
15
- print(f"PDF generation unavailable: {e}")
16
- PDF_AVAILABLE = False
17
- browser_instructions = "PDF generation not available"
18
 
19
- def main():
20
- st.set_page_config(
21
- page_title="SEO Report Generator",
22
- page_icon="πŸ”",
23
- layout="wide"
24
- )
25
-
26
- st.title("πŸ” One-Click SEO Report Generator")
27
- st.markdown("Generate comprehensive SEO reports for any website")
28
-
29
- # Input section
30
- col1, col2 = st.columns([2, 1])
31
-
32
- with col1:
33
- url = st.text_input(
34
- "Website URL",
35
- placeholder="https://example.com",
36
- help="Enter the website URL you want to analyze"
37
- )
38
 
39
- competitors = st.text_area(
40
- "Competitor URLs (Optional)",
41
- placeholder="https://competitor1.com\nhttps://competitor2.com",
42
- help="Enter competitor URLs, one per line"
43
- )
44
-
45
- with col2:
46
- st.markdown("### Report Options")
47
- include_charts = st.checkbox("Include Charts", value=True)
48
- include_competitors = st.checkbox("Include Competitor Analysis", value=True)
49
-
50
- # Generate report button
51
- if st.button("Generate SEO Report", type="primary"):
52
  if not url:
53
- st.error("Please enter a website URL")
54
- return
55
 
56
  if not validators.url(url):
57
- st.error("Please enter a valid URL")
58
- return
59
-
60
- # Process competitor URLs
61
- competitor_list = []
62
- if competitors and include_competitors:
63
- competitor_list = [c.strip() for c in competitors.split('\n') if c.strip() and validators.url(c.strip())]
64
 
65
- # Generate report
66
- with st.spinner("Generating SEO report... This may take a few minutes."):
67
- generate_report(url, competitor_list, include_charts)
68
-
69
- def generate_report(url, competitors, include_charts):
70
- try:
71
- # Initialize report generator
72
- report_gen = ReportGenerator()
73
 
74
- # Progress tracking
75
- progress_bar = st.progress(0)
76
- status_text = st.empty()
 
 
 
77
 
78
  # Technical SEO Analysis
79
- status_text.text("Analyzing technical SEO...")
80
- progress_bar.progress(20)
81
- technical_module = TechnicalSEOModule()
82
  technical_data = technical_module.analyze(url)
83
 
84
  # Content Audit
85
- status_text.text("Performing content audit...")
86
- progress_bar.progress(50)
87
- content_module = ContentAuditModule()
88
  content_data = content_module.analyze(url)
89
 
90
  # Competitor Analysis
91
  competitor_data = []
92
- if competitors:
93
- status_text.text("Analyzing competitors...")
94
- progress_bar.progress(70)
95
- for comp_url in competitors:
96
- comp_technical = technical_module.analyze(comp_url)
97
- comp_content = content_module.analyze(comp_url, quick_scan=True)
98
- competitor_data.append({
99
- 'url': comp_url,
100
- 'technical': comp_technical,
101
- 'content': comp_content
102
- })
103
-
104
- # Generate report
105
- status_text.text("Generating report...")
106
- progress_bar.progress(90)
107
 
 
108
  report_html = report_gen.generate_html_report(
109
  url=url,
110
  technical_data=technical_data,
111
  content_data=content_data,
112
  competitor_data=competitor_data,
113
- include_charts=include_charts
114
  )
115
 
116
- progress_bar.progress(100)
117
- status_text.text("Report generated successfully!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
 
119
- # Display report
120
- st.success("SEO Report Generated Successfully!")
 
 
121
 
122
- # Report preview
123
- st.markdown("### Report Preview")
124
- st.components.v1.html(report_html, height=800, scrolling=True)
125
 
126
- # Download buttons
127
- col1, col2 = st.columns(2)
128
- with col1:
129
- st.download_button(
130
- label="πŸ“„ Download HTML Report",
131
- data=report_html,
132
- file_name=f"seo_report_{url.replace('https://', '').replace('http://', '').replace('/', '_')}.html",
133
- mime="text/html"
134
- )
135
 
136
- with col2:
137
- # Generate PDF if available
138
- if PDF_AVAILABLE:
139
- try:
140
- pdf_data = pdf_gen.generate_pdf(report_html)
141
-
142
- st.download_button(
143
- label="πŸ“‘ Download PDF Report",
144
- data=pdf_data,
145
- file_name=f"seo_report_{url.replace('https://', '').replace('http://', '').replace('/', '_')}.pdf",
146
- mime="application/pdf"
147
- )
148
- except Exception as e:
149
- st.error(f"PDF generation failed: {str(e)}")
150
- st.info("HTML report is available for download")
151
- else:
152
- st.info("πŸ’‘ Create PDF from HTML Report")
153
- with st.expander("πŸ“– Instructions"):
154
- st.markdown(browser_instructions)
155
-
156
  except Exception as e:
157
- st.error(f"Error generating report: {str(e)}")
158
- st.exception(e)
159
 
160
- if __name__ == "__main__":
161
- main()
 
1
+ from flask import Flask, render_template, request, jsonify, send_file, redirect, url_for
2
  import validators
3
+ import os
4
+ import tempfile
5
+ import uuid
6
  from modules.technical_seo import TechnicalSEOModule
7
  from modules.content_audit import ContentAuditModule
8
  from report_generator import ReportGenerator
9
+ from simple_pdf_generator import SimplePDFGenerator
10
 
11
+ app = Flask(__name__)
12
+ app.secret_key = 'seo_report_generator_2024'
 
 
 
 
 
 
 
 
 
13
 
14
+ # Initialize modules
15
+ technical_module = TechnicalSEOModule()
16
+ content_module = ContentAuditModule()
17
+ report_gen = ReportGenerator()
18
+ pdf_gen = SimplePDFGenerator()
19
+
20
+ # Store for generated reports (in production, use database)
21
+ reports_store = {}
22
+
23
+ @app.route('/')
24
+ def index():
25
+ return render_template('index.html')
26
+
27
+ @app.route('/generate', methods=['POST'])
28
+ def generate_report():
29
+ try:
30
+ data = request.json
31
+ url = data.get('url', '').strip()
32
+ competitors = data.get('competitors', [])
33
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  if not url:
35
+ return jsonify({'error': 'Website URL is required'}), 400
 
36
 
37
  if not validators.url(url):
38
+ return jsonify({'error': 'Please enter a valid URL'}), 400
 
 
 
 
 
 
39
 
40
+ # Generate unique report ID
41
+ report_id = str(uuid.uuid4())
 
 
 
 
 
 
42
 
43
+ # Validate competitor URLs
44
+ competitor_list = []
45
+ for comp in competitors:
46
+ comp = comp.strip()
47
+ if comp and validators.url(comp):
48
+ competitor_list.append(comp)
49
 
50
  # Technical SEO Analysis
 
 
 
51
  technical_data = technical_module.analyze(url)
52
 
53
  # Content Audit
 
 
 
54
  content_data = content_module.analyze(url)
55
 
56
  # Competitor Analysis
57
  competitor_data = []
58
+ for comp_url in competitor_list:
59
+ comp_technical = technical_module.analyze(comp_url)
60
+ comp_content = content_module.analyze(comp_url, quick_scan=True)
61
+ competitor_data.append({
62
+ 'url': comp_url,
63
+ 'technical': comp_technical,
64
+ 'content': comp_content
65
+ })
 
 
 
 
 
 
 
66
 
67
+ # Generate HTML report
68
  report_html = report_gen.generate_html_report(
69
  url=url,
70
  technical_data=technical_data,
71
  content_data=content_data,
72
  competitor_data=competitor_data,
73
+ include_charts=True
74
  )
75
 
76
+ # Store report
77
+ reports_store[report_id] = {
78
+ 'url': url,
79
+ 'html': report_html,
80
+ 'technical_data': technical_data,
81
+ 'content_data': content_data,
82
+ 'competitor_data': competitor_data
83
+ }
84
+
85
+ return jsonify({
86
+ 'success': True,
87
+ 'report_id': report_id,
88
+ 'redirect_url': f'/report/{report_id}'
89
+ })
90
+
91
+ except Exception as e:
92
+ return jsonify({'error': f'Error generating report: {str(e)}'}), 500
93
+
94
+ @app.route('/report/<report_id>')
95
+ def view_report(report_id):
96
+ if report_id not in reports_store:
97
+ return redirect(url_for('index'))
98
+
99
+ report_data = reports_store[report_id]
100
+ return render_template('report.html',
101
+ report_html=report_data['html'],
102
+ report_id=report_id,
103
+ url=report_data['url'])
104
+
105
+ @app.route('/download/<report_id>')
106
+ def download_html(report_id):
107
+ if report_id not in reports_store:
108
+ return jsonify({'error': 'Report not found'}), 404
109
+
110
+ report_data = reports_store[report_id]
111
+
112
+ # Create temporary file
113
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.html', delete=False) as f:
114
+ f.write(report_data['html'])
115
+ temp_path = f.name
116
+
117
+ filename = f"seo_report_{report_data['url'].replace('https://', '').replace('http://', '').replace('/', '_')}.html"
118
+
119
+ return send_file(temp_path, as_attachment=True, download_name=filename, mimetype='text/html')
120
+
121
+ @app.route('/download-pdf/<report_id>')
122
+ def download_pdf(report_id):
123
+ if report_id not in reports_store:
124
+ return jsonify({'error': 'Report not found'}), 404
125
+
126
+ try:
127
+ report_data = reports_store[report_id]
128
+
129
+ # Generate PDF
130
+ pdf_data = pdf_gen.generate_pdf(report_data['html'])
131
 
132
+ # Create temporary file
133
+ with tempfile.NamedTemporaryFile(suffix='.pdf', delete=False) as f:
134
+ f.write(pdf_data)
135
+ temp_path = f.name
136
 
137
+ filename = f"seo_report_{report_data['url'].replace('https://', '').replace('http://', '').replace('/', '_')}.pdf"
 
 
138
 
139
+ return send_file(temp_path, as_attachment=True, download_name=filename, mimetype='application/pdf')
 
 
 
 
 
 
 
 
140
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  except Exception as e:
142
+ return jsonify({'error': f'PDF generation failed: {str(e)}'}), 500
 
143
 
144
+ if __name__ == '__main__':
145
+ app.run(debug=True, host='0.0.0.0', port=7860)
requirements.txt CHANGED
@@ -1,4 +1,4 @@
1
- streamlit
2
  requests
3
  beautifulsoup4
4
  pandas
@@ -6,4 +6,5 @@ plotly
6
  jinja2
7
  validators
8
  urllib3
9
- lxml
 
 
1
+ flask
2
  requests
3
  beautifulsoup4
4
  pandas
 
6
  jinja2
7
  validators
8
  urllib3
9
+ lxml
10
+ uuid
templates/index.html ADDED
@@ -0,0 +1,350 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>SEO Report Generator</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ background: #0a0a0a;
16
+ color: #ffffff;
17
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
18
+ min-height: 100vh;
19
+ background-image: radial-gradient(circle at 20% 20%, rgba(120, 119, 198, 0.3) 0%, transparent 50%),
20
+ radial-gradient(circle at 80% 80%, rgba(255, 119, 198, 0.3) 0%, transparent 50%);
21
+ }
22
+
23
+ .container {
24
+ max-width: 1200px;
25
+ margin: 0 auto;
26
+ padding: 40px 20px;
27
+ }
28
+
29
+ .header {
30
+ text-align: center;
31
+ margin-bottom: 60px;
32
+ }
33
+
34
+ .logo {
35
+ font-size: 48px;
36
+ font-weight: 700;
37
+ margin-bottom: 10px;
38
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
39
+ -webkit-background-clip: text;
40
+ -webkit-text-fill-color: transparent;
41
+ background-clip: text;
42
+ }
43
+
44
+ .subtitle {
45
+ font-size: 18px;
46
+ color: #888;
47
+ margin-bottom: 40px;
48
+ }
49
+
50
+ .main-card {
51
+ background: rgba(255, 255, 255, 0.02);
52
+ border: 1px solid rgba(255, 255, 255, 0.1);
53
+ border-radius: 16px;
54
+ padding: 40px;
55
+ backdrop-filter: blur(10px);
56
+ margin-bottom: 40px;
57
+ }
58
+
59
+ .form-group {
60
+ margin-bottom: 24px;
61
+ }
62
+
63
+ .form-label {
64
+ display: block;
65
+ margin-bottom: 8px;
66
+ font-weight: 600;
67
+ color: #fff;
68
+ }
69
+
70
+ .form-input {
71
+ width: 100%;
72
+ padding: 16px;
73
+ background: rgba(255, 255, 255, 0.05);
74
+ border: 1px solid rgba(255, 255, 255, 0.1);
75
+ border-radius: 8px;
76
+ color: #fff;
77
+ font-size: 16px;
78
+ transition: all 0.3s ease;
79
+ }
80
+
81
+ .form-input:focus {
82
+ outline: none;
83
+ border-color: #667eea;
84
+ background: rgba(255, 255, 255, 0.08);
85
+ }
86
+
87
+ .form-input::placeholder {
88
+ color: #666;
89
+ }
90
+
91
+ .textarea {
92
+ min-height: 120px;
93
+ resize: vertical;
94
+ font-family: inherit;
95
+ }
96
+
97
+ .generate-btn {
98
+ width: 100%;
99
+ padding: 18px;
100
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
101
+ border: none;
102
+ border-radius: 8px;
103
+ color: white;
104
+ font-size: 18px;
105
+ font-weight: 600;
106
+ cursor: pointer;
107
+ transition: transform 0.2s ease;
108
+ margin-top: 20px;
109
+ }
110
+
111
+ .generate-btn:hover {
112
+ transform: translateY(-2px);
113
+ }
114
+
115
+ .generate-btn:disabled {
116
+ opacity: 0.6;
117
+ cursor: not-allowed;
118
+ transform: none;
119
+ }
120
+
121
+ .options-grid {
122
+ display: grid;
123
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
124
+ gap: 24px;
125
+ margin-bottom: 40px;
126
+ }
127
+
128
+ .option-card {
129
+ background: rgba(255, 255, 255, 0.02);
130
+ border: 1px solid rgba(255, 255, 255, 0.1);
131
+ border-radius: 12px;
132
+ padding: 24px;
133
+ text-align: center;
134
+ transition: all 0.3s ease;
135
+ cursor: pointer;
136
+ }
137
+
138
+ .option-card:hover {
139
+ background: rgba(255, 255, 255, 0.05);
140
+ transform: translateY(-4px);
141
+ }
142
+
143
+ .option-icon {
144
+ font-size: 32px;
145
+ margin-bottom: 12px;
146
+ display: block;
147
+ }
148
+
149
+ .option-title {
150
+ font-size: 18px;
151
+ font-weight: 600;
152
+ margin-bottom: 8px;
153
+ }
154
+
155
+ .option-desc {
156
+ color: #888;
157
+ font-size: 14px;
158
+ }
159
+
160
+ .loading-overlay {
161
+ display: none;
162
+ position: fixed;
163
+ top: 0;
164
+ left: 0;
165
+ width: 100%;
166
+ height: 100%;
167
+ background: rgba(0, 0, 0, 0.8);
168
+ z-index: 1000;
169
+ justify-content: center;
170
+ align-items: center;
171
+ flex-direction: column;
172
+ }
173
+
174
+ .loading-spinner {
175
+ width: 60px;
176
+ height: 60px;
177
+ border: 3px solid rgba(255, 255, 255, 0.3);
178
+ border-top: 3px solid #667eea;
179
+ border-radius: 50%;
180
+ animation: spin 1s linear infinite;
181
+ margin-bottom: 20px;
182
+ }
183
+
184
+ .loading-text {
185
+ color: #fff;
186
+ font-size: 18px;
187
+ margin-bottom: 10px;
188
+ }
189
+
190
+ .loading-subtext {
191
+ color: #888;
192
+ font-size: 14px;
193
+ }
194
+
195
+ @keyframes spin {
196
+ 0% { transform: rotate(0deg); }
197
+ 100% { transform: rotate(360deg); }
198
+ }
199
+
200
+ .error-message {
201
+ background: rgba(220, 38, 38, 0.1);
202
+ border: 1px solid rgba(220, 38, 38, 0.3);
203
+ border-radius: 8px;
204
+ padding: 16px;
205
+ color: #fca5a5;
206
+ margin-top: 16px;
207
+ display: none;
208
+ }
209
+
210
+ @media (max-width: 768px) {
211
+ .container {
212
+ padding: 20px 16px;
213
+ }
214
+
215
+ .main-card {
216
+ padding: 24px;
217
+ }
218
+
219
+ .logo {
220
+ font-size: 36px;
221
+ }
222
+ }
223
+ </style>
224
+ </head>
225
+ <body>
226
+ <div class="container">
227
+ <div class="header">
228
+ <h1 class="logo">πŸ” SEO Report Generator</h1>
229
+ <p class="subtitle">Professional SEO Analysis & Reporting</p>
230
+ </div>
231
+
232
+ <div class="options-grid">
233
+ <div class="option-card">
234
+ <span class="option-icon">⚑</span>
235
+ <div class="option-title">Technical SEO</div>
236
+ <div class="option-desc">PageSpeed insights, Core Web Vitals</div>
237
+ </div>
238
+ <div class="option-card">
239
+ <span class="option-icon">πŸ“Š</span>
240
+ <div class="option-title">Content Audit</div>
241
+ <div class="option-desc">Metadata analysis, content quality</div>
242
+ </div>
243
+ <div class="option-card">
244
+ <span class="option-icon">πŸ†</span>
245
+ <div class="option-title">Competitor Analysis</div>
246
+ <div class="option-desc">Performance benchmarking</div>
247
+ </div>
248
+ <div class="option-card">
249
+ <span class="option-icon">πŸ“‘</span>
250
+ <div class="option-title">Professional Reports</div>
251
+ <div class="option-desc">HTML & PDF export</div>
252
+ </div>
253
+ </div>
254
+
255
+ <div class="main-card">
256
+ <form id="seoForm">
257
+ <div class="form-group">
258
+ <label class="form-label" for="url">Website URL</label>
259
+ <input type="url" id="url" name="url" class="form-input"
260
+ placeholder="https://example.com" required>
261
+ </div>
262
+
263
+ <div class="form-group">
264
+ <label class="form-label" for="competitors">Competitor URLs (Optional)</label>
265
+ <textarea id="competitors" name="competitors" class="form-input textarea"
266
+ placeholder="https://competitor1.com&#10;https://competitor2.com&#10;One URL per line"></textarea>
267
+ </div>
268
+
269
+ <button type="submit" class="generate-btn" id="generateBtn">
270
+ Generate SEO Report
271
+ </button>
272
+
273
+ <div class="error-message" id="errorMessage"></div>
274
+ </form>
275
+ </div>
276
+ </div>
277
+
278
+ <div class="loading-overlay" id="loadingOverlay">
279
+ <div class="loading-spinner"></div>
280
+ <div class="loading-text" id="loadingText">Generating SEO report...</div>
281
+ <div class="loading-subtext" id="loadingSubtext">This may take a few minutes</div>
282
+ </div>
283
+
284
+ <script>
285
+ document.getElementById('seoForm').addEventListener('submit', async function(e) {
286
+ e.preventDefault();
287
+
288
+ const url = document.getElementById('url').value.trim();
289
+ const competitors = document.getElementById('competitors').value
290
+ .split('\n')
291
+ .map(c => c.trim())
292
+ .filter(c => c);
293
+
294
+ const loadingOverlay = document.getElementById('loadingOverlay');
295
+ const errorMessage = document.getElementById('errorMessage');
296
+ const loadingText = document.getElementById('loadingText');
297
+ const loadingSubtext = document.getElementById('loadingSubtext');
298
+
299
+ errorMessage.style.display = 'none';
300
+ loadingOverlay.style.display = 'flex';
301
+
302
+ const loadingMessages = [
303
+ { text: 'Analyzing technical SEO...', subtext: 'Checking PageSpeed insights' },
304
+ { text: 'Performing content audit...', subtext: 'Crawling website content' },
305
+ { text: 'Analyzing competitors...', subtext: 'Comparing performance metrics' },
306
+ { text: 'Generating professional report...', subtext: 'Creating charts and visualizations' }
307
+ ];
308
+
309
+ let messageIndex = 0;
310
+ const messageInterval = setInterval(() => {
311
+ if (messageIndex < loadingMessages.length) {
312
+ loadingText.textContent = loadingMessages[messageIndex].text;
313
+ loadingSubtext.textContent = loadingMessages[messageIndex].subtext;
314
+ messageIndex++;
315
+ }
316
+ }, 3000);
317
+
318
+ try {
319
+ const response = await fetch('/generate', {
320
+ method: 'POST',
321
+ headers: {
322
+ 'Content-Type': 'application/json',
323
+ },
324
+ body: JSON.stringify({
325
+ url: url,
326
+ competitors: competitors
327
+ })
328
+ });
329
+
330
+ const data = await response.json();
331
+
332
+ clearInterval(messageInterval);
333
+ loadingOverlay.style.display = 'none';
334
+
335
+ if (data.success) {
336
+ window.location.href = data.redirect_url;
337
+ } else {
338
+ errorMessage.textContent = data.error;
339
+ errorMessage.style.display = 'block';
340
+ }
341
+ } catch (error) {
342
+ clearInterval(messageInterval);
343
+ loadingOverlay.style.display = 'none';
344
+ errorMessage.textContent = 'Network error. Please try again.';
345
+ errorMessage.style.display = 'block';
346
+ }
347
+ });
348
+ </script>
349
+ </body>
350
+ </html>
templates/report.html ADDED
@@ -0,0 +1,212 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>SEO Report - {{ url }}</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ background: #0a0a0a;
16
+ color: #ffffff;
17
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
18
+ min-height: 100vh;
19
+ }
20
+
21
+ .header {
22
+ background: rgba(255, 255, 255, 0.02);
23
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
24
+ padding: 20px 0;
25
+ position: sticky;
26
+ top: 0;
27
+ backdrop-filter: blur(10px);
28
+ z-index: 100;
29
+ }
30
+
31
+ .header-content {
32
+ max-width: 1200px;
33
+ margin: 0 auto;
34
+ padding: 0 20px;
35
+ display: flex;
36
+ justify-content: space-between;
37
+ align-items: center;
38
+ }
39
+
40
+ .header-title {
41
+ font-size: 24px;
42
+ font-weight: 700;
43
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
44
+ -webkit-background-clip: text;
45
+ -webkit-text-fill-color: transparent;
46
+ background-clip: text;
47
+ }
48
+
49
+ .header-url {
50
+ color: #888;
51
+ font-size: 16px;
52
+ }
53
+
54
+ .download-buttons {
55
+ display: flex;
56
+ gap: 12px;
57
+ }
58
+
59
+ .download-btn {
60
+ padding: 12px 24px;
61
+ border: 1px solid rgba(255, 255, 255, 0.2);
62
+ border-radius: 8px;
63
+ background: rgba(255, 255, 255, 0.05);
64
+ color: #fff;
65
+ text-decoration: none;
66
+ font-weight: 500;
67
+ transition: all 0.3s ease;
68
+ display: flex;
69
+ align-items: center;
70
+ gap: 8px;
71
+ }
72
+
73
+ .download-btn:hover {
74
+ background: rgba(255, 255, 255, 0.1);
75
+ transform: translateY(-2px);
76
+ }
77
+
78
+ .download-btn.primary {
79
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
80
+ border: none;
81
+ }
82
+
83
+ .back-btn {
84
+ padding: 12px 24px;
85
+ border: 1px solid rgba(255, 255, 255, 0.2);
86
+ border-radius: 8px;
87
+ background: transparent;
88
+ color: #fff;
89
+ text-decoration: none;
90
+ font-weight: 500;
91
+ transition: all 0.3s ease;
92
+ }
93
+
94
+ .back-btn:hover {
95
+ background: rgba(255, 255, 255, 0.1);
96
+ }
97
+
98
+ .report-container {
99
+ max-width: 1200px;
100
+ margin: 0 auto;
101
+ padding: 20px;
102
+ }
103
+
104
+ .report-content {
105
+ background: #fff;
106
+ border-radius: 12px;
107
+ overflow: hidden;
108
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
109
+ }
110
+
111
+ /* Override any purple/blue styling in the report */
112
+ .report-content :global(.card) {
113
+ border: 1px solid #e5e7eb !important;
114
+ background: #fff !important;
115
+ }
116
+
117
+ .report-content :global(.bg-primary) {
118
+ background-color: #374151 !important;
119
+ }
120
+
121
+ .report-content :global(.text-primary) {
122
+ color: #374151 !important;
123
+ }
124
+
125
+ .report-content :global(.border-primary) {
126
+ border-color: #d1d5db !important;
127
+ }
128
+
129
+ /* Clean professional styling for report content */
130
+ .report-content h1, .report-content h2, .report-content h3 {
131
+ color: #111827;
132
+ font-weight: 600;
133
+ }
134
+
135
+ .report-content .card {
136
+ background: #f9fafb;
137
+ border: 1px solid #e5e7eb;
138
+ border-radius: 8px;
139
+ margin-bottom: 20px;
140
+ }
141
+
142
+ @media (max-width: 768px) {
143
+ .header-content {
144
+ flex-direction: column;
145
+ gap: 16px;
146
+ text-align: center;
147
+ }
148
+
149
+ .download-buttons {
150
+ flex-wrap: wrap;
151
+ justify-content: center;
152
+ }
153
+
154
+ .report-container {
155
+ padding: 16px;
156
+ }
157
+ }
158
+ </style>
159
+ </head>
160
+ <body>
161
+ <div class="header">
162
+ <div class="header-content">
163
+ <div>
164
+ <div class="header-title">πŸ” SEO Report</div>
165
+ <div class="header-url">{{ url }}</div>
166
+ </div>
167
+ <div class="download-buttons">
168
+ <a href="/" class="back-btn">← Back to Generator</a>
169
+ <a href="/download/{{ report_id }}" class="download-btn">
170
+ πŸ“„ Download HTML
171
+ </a>
172
+ <a href="/download-pdf/{{ report_id }}" class="download-btn primary">
173
+ πŸ“‘ Download PDF
174
+ </a>
175
+ </div>
176
+ </div>
177
+ </div>
178
+
179
+ <div class="report-container">
180
+ <div class="report-content">
181
+ {{ report_html|safe }}
182
+ </div>
183
+ </div>
184
+
185
+ <script>
186
+ // Clean up any unwanted styling from the injected HTML
187
+ document.addEventListener('DOMContentLoaded', function() {
188
+ // Remove any purple/blue backgrounds or borders that might be in the report
189
+ const elements = document.querySelectorAll('[style*="purple"], [style*="blue"], [style*="#667eea"], [style*="#764ba2"]');
190
+ elements.forEach(el => {
191
+ if (el.style.backgroundColor) {
192
+ el.style.backgroundColor = '#f9fafb';
193
+ }
194
+ if (el.style.borderColor) {
195
+ el.style.borderColor = '#e5e7eb';
196
+ }
197
+ if (el.style.color && (el.style.color.includes('purple') || el.style.color.includes('blue'))) {
198
+ el.style.color = '#374151';
199
+ }
200
+ });
201
+
202
+ // Ensure all cards have clean styling
203
+ const cards = document.querySelectorAll('.card, [class*="card"]');
204
+ cards.forEach(card => {
205
+ card.style.backgroundColor = '#f9fafb';
206
+ card.style.border = '1px solid #e5e7eb';
207
+ card.style.borderRadius = '8px';
208
+ });
209
+ });
210
+ </script>
211
+ </body>
212
+ </html>