Update app.py
Browse files
app.py
CHANGED
@@ -33,15 +33,15 @@ def generate_prisma_flow_chart(records_db, records_other, duplicates, excluded_s
|
|
33 |
|
34 |
records_after_duplicates = records_db + records_other - duplicates
|
35 |
|
36 |
-
# Create SVG content for PRISMA flow chart with
|
37 |
meta_analysis_section = ''
|
38 |
if included_meta is not None:
|
39 |
meta_analysis_section = f'''
|
40 |
-
<line x1="400" y1="
|
41 |
-
<rect x="
|
42 |
-
<text x="400" y="
|
43 |
-
<text x="400" y="
|
44 |
-
<text x="400" y="
|
45 |
'''
|
46 |
|
47 |
svg_content = f'''
|
@@ -61,9 +61,11 @@ def generate_prisma_flow_chart(records_db, records_other, duplicates, excluded_s
|
|
61 |
font-family: Arial, sans-serif;
|
62 |
font-size: 14px;
|
63 |
text-anchor: middle;
|
|
|
64 |
}}
|
65 |
.box-title {{
|
66 |
font-weight: bold;
|
|
|
67 |
}}
|
68 |
</style>
|
69 |
|
@@ -73,67 +75,67 @@ def generate_prisma_flow_chart(records_db, records_other, duplicates, excluded_s
|
|
73 |
</marker>
|
74 |
</defs>
|
75 |
|
|
|
|
|
|
|
76 |
<!-- Identification Section -->
|
77 |
-
<rect x="
|
78 |
-
<text x="400" y="
|
79 |
-
<text x="400" y="
|
80 |
-
<text x="400" y="
|
81 |
|
82 |
-
<rect x="
|
83 |
-
<text x="
|
84 |
-
<text x="
|
85 |
-
<text x="
|
86 |
|
87 |
<!-- Arrows -->
|
88 |
-
<line x1="400" y1="
|
89 |
-
<line x1="
|
90 |
-
<line x1="
|
91 |
|
92 |
<!-- Screening Section -->
|
93 |
-
<rect x="
|
94 |
-
<text x="400" y="
|
95 |
-
<text x="400" y="
|
96 |
-
<text x="400" y="
|
97 |
|
98 |
-
<line x1="400" y1="
|
99 |
|
100 |
-
|
101 |
-
<
|
102 |
-
<text x="400" y="
|
103 |
-
<text x="400" y="
|
|
|
104 |
|
105 |
-
<rect x="
|
106 |
-
<text x="
|
107 |
-
<text x="
|
108 |
|
109 |
-
<line x1="400" y1="
|
110 |
-
<line x1="500" y1="
|
111 |
|
112 |
<!-- Eligibility Section -->
|
113 |
-
<rect x="
|
114 |
-
<text x="400" y="
|
115 |
-
<text x="400" y="
|
116 |
-
<text x="400" y="
|
117 |
|
118 |
-
<rect x="
|
119 |
-
<text x="
|
120 |
-
<text x="
|
121 |
-
<text x="700" y="460">(n = {excluded_fulltext})</text>
|
122 |
|
123 |
-
<line x1="400" y1="
|
124 |
-
<line x1="500" y1="
|
125 |
|
126 |
<!-- Included Section -->
|
127 |
-
<rect x="
|
128 |
-
<text x="400" y="
|
129 |
-
<text x="400" y="
|
130 |
-
<text x="400" y="
|
131 |
|
132 |
<!-- Optional Meta-Analysis Box -->
|
133 |
{meta_analysis_section}
|
134 |
-
|
135 |
-
<!-- PRISMA Title -->
|
136 |
-
<text x="400" y="10" style="font-size: 18px; font-weight: bold; text-anchor: middle;">PRISMA Flow Diagram</text>
|
137 |
</svg>
|
138 |
'''
|
139 |
|
@@ -263,7 +265,6 @@ def generate_systematic_review(pdf_files, review_question, prisma_numbers, inclu
|
|
263 |
)
|
264 |
|
265 |
# Find a suitable place to insert the PRISMA flow chart
|
266 |
-
# Look for the methodology section or PRISMA mentions
|
267 |
prisma_html = f'''
|
268 |
<div class="prisma-flow-chart">
|
269 |
<h3>Figure 1: PRISMA Flow Diagram</h3>
|
@@ -271,22 +272,18 @@ def generate_systematic_review(pdf_files, review_question, prisma_numbers, inclu
|
|
271 |
</div>
|
272 |
'''
|
273 |
|
274 |
-
# Try to insert PRISMA diagram after methodology section or before results
|
275 |
if "<h2>3. Methodology" in review_content:
|
276 |
parts = review_content.split("<h2>3. Methodology", 1)
|
277 |
if len(parts) > 1:
|
278 |
-
# Find the next h2 section after methodology
|
279 |
next_section = parts[1].find("<h2>")
|
280 |
if next_section > -1:
|
281 |
review_content = parts[0] + "<h2>3. Methodology" + parts[1][:next_section] + prisma_html + parts[1][next_section:]
|
282 |
else:
|
283 |
review_content = parts[0] + "<h2>3. Methodology" + parts[1] + prisma_html
|
284 |
else:
|
285 |
-
# If methodology section not found, insert before results or at the end
|
286 |
insert_pos = review_content.find("<h2>4.") if "<h2>4." in review_content else len(review_content) - 100
|
287 |
review_content = review_content[:insert_pos] + prisma_html + review_content[insert_pos:]
|
288 |
|
289 |
-
# Apply professional academic paper styling
|
290 |
styled_html = f"""
|
291 |
<!DOCTYPE html>
|
292 |
<html lang="en">
|
@@ -295,7 +292,6 @@ def generate_systematic_review(pdf_files, review_question, prisma_numbers, inclu
|
|
295 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
296 |
<title>Systematic Review</title>
|
297 |
<style>
|
298 |
-
/* Academic Paper Styling */
|
299 |
body {{
|
300 |
font-family: 'Times New Roman', Times, serif;
|
301 |
line-height: 1.6;
|
@@ -453,9 +449,7 @@ def save_uploaded_files(files):
|
|
453 |
saved_paths = []
|
454 |
for file in files:
|
455 |
if file is not None:
|
456 |
-
# Create a temporary file
|
457 |
with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp_file:
|
458 |
-
# Read the content from the file path provided by Gradio
|
459 |
with open(file, "rb") as src_file:
|
460 |
tmp_file.write(src_file.read())
|
461 |
saved_paths.append(tmp_file.name)
|
@@ -467,11 +461,9 @@ def create_html_download_link(html_content):
|
|
467 |
if not html_content or "<div style=\"color: red; padding: 20px;" in html_content or "Please upload at least one PDF file" in html_content:
|
468 |
return None
|
469 |
|
470 |
-
# Create timestamp for the filename
|
471 |
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
472 |
filename = f"systematic_review_{timestamp}.html"
|
473 |
|
474 |
-
# Encode the HTML content for download
|
475 |
b64_html = base64.b64encode(html_content.encode()).decode()
|
476 |
download_link = f'<a href="data:text/html;base64,{b64_html}" download="{filename}" class="download-button">Download HTML</a>'
|
477 |
|
@@ -480,39 +472,32 @@ def create_html_download_link(html_content):
|
|
480 |
# Add CSS styling for the Gradio interface
|
481 |
custom_css = """
|
482 |
<style>
|
483 |
-
|
484 |
-
.gradio-container {
|
485 |
font-family: 'Arial', sans-serif;
|
486 |
background-color: #f9f9f9;
|
487 |
-
}
|
488 |
-
|
489 |
-
/* Header */
|
490 |
-
h1 {
|
491 |
font-size: 28px;
|
492 |
color: #333;
|
493 |
margin-bottom: 20px;
|
494 |
text-align: center;
|
495 |
padding-bottom: 10px;
|
496 |
border-bottom: 2px solid #4a00e0;
|
497 |
-
}
|
498 |
-
|
499 |
-
/* Primary Button */
|
500 |
-
#generate_button {
|
501 |
background: linear-gradient(135deg, #4a00e0 0%, #8e2de2 100%);
|
502 |
color: white;
|
503 |
font-weight: bold;
|
504 |
padding: 10px 20px;
|
505 |
border-radius: 5px;
|
506 |
transition: all 0.3s ease;
|
507 |
-
}
|
508 |
-
#generate_button:hover {
|
509 |
background: linear-gradient(135deg, #5b10f1 0%, #9f3ef3 100%);
|
510 |
transform: translateY(-2px);
|
511 |
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
512 |
-
}
|
513 |
-
|
514 |
-
/* API Key Button */
|
515 |
-
#api_key_button {
|
516 |
background: linear-gradient(135deg, #68d391 0%, #48bb78 100%);
|
517 |
color: white;
|
518 |
font-weight: bold;
|
@@ -520,56 +505,44 @@ custom_css = """
|
|
520 |
padding: 10px 20px;
|
521 |
border-radius: 5px;
|
522 |
transition: all 0.3s ease;
|
523 |
-
}
|
524 |
-
#api_key_button:hover {
|
525 |
background: linear-gradient(135deg, #38a169 0%, #68d391 100%);
|
526 |
transform: translateY(-2px);
|
527 |
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
528 |
-
}
|
529 |
-
|
530 |
-
/* Form Elements */
|
531 |
-
.input-container {
|
532 |
background-color: white;
|
533 |
padding: 20px;
|
534 |
border-radius: 8px;
|
535 |
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
|
536 |
margin-bottom: 20px;
|
537 |
-
}
|
538 |
-
|
539 |
-
/* Labels */
|
540 |
-
label {
|
541 |
font-weight: 600;
|
542 |
color: #555;
|
543 |
margin-bottom: 8px;
|
544 |
-
}
|
545 |
-
|
546 |
-
/* Instructions Accordion */
|
547 |
-
.accordion {
|
548 |
background-color: white;
|
549 |
border: 1px solid #e0e0e0;
|
550 |
border-radius: 8px;
|
551 |
margin-bottom: 20px;
|
552 |
-
}
|
553 |
-
|
554 |
-
/* Output Container */
|
555 |
-
.output-container {
|
556 |
background-color: white;
|
557 |
padding: 15px;
|
558 |
border-radius: 8px;
|
559 |
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
|
560 |
-
}
|
561 |
-
|
562 |
-
/* File Upload Area */
|
563 |
-
.file-upload {
|
564 |
border: 2px dashed #ccc;
|
565 |
border-radius: 5px;
|
566 |
padding: 20px;
|
567 |
text-align: center;
|
568 |
margin-bottom: 20px;
|
569 |
-
}
|
570 |
-
|
571 |
-
/* Download Button */
|
572 |
-
.download-button {
|
573 |
display: inline-block;
|
574 |
background: linear-gradient(135deg, #4a00e0 0%, #8e2de2 100%);
|
575 |
color: white;
|
@@ -579,47 +552,40 @@ custom_css = """
|
|
579 |
text-decoration: none;
|
580 |
margin-bottom: 10px;
|
581 |
transition: all 0.3s ease;
|
582 |
-
}
|
583 |
-
.download-button:hover {
|
584 |
background: linear-gradient(135deg, #5b10f1 0%, #9f3ef3 100%);
|
585 |
transform: translateY(-2px);
|
586 |
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
587 |
-
}
|
588 |
-
|
589 |
-
/* Download Container */
|
590 |
-
#download-container {
|
591 |
display: flex;
|
592 |
justify-content: center;
|
593 |
margin: 20px 0;
|
594 |
padding: 15px;
|
595 |
background-color: #f5f5f5;
|
596 |
border-radius: 8px;
|
597 |
-
}
|
598 |
-
|
599 |
-
/* PRISMA Flow Chart Container */
|
600 |
-
.prisma-section {
|
601 |
background-color: white;
|
602 |
padding: 20px;
|
603 |
border-radius: 8px;
|
604 |
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
|
605 |
margin-bottom: 20px;
|
606 |
-
}
|
607 |
-
|
608 |
-
.prisma-form {
|
609 |
display: grid;
|
610 |
grid-template-columns: 1fr 1fr;
|
611 |
gap: 15px;
|
612 |
-
}
|
613 |
-
|
614 |
-
|
615 |
-
@media screen and (max-width: 768px) {
|
616 |
-
.gradio-container {
|
617 |
padding: 10px;
|
618 |
-
}
|
619 |
-
.prisma-form {
|
620 |
grid-template-columns: 1fr;
|
621 |
-
}
|
622 |
-
}
|
623 |
</style>
|
624 |
"""
|
625 |
|
@@ -723,10 +689,10 @@ with gr.Blocks(css=custom_css) as demo:
|
|
723 |
<div style="width: 30%; height: 100%; background: linear-gradient(90deg, #4a00e0, #8e2de2); animation: progress 2s infinite linear;"></div>
|
724 |
</div>
|
725 |
<style>
|
726 |
-
@keyframes progress {
|
727 |
-
0% { margin-left: -30%; }
|
728 |
-
100% { margin-left: 100%; }
|
729 |
-
}
|
730 |
</style>
|
731 |
</div>
|
732 |
"""
|
|
|
33 |
|
34 |
records_after_duplicates = records_db + records_other - duplicates
|
35 |
|
36 |
+
# Create SVG content for PRISMA flow chart with clean and centered layout
|
37 |
meta_analysis_section = ''
|
38 |
if included_meta is not None:
|
39 |
meta_analysis_section = f'''
|
40 |
+
<line x1="400" y1="580" x2="400" y2="620" class="arrow" />
|
41 |
+
<rect x="300" y="620" width="200" height="80" class="box" />
|
42 |
+
<text x="400" y="650" class="box-title">Meta-Analysis</text>
|
43 |
+
<text x="400" y="670">Studies included in quantitative</text>
|
44 |
+
<text x="400" y="685">synthesis (n = {included_meta})</text>
|
45 |
'''
|
46 |
|
47 |
svg_content = f'''
|
|
|
61 |
font-family: Arial, sans-serif;
|
62 |
font-size: 14px;
|
63 |
text-anchor: middle;
|
64 |
+
dominant-baseline: middle;
|
65 |
}}
|
66 |
.box-title {{
|
67 |
font-weight: bold;
|
68 |
+
font-size: 16px;
|
69 |
}}
|
70 |
</style>
|
71 |
|
|
|
75 |
</marker>
|
76 |
</defs>
|
77 |
|
78 |
+
<!-- PRISMA Title -->
|
79 |
+
<text x="400" y="20" style="font-size: 18px; font-weight: bold; text-anchor: middle;">PRISMA Flow Diagram</text>
|
80 |
+
|
81 |
<!-- Identification Section -->
|
82 |
+
<rect x="250" y="60" width="300" height="80" class="box" />
|
83 |
+
<text x="400" y="100" class="box-title">Identification</text>
|
84 |
+
<text x="400" y="120">Records identified through</text>
|
85 |
+
<text x="400" y="135">database searching (n = {records_db})</text>
|
86 |
|
87 |
+
<rect x="550" y="60" width="200" height="80" class="box" />
|
88 |
+
<text x="650" y="100" class="box-title">Additional</text>
|
89 |
+
<text x="650" y="120">Other sources</text>
|
90 |
+
<text x="650" y="135">(n = {records_other})</text>
|
91 |
|
92 |
<!-- Arrows -->
|
93 |
+
<line x1="400" y1="140" x2="400" y2="180" class="arrow" />
|
94 |
+
<line x1="650" y1="140" x2="650" y2="160" class="arrow" />
|
95 |
+
<line x1="650" y1="160" x2="400" y2="160" class="arrow" />
|
96 |
|
97 |
<!-- Screening Section -->
|
98 |
+
<rect x="250" y="180" width="300" height="80" class="box" />
|
99 |
+
<text x="400" y="220" class="box-title">Screening</text>
|
100 |
+
<text x="400" y="240">Records after duplicates removed</text>
|
101 |
+
<text x="400" y="255">(n = {records_after_duplicates})</text>
|
102 |
|
103 |
+
<line x1="400" y1="260" x2="400" y2="300" class="arrow" />
|
104 |
|
105 |
+
<!-- Title/Abstract Screening Section -->
|
106 |
+
<rect x="250" y="300" width="300" height="80" class="box" />
|
107 |
+
<text x="400" y="340" class="box-title">Title/Abstract Screening</text>
|
108 |
+
<text x="400" y="360">Records screened</text>
|
109 |
+
<text x="400" y="375">(n = {records_after_duplicates})</text>
|
110 |
|
111 |
+
<rect x="550" y="300" width="200" height="80" class="box" />
|
112 |
+
<text x="650" y="340">Records excluded</text>
|
113 |
+
<text x="650" y="355">(n = {excluded_screening})</text>
|
114 |
|
115 |
+
<line x1="400" y1="380" x2="400" y2="420" class="arrow" />
|
116 |
+
<line x1="500" y1="340" x2="550" y2="340" class="arrow" />
|
117 |
|
118 |
<!-- Eligibility Section -->
|
119 |
+
<rect x="250" y="420" width="300" height="80" class="box" />
|
120 |
+
<text x="400" y="460" class="box-title">Eligibility</text>
|
121 |
+
<text x="400" y="480">Full-text articles assessed for</text>
|
122 |
+
<text x="400" y="495">eligibility (n = {fulltext_assessed})</text>
|
123 |
|
124 |
+
<rect x="550" y="420" width="200" height="80" class="box" />
|
125 |
+
<text x="650" y="460">Full-text articles excluded,</text>
|
126 |
+
<text x="650" y="475">with reasons (n = {excluded_fulltext})</text>
|
|
|
127 |
|
128 |
+
<line x1="400" y1="500" x2="400" y2="540" class="arrow" />
|
129 |
+
<line x1="500" y1="460" x2="550" y2="460" class="arrow" />
|
130 |
|
131 |
<!-- Included Section -->
|
132 |
+
<rect x="250" y="540" width="300" height="80" class="box" />
|
133 |
+
<text x="400" y="580" class="box-title">Included</text>
|
134 |
+
<text x="400" y="600">Studies included in qualitative</text>
|
135 |
+
<text x="400" y="615">synthesis (n = {included_studies})</text>
|
136 |
|
137 |
<!-- Optional Meta-Analysis Box -->
|
138 |
{meta_analysis_section}
|
|
|
|
|
|
|
139 |
</svg>
|
140 |
'''
|
141 |
|
|
|
265 |
)
|
266 |
|
267 |
# Find a suitable place to insert the PRISMA flow chart
|
|
|
268 |
prisma_html = f'''
|
269 |
<div class="prisma-flow-chart">
|
270 |
<h3>Figure 1: PRISMA Flow Diagram</h3>
|
|
|
272 |
</div>
|
273 |
'''
|
274 |
|
|
|
275 |
if "<h2>3. Methodology" in review_content:
|
276 |
parts = review_content.split("<h2>3. Methodology", 1)
|
277 |
if len(parts) > 1:
|
|
|
278 |
next_section = parts[1].find("<h2>")
|
279 |
if next_section > -1:
|
280 |
review_content = parts[0] + "<h2>3. Methodology" + parts[1][:next_section] + prisma_html + parts[1][next_section:]
|
281 |
else:
|
282 |
review_content = parts[0] + "<h2>3. Methodology" + parts[1] + prisma_html
|
283 |
else:
|
|
|
284 |
insert_pos = review_content.find("<h2>4.") if "<h2>4." in review_content else len(review_content) - 100
|
285 |
review_content = review_content[:insert_pos] + prisma_html + review_content[insert_pos:]
|
286 |
|
|
|
287 |
styled_html = f"""
|
288 |
<!DOCTYPE html>
|
289 |
<html lang="en">
|
|
|
292 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
293 |
<title>Systematic Review</title>
|
294 |
<style>
|
|
|
295 |
body {{
|
296 |
font-family: 'Times New Roman', Times, serif;
|
297 |
line-height: 1.6;
|
|
|
449 |
saved_paths = []
|
450 |
for file in files:
|
451 |
if file is not None:
|
|
|
452 |
with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp_file:
|
|
|
453 |
with open(file, "rb") as src_file:
|
454 |
tmp_file.write(src_file.read())
|
455 |
saved_paths.append(tmp_file.name)
|
|
|
461 |
if not html_content or "<div style=\"color: red; padding: 20px;" in html_content or "Please upload at least one PDF file" in html_content:
|
462 |
return None
|
463 |
|
|
|
464 |
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
465 |
filename = f"systematic_review_{timestamp}.html"
|
466 |
|
|
|
467 |
b64_html = base64.b64encode(html_content.encode()).decode()
|
468 |
download_link = f'<a href="data:text/html;base64,{b64_html}" download="{filename}" class="download-button">Download HTML</a>'
|
469 |
|
|
|
472 |
# Add CSS styling for the Gradio interface
|
473 |
custom_css = """
|
474 |
<style>
|
475 |
+
.gradio-container {{
|
|
|
476 |
font-family: 'Arial', sans-serif;
|
477 |
background-color: #f9f9f9;
|
478 |
+
}}
|
479 |
+
h1 {{
|
|
|
|
|
480 |
font-size: 28px;
|
481 |
color: #333;
|
482 |
margin-bottom: 20px;
|
483 |
text-align: center;
|
484 |
padding-bottom: 10px;
|
485 |
border-bottom: 2px solid #4a00e0;
|
486 |
+
}}
|
487 |
+
#generate_button {{
|
|
|
|
|
488 |
background: linear-gradient(135deg, #4a00e0 0%, #8e2de2 100%);
|
489 |
color: white;
|
490 |
font-weight: bold;
|
491 |
padding: 10px 20px;
|
492 |
border-radius: 5px;
|
493 |
transition: all 0.3s ease;
|
494 |
+
}}
|
495 |
+
#generate_button:hover {{
|
496 |
background: linear-gradient(135deg, #5b10f1 0%, #9f3ef3 100%);
|
497 |
transform: translateY(-2px);
|
498 |
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
499 |
+
}}
|
500 |
+
#api_key_button {{
|
|
|
|
|
501 |
background: linear-gradient(135deg, #68d391 0%, #48bb78 100%);
|
502 |
color: white;
|
503 |
font-weight: bold;
|
|
|
505 |
padding: 10px 20px;
|
506 |
border-radius: 5px;
|
507 |
transition: all 0.3s ease;
|
508 |
+
}}
|
509 |
+
#api_key_button:hover {{
|
510 |
background: linear-gradient(135deg, #38a169 0%, #68d391 100%);
|
511 |
transform: translateY(-2px);
|
512 |
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
513 |
+
}}
|
514 |
+
.input-container {{
|
|
|
|
|
515 |
background-color: white;
|
516 |
padding: 20px;
|
517 |
border-radius: 8px;
|
518 |
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
|
519 |
margin-bottom: 20px;
|
520 |
+
}}
|
521 |
+
label {{
|
|
|
|
|
522 |
font-weight: 600;
|
523 |
color: #555;
|
524 |
margin-bottom: 8px;
|
525 |
+
}}
|
526 |
+
.accordion {{
|
|
|
|
|
527 |
background-color: white;
|
528 |
border: 1px solid #e0e0e0;
|
529 |
border-radius: 8px;
|
530 |
margin-bottom: 20px;
|
531 |
+
}}
|
532 |
+
.output-container {{
|
|
|
|
|
533 |
background-color: white;
|
534 |
padding: 15px;
|
535 |
border-radius: 8px;
|
536 |
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
|
537 |
+
}}
|
538 |
+
.file-upload {{
|
|
|
|
|
539 |
border: 2px dashed #ccc;
|
540 |
border-radius: 5px;
|
541 |
padding: 20px;
|
542 |
text-align: center;
|
543 |
margin-bottom: 20px;
|
544 |
+
}}
|
545 |
+
.download-button {{
|
|
|
|
|
546 |
display: inline-block;
|
547 |
background: linear-gradient(135deg, #4a00e0 0%, #8e2de2 100%);
|
548 |
color: white;
|
|
|
552 |
text-decoration: none;
|
553 |
margin-bottom: 10px;
|
554 |
transition: all 0.3s ease;
|
555 |
+
}}
|
556 |
+
.download-button:hover {{
|
557 |
background: linear-gradient(135deg, #5b10f1 0%, #9f3ef3 100%);
|
558 |
transform: translateY(-2px);
|
559 |
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
560 |
+
}}
|
561 |
+
#download-container {{
|
|
|
|
|
562 |
display: flex;
|
563 |
justify-content: center;
|
564 |
margin: 20px 0;
|
565 |
padding: 15px;
|
566 |
background-color: #f5f5f5;
|
567 |
border-radius: 8px;
|
568 |
+
}}
|
569 |
+
.prisma-section {{
|
|
|
|
|
570 |
background-color: white;
|
571 |
padding: 20px;
|
572 |
border-radius: 8px;
|
573 |
box-shadow: 0 2px 10px rgba(0,0,0,0.05);
|
574 |
margin-bottom: 20px;
|
575 |
+
}}
|
576 |
+
.prisma-form {{
|
|
|
577 |
display: grid;
|
578 |
grid-template-columns: 1fr 1fr;
|
579 |
gap: 15px;
|
580 |
+
}}
|
581 |
+
@media screen and (max-width: 768px) {{
|
582 |
+
.gradio-container {{
|
|
|
|
|
583 |
padding: 10px;
|
584 |
+
}}
|
585 |
+
.prisma-form {{
|
586 |
grid-template-columns: 1fr;
|
587 |
+
}}
|
588 |
+
}}
|
589 |
</style>
|
590 |
"""
|
591 |
|
|
|
689 |
<div style="width: 30%; height: 100%; background: linear-gradient(90deg, #4a00e0, #8e2de2); animation: progress 2s infinite linear;"></div>
|
690 |
</div>
|
691 |
<style>
|
692 |
+
@keyframes progress {{
|
693 |
+
0% {{ margin-left: -30%; }}
|
694 |
+
100% {{ margin-left: 100%; }}
|
695 |
+
}}
|
696 |
</style>
|
697 |
</div>
|
698 |
"""
|