Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -228,41 +228,67 @@ class ContentAnalyzer:
|
|
| 228 |
self.GPT4_OUTPUT_COST = 0.60 / 1_000_000 # $0.60 per 1M tokens output
|
| 229 |
|
| 230 |
def analyze_content(self, transcript: str, progress_callback=None) -> Dict[str, Any]:
|
| 231 |
-
"""Analyze teaching content with
|
| 232 |
for attempt in range(self.retry_count):
|
| 233 |
try:
|
| 234 |
if progress_callback:
|
| 235 |
progress_callback(0.2, "Preparing content analysis...")
|
| 236 |
|
| 237 |
prompt = self._create_analysis_prompt(transcript)
|
|
|
|
|
|
|
| 238 |
|
| 239 |
if progress_callback:
|
| 240 |
progress_callback(0.5, "Processing with AI model...")
|
| 241 |
|
| 242 |
try:
|
| 243 |
response = self.client.chat.completions.create(
|
| 244 |
-
model="gpt-4o-mini",
|
| 245 |
messages=[
|
| 246 |
-
{"role": "system", "content": """You are a teaching
|
| 247 |
-
|
| 248 |
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
- First Principles Approach (Score: 0/1, Citations with timestamps)
|
| 252 |
-
- Examples and Business Context (Score: 0/1, Citations with timestamps)
|
| 253 |
-
- Cohesive Storytelling (Score: 0/1, Citations with timestamps)
|
| 254 |
-
- Engagement and Interaction (Score: 0/1, Citations with timestamps)
|
| 255 |
-
- Professional Tone (Score: 0/1, Citations with timestamps)
|
| 256 |
|
| 257 |
-
|
| 258 |
-
-
|
| 259 |
-
-
|
| 260 |
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
|
| 262 |
Always respond with valid JSON containing these exact categories."""},
|
| 263 |
{"role": "user", "content": prompt}
|
| 264 |
],
|
| 265 |
-
response_format={"type": "json_object"}
|
|
|
|
| 266 |
)
|
| 267 |
logger.info("API call successful")
|
| 268 |
except Exception as api_error:
|
|
@@ -324,7 +350,7 @@ class ContentAnalyzer:
|
|
| 324 |
raise
|
| 325 |
|
| 326 |
except Exception as e:
|
| 327 |
-
logger.error(f"Content analysis attempt {attempt + 1} failed: {
|
| 328 |
if attempt == self.retry_count - 1:
|
| 329 |
logger.error("All attempts failed, returning default structure")
|
| 330 |
return {
|
|
@@ -663,7 +689,7 @@ class MentorEvaluator:
|
|
| 663 |
logger.info("Attempting to initialize Whisper model...")
|
| 664 |
# First try to initialize model with downloading allowed
|
| 665 |
self._whisper_model = WhisperModel(
|
| 666 |
-
"
|
| 667 |
device="cpu",
|
| 668 |
compute_type="int8",
|
| 669 |
download_root=self.model_cache_dir,
|
|
@@ -676,7 +702,7 @@ class MentorEvaluator:
|
|
| 676 |
try:
|
| 677 |
logger.info("Attempting to load model from local cache...")
|
| 678 |
self._whisper_model = WhisperModel(
|
| 679 |
-
"
|
| 680 |
device="cpu",
|
| 681 |
compute_type="int8",
|
| 682 |
download_root=self.model_cache_dir,
|
|
@@ -1483,9 +1509,10 @@ def main():
|
|
| 1483 |
# Add custom CSS for animations and styling
|
| 1484 |
st.markdown("""
|
| 1485 |
<style>
|
|
|
|
| 1486 |
@keyframes fadeIn {
|
| 1487 |
-
from { opacity: 0; }
|
| 1488 |
-
to { opacity: 1; }
|
| 1489 |
}
|
| 1490 |
|
| 1491 |
@keyframes slideIn {
|
|
@@ -1499,245 +1526,192 @@ def main():
|
|
| 1499 |
100% { transform: scale(1); }
|
| 1500 |
}
|
| 1501 |
|
| 1502 |
-
|
| 1503 |
-
|
|
|
|
|
|
|
| 1504 |
}
|
| 1505 |
|
| 1506 |
-
|
| 1507 |
-
|
|
|
|
| 1508 |
}
|
| 1509 |
|
| 1510 |
-
.
|
| 1511 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1512 |
}
|
| 1513 |
|
| 1514 |
-
.
|
| 1515 |
-
background
|
| 1516 |
-
|
| 1517 |
-
|
| 1518 |
-
|
| 1519 |
-
|
|
|
|
| 1520 |
transition: transform 0.3s ease;
|
| 1521 |
}
|
| 1522 |
|
| 1523 |
-
.
|
| 1524 |
transform: translateY(-5px);
|
| 1525 |
}
|
| 1526 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1527 |
.stButton>button {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1528 |
transition: all 0.3s ease;
|
| 1529 |
}
|
| 1530 |
|
| 1531 |
.stButton>button:hover {
|
| 1532 |
-
transform:
|
|
|
|
| 1533 |
}
|
| 1534 |
|
| 1535 |
-
.
|
| 1536 |
-
background: linear-gradient(90deg, #
|
| 1537 |
-
color: white;
|
| 1538 |
-
padding: 10px;
|
| 1539 |
-
border-radius: 5px;
|
| 1540 |
-
margin: 10px 0;
|
| 1541 |
}
|
| 1542 |
|
| 1543 |
-
|
| 1544 |
-
|
| 1545 |
-
|
| 1546 |
-
|
|
|
|
|
|
|
|
|
|
| 1547 |
}
|
| 1548 |
|
| 1549 |
-
.
|
| 1550 |
-
background
|
| 1551 |
-
|
|
|
|
|
|
|
| 1552 |
}
|
| 1553 |
|
| 1554 |
-
|
| 1555 |
-
|
| 1556 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1557 |
}
|
| 1558 |
</style>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1559 |
""", unsafe_allow_html=True)
|
| 1560 |
|
| 1561 |
-
# Sidebar with
|
| 1562 |
with st.sidebar:
|
| 1563 |
st.markdown("""
|
| 1564 |
-
<div class="
|
| 1565 |
-
<h2>Instructions</h2>
|
| 1566 |
-
<
|
| 1567 |
-
|
| 1568 |
-
<li>
|
| 1569 |
-
<li>
|
|
|
|
| 1570 |
<li>Download the report</li>
|
| 1571 |
</ol>
|
| 1572 |
</div>
|
| 1573 |
""", unsafe_allow_html=True)
|
| 1574 |
|
| 1575 |
-
|
| 1576 |
-
|
| 1577 |
-
|
| 1578 |
-
|
| 1579 |
-
|
| 1580 |
-
|
| 1581 |
-
|
| 1582 |
-
# Main title (only once)
|
| 1583 |
-
st.markdown("""
|
| 1584 |
-
<div class="fade-in">
|
| 1585 |
-
<h1 style='text-align: center; color: #1f77b4;'>
|
| 1586 |
-
🎓 Mentor Demo Review System
|
| 1587 |
-
</h1>
|
| 1588 |
-
</div>
|
| 1589 |
-
""", unsafe_allow_html=True)
|
| 1590 |
-
|
| 1591 |
-
# Check dependencies with progress
|
| 1592 |
-
with st.status("Checking system requirements...") as status:
|
| 1593 |
-
progress_bar = st.progress(0)
|
| 1594 |
-
|
| 1595 |
-
status.update(label="Checking FFmpeg installation...")
|
| 1596 |
-
progress_bar.progress(0.3)
|
| 1597 |
-
missing_deps = check_dependencies()
|
| 1598 |
-
|
| 1599 |
-
progress_bar.progress(0.6)
|
| 1600 |
-
if missing_deps:
|
| 1601 |
-
status.update(label="Missing dependencies detected!", state="error")
|
| 1602 |
-
st.error(f"Missing required dependencies: {', '.join(missing_deps)}")
|
| 1603 |
-
st.markdown("""
|
| 1604 |
-
Please install the missing dependencies:
|
| 1605 |
-
```bash
|
| 1606 |
-
sudo apt-get update
|
| 1607 |
-
sudo apt-get install ffmpeg
|
| 1608 |
-
```
|
| 1609 |
-
""")
|
| 1610 |
-
return
|
| 1611 |
-
|
| 1612 |
-
progress_bar.progress(1.0)
|
| 1613 |
-
status.update(label="System requirements satisfied!", state="complete")
|
| 1614 |
-
|
| 1615 |
-
# Temporary: Add radio button for input type selection
|
| 1616 |
-
input_type = st.radio(
|
| 1617 |
-
"Select Input Type (Temporary Feature)",
|
| 1618 |
-
["Video Only", "Video + Transcript"],
|
| 1619 |
-
help="Temporary feature: Choose to upload video only or video with transcript"
|
| 1620 |
-
)
|
| 1621 |
-
|
| 1622 |
-
uploaded_file = st.file_uploader(
|
| 1623 |
-
"Upload Teaching Video",
|
| 1624 |
-
type=['mp4', 'avi', 'mov'],
|
| 1625 |
-
help="Upload your teaching video in MP4, AVI, or MOV format"
|
| 1626 |
-
)
|
| 1627 |
-
|
| 1628 |
-
# Temporary: Add transcript uploader if Video + Transcript is selected
|
| 1629 |
-
uploaded_transcript = None
|
| 1630 |
-
if input_type == "Video + Transcript":
|
| 1631 |
-
uploaded_transcript = st.file_uploader(
|
| 1632 |
-
"Upload Transcript (Optional)",
|
| 1633 |
-
type=['txt'],
|
| 1634 |
-
help="Upload your transcript in TXT format"
|
| 1635 |
-
)
|
| 1636 |
|
| 1637 |
if uploaded_file:
|
| 1638 |
-
# Add a
|
| 1639 |
st.markdown("""
|
| 1640 |
-
<div class="
|
| 1641 |
-
<h3>Processing
|
|
|
|
| 1642 |
</div>
|
| 1643 |
""", unsafe_allow_html=True)
|
| 1644 |
-
|
| 1645 |
-
|
| 1646 |
-
|
| 1647 |
-
|
| 1648 |
-
|
| 1649 |
-
|
| 1650 |
-
|
| 1651 |
-
|
| 1652 |
-
|
| 1653 |
-
|
| 1654 |
-
|
| 1655 |
-
|
| 1656 |
-
|
| 1657 |
-
|
| 1658 |
-
|
| 1659 |
-
|
| 1660 |
-
|
| 1661 |
-
|
| 1662 |
-
|
| 1663 |
-
|
| 1664 |
-
|
| 1665 |
-
|
| 1666 |
-
|
| 1667 |
-
|
| 1668 |
-
|
| 1669 |
-
|
| 1670 |
-
|
| 1671 |
-
|
| 1672 |
-
|
| 1673 |
-
st.error("File size exceeds 2GB limit. Please upload a smaller file.")
|
| 1674 |
-
return
|
| 1675 |
-
|
| 1676 |
-
# Store evaluation results in session state
|
| 1677 |
-
if 'evaluation_results' not in st.session_state:
|
| 1678 |
-
# Process video only if results aren't already in session state
|
| 1679 |
-
with st.spinner("Processing video"):
|
| 1680 |
-
evaluator = MentorEvaluator()
|
| 1681 |
-
|
| 1682 |
-
# Temporary: Handle transcript if provided
|
| 1683 |
-
if uploaded_transcript:
|
| 1684 |
-
transcript_text = uploaded_transcript.getvalue().decode('utf-8')
|
| 1685 |
-
# Extract audio features but skip transcription
|
| 1686 |
-
audio_features = evaluator.feature_extractor.extract_features(video_path)
|
| 1687 |
-
|
| 1688 |
-
# Evaluate speech metrics
|
| 1689 |
-
speech_metrics = evaluator._evaluate_speech_metrics(
|
| 1690 |
-
transcript_text,
|
| 1691 |
-
audio_features
|
| 1692 |
-
)
|
| 1693 |
-
|
| 1694 |
-
# Analyze content
|
| 1695 |
-
content_analysis = evaluator.content_analyzer.analyze_content(transcript_text)
|
| 1696 |
-
|
| 1697 |
-
# Generate recommendations
|
| 1698 |
-
recommendations = evaluator.recommendation_generator.generate_recommendations(
|
| 1699 |
-
speech_metrics,
|
| 1700 |
-
content_analysis
|
| 1701 |
-
)
|
| 1702 |
-
|
| 1703 |
-
# Combine results
|
| 1704 |
-
st.session_state.evaluation_results = {
|
| 1705 |
-
"communication": speech_metrics,
|
| 1706 |
-
"teaching": content_analysis,
|
| 1707 |
-
"recommendations": recommendations,
|
| 1708 |
-
"transcript": transcript_text
|
| 1709 |
-
}
|
| 1710 |
-
else:
|
| 1711 |
-
# Original flow: full video evaluation
|
| 1712 |
-
st.session_state.evaluation_results = evaluator.evaluate_video(video_path)
|
| 1713 |
-
|
| 1714 |
-
# Display results using stored evaluation
|
| 1715 |
-
st.success("Analysis complete!")
|
| 1716 |
-
display_evaluation(st.session_state.evaluation_results)
|
| 1717 |
-
|
| 1718 |
-
# Add download button using stored results
|
| 1719 |
-
if st.download_button(
|
| 1720 |
-
"📥 Download Full Report",
|
| 1721 |
-
json.dumps(st.session_state.evaluation_results, indent=2),
|
| 1722 |
-
"evaluation_report.json",
|
| 1723 |
-
"application/json",
|
| 1724 |
-
help="Download the complete evaluation report in JSON format"
|
| 1725 |
-
):
|
| 1726 |
-
st.success("Report downloaded successfully!")
|
| 1727 |
-
|
| 1728 |
-
# Debugging code
|
| 1729 |
-
# if st.session_state.evaluation_results:
|
| 1730 |
-
# st.write("Debug: Teaching Analysis Structure")
|
| 1731 |
-
# teaching_data = st.session_state.evaluation_results.get("teaching", {})
|
| 1732 |
-
# st.json(teaching_data)
|
| 1733 |
-
|
| 1734 |
-
except Exception as e:
|
| 1735 |
-
st.error(f"Error during evaluation: {str(e)}")
|
| 1736 |
-
|
| 1737 |
-
finally:
|
| 1738 |
-
# Clean up temp files
|
| 1739 |
-
if 'temp_dir' in locals():
|
| 1740 |
-
shutil.rmtree(temp_dir)
|
| 1741 |
|
| 1742 |
except Exception as e:
|
| 1743 |
st.error(f"Application error: {str(e)}")
|
|
|
|
| 228 |
self.GPT4_OUTPUT_COST = 0.60 / 1_000_000 # $0.60 per 1M tokens output
|
| 229 |
|
| 230 |
def analyze_content(self, transcript: str, progress_callback=None) -> Dict[str, Any]:
|
| 231 |
+
"""Analyze teaching content with more lenient validation and robust JSON handling"""
|
| 232 |
for attempt in range(self.retry_count):
|
| 233 |
try:
|
| 234 |
if progress_callback:
|
| 235 |
progress_callback(0.2, "Preparing content analysis...")
|
| 236 |
|
| 237 |
prompt = self._create_analysis_prompt(transcript)
|
| 238 |
+
logger.info(f"Attempt {attempt + 1}: Sending analysis request")
|
| 239 |
+
logger.info(f"Transcript length: {len(transcript)} characters")
|
| 240 |
|
| 241 |
if progress_callback:
|
| 242 |
progress_callback(0.5, "Processing with AI model...")
|
| 243 |
|
| 244 |
try:
|
| 245 |
response = self.client.chat.completions.create(
|
| 246 |
+
model="gpt-4o-mini", # Keeping original model
|
| 247 |
messages=[
|
| 248 |
+
{"role": "system", "content": """You are a strict teaching evaluator focusing on core teaching competencies.
|
| 249 |
+
Ignore minor transcription errors or verbal stumbles. Focus on substantive teaching quality.
|
| 250 |
|
| 251 |
+
Score of 1 should be given when CORE criteria below are met with clear evidence.
|
| 252 |
+
Default to 0 if MAJOR teaching deficiencies are present.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 253 |
|
| 254 |
+
Concept Assessment Scoring Criteria:
|
| 255 |
+
- Subject Matter Accuracy (Score 1 requires: Core concepts are accurately explained. Ignore minor verbal slips)
|
| 256 |
+
- First Principles Approach (Score 1 requires: Clear explanation of fundamentals before advanced concepts)
|
| 257 |
+
- Examples and Business Context (Score 1 requires: Multiple relevant examples with clear business impact)
|
| 258 |
+
- Cohesive Storytelling (Score 1 requires: Clear logical progression of concepts. Minor verbal transitions can be ignored)
|
| 259 |
+
- Engagement and Interaction (Score 1 requires: Regular attempts to engage learners with meaningful questions)
|
| 260 |
+
- Professional Tone (Score 1 requires: Overall professional delivery. Ignore occasional filler words)
|
| 261 |
+
|
| 262 |
+
Code Assessment Scoring Criteria:
|
| 263 |
+
- Depth of Explanation (Score 1 requires: Clear explanation of key implementation aspects)
|
| 264 |
+
- Output Interpretation (Score 1 requires: Clear connection between code and business outcomes)
|
| 265 |
+
- Breaking down Complexity (Score 1 requires: Systematic breakdown of complex concepts)
|
| 266 |
+
|
| 267 |
+
What to Ignore:
|
| 268 |
+
- Minor transcription errors
|
| 269 |
+
- Occasional verbal stumbles or filler words
|
| 270 |
+
- Small grammatical mistakes
|
| 271 |
+
- Brief pauses or hesitations
|
| 272 |
+
- Minor code syntax errors in verbal explanation
|
| 273 |
+
|
| 274 |
+
What to Focus On:
|
| 275 |
+
- Accuracy of core technical concepts
|
| 276 |
+
- Clarity of fundamental explanations
|
| 277 |
+
- Quality of real-world examples
|
| 278 |
+
- Overall teaching structure and flow
|
| 279 |
+
- Meaningful learner engagement
|
| 280 |
+
- Key implementation explanations
|
| 281 |
+
|
| 282 |
+
Citations Requirements:
|
| 283 |
+
- Include timestamps [MM:SS] where possible
|
| 284 |
+
- Focus on substantive teaching moments
|
| 285 |
+
- Provide specific examples of effective or ineffective teaching
|
| 286 |
|
| 287 |
Always respond with valid JSON containing these exact categories."""},
|
| 288 |
{"role": "user", "content": prompt}
|
| 289 |
],
|
| 290 |
+
response_format={"type": "json_object"},
|
| 291 |
+
temperature=0.4 # Slightly higher temperature for more lenient evaluation
|
| 292 |
)
|
| 293 |
logger.info("API call successful")
|
| 294 |
except Exception as api_error:
|
|
|
|
| 350 |
raise
|
| 351 |
|
| 352 |
except Exception as e:
|
| 353 |
+
logger.error(f"Content analysis attempt {attempt + 1} failed: {e}")
|
| 354 |
if attempt == self.retry_count - 1:
|
| 355 |
logger.error("All attempts failed, returning default structure")
|
| 356 |
return {
|
|
|
|
| 689 |
logger.info("Attempting to initialize Whisper model...")
|
| 690 |
# First try to initialize model with downloading allowed
|
| 691 |
self._whisper_model = WhisperModel(
|
| 692 |
+
"medium",
|
| 693 |
device="cpu",
|
| 694 |
compute_type="int8",
|
| 695 |
download_root=self.model_cache_dir,
|
|
|
|
| 702 |
try:
|
| 703 |
logger.info("Attempting to load model from local cache...")
|
| 704 |
self._whisper_model = WhisperModel(
|
| 705 |
+
"medium",
|
| 706 |
device="cpu",
|
| 707 |
compute_type="int8",
|
| 708 |
download_root=self.model_cache_dir,
|
|
|
|
| 1509 |
# Add custom CSS for animations and styling
|
| 1510 |
st.markdown("""
|
| 1511 |
<style>
|
| 1512 |
+
/* Modern animations */
|
| 1513 |
@keyframes fadeIn {
|
| 1514 |
+
from { opacity: 0; transform: translateY(20px); }
|
| 1515 |
+
to { opacity: 1; transform: translateY(0); }
|
| 1516 |
}
|
| 1517 |
|
| 1518 |
@keyframes slideIn {
|
|
|
|
| 1526 |
100% { transform: scale(1); }
|
| 1527 |
}
|
| 1528 |
|
| 1529 |
+
@keyframes gradientBG {
|
| 1530 |
+
0% { background-position: 0% 50%; }
|
| 1531 |
+
50% { background-position: 100% 50%; }
|
| 1532 |
+
100% { background-position: 0% 50%; }
|
| 1533 |
}
|
| 1534 |
|
| 1535 |
+
/* Modern styling */
|
| 1536 |
+
.stApp {
|
| 1537 |
+
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
| 1538 |
}
|
| 1539 |
|
| 1540 |
+
.main-title {
|
| 1541 |
+
text-align: center;
|
| 1542 |
+
color: #2c3e50;
|
| 1543 |
+
font-size: 2.5rem;
|
| 1544 |
+
font-weight: 700;
|
| 1545 |
+
margin: 2rem 0;
|
| 1546 |
+
padding: 1rem;
|
| 1547 |
+
border-radius: 10px;
|
| 1548 |
+
background: linear-gradient(120deg, #84fab0 0%, #8fd3f4 100%);
|
| 1549 |
+
animation: fadeIn 1s ease-out;
|
| 1550 |
}
|
| 1551 |
|
| 1552 |
+
.card {
|
| 1553 |
+
background: white;
|
| 1554 |
+
padding: 1.5rem;
|
| 1555 |
+
border-radius: 15px;
|
| 1556 |
+
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
|
| 1557 |
+
margin: 1rem 0;
|
| 1558 |
+
animation: fadeIn 0.5s ease-out;
|
| 1559 |
transition: transform 0.3s ease;
|
| 1560 |
}
|
| 1561 |
|
| 1562 |
+
.card:hover {
|
| 1563 |
transform: translateY(-5px);
|
| 1564 |
}
|
| 1565 |
|
| 1566 |
+
.metric-card {
|
| 1567 |
+
background: linear-gradient(120deg, #a1c4fd 0%, #c2e9fb 100%);
|
| 1568 |
+
color: #1a202c;
|
| 1569 |
+
padding: 1rem;
|
| 1570 |
+
border-radius: 10px;
|
| 1571 |
+
text-align: center;
|
| 1572 |
+
animation: fadeIn 0.5s ease-out;
|
| 1573 |
+
}
|
| 1574 |
+
|
| 1575 |
+
.sidebar-content {
|
| 1576 |
+
background: rgba(255, 255, 255, 0.9);
|
| 1577 |
+
padding: 1.5rem;
|
| 1578 |
+
border-radius: 10px;
|
| 1579 |
+
margin: 1rem 0;
|
| 1580 |
+
}
|
| 1581 |
+
|
| 1582 |
.stButton>button {
|
| 1583 |
+
background: linear-gradient(120deg, #4facfe 0%, #00f2fe 100%);
|
| 1584 |
+
color: white;
|
| 1585 |
+
border: none;
|
| 1586 |
+
padding: 0.75rem 1.5rem;
|
| 1587 |
+
border-radius: 25px;
|
| 1588 |
+
font-weight: 600;
|
| 1589 |
transition: all 0.3s ease;
|
| 1590 |
}
|
| 1591 |
|
| 1592 |
.stButton>button:hover {
|
| 1593 |
+
transform: translateY(-2px);
|
| 1594 |
+
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
|
| 1595 |
}
|
| 1596 |
|
| 1597 |
+
.stProgress > div > div {
|
| 1598 |
+
background: linear-gradient(90deg, #4facfe 0%, #00f2fe 100%);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1599 |
}
|
| 1600 |
|
| 1601 |
+
/* Status indicators */
|
| 1602 |
+
.status-processing {
|
| 1603 |
+
background: linear-gradient(120deg, #a1c4fd 0%, #c2e9fb 100%);
|
| 1604 |
+
padding: 1rem;
|
| 1605 |
+
border-radius: 10px;
|
| 1606 |
+
text-align: center;
|
| 1607 |
+
animation: pulse 2s infinite;
|
| 1608 |
}
|
| 1609 |
|
| 1610 |
+
.status-complete {
|
| 1611 |
+
background: linear-gradient(120deg, #84fab0 0%, #8fd3f4 100%);
|
| 1612 |
+
padding: 1rem;
|
| 1613 |
+
border-radius: 10px;
|
| 1614 |
+
text-align: center;
|
| 1615 |
}
|
| 1616 |
|
| 1617 |
+
/* Expander styling */
|
| 1618 |
+
.streamlit-expanderHeader {
|
| 1619 |
+
background: linear-gradient(90deg, #f6f9fc 0%, #f0f4f8 100%);
|
| 1620 |
+
border-radius: 8px;
|
| 1621 |
+
padding: 0.5rem 1rem;
|
| 1622 |
+
font-weight: 600;
|
| 1623 |
+
}
|
| 1624 |
+
|
| 1625 |
+
/* File uploader styling */
|
| 1626 |
+
.uploadedFile {
|
| 1627 |
+
background: white;
|
| 1628 |
+
padding: 1rem;
|
| 1629 |
+
border-radius: 10px;
|
| 1630 |
+
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
|
| 1631 |
+
}
|
| 1632 |
+
|
| 1633 |
+
/* Metrics styling */
|
| 1634 |
+
div[data-testid="stMetricValue"] {
|
| 1635 |
+
font-size: 2rem;
|
| 1636 |
+
font-weight: 700;
|
| 1637 |
+
color: #2c3e50;
|
| 1638 |
+
}
|
| 1639 |
+
|
| 1640 |
+
/* Success/Error message styling */
|
| 1641 |
+
.stSuccess, .stError {
|
| 1642 |
+
padding: 1rem;
|
| 1643 |
+
border-radius: 10px;
|
| 1644 |
+
animation: fadeIn 0.5s ease-out;
|
| 1645 |
}
|
| 1646 |
</style>
|
| 1647 |
+
|
| 1648 |
+
<div class="fade-in">
|
| 1649 |
+
<h1 class="main-title">
|
| 1650 |
+
🎓 Mentor Demo Review System
|
| 1651 |
+
</h1>
|
| 1652 |
+
</div>
|
| 1653 |
""", unsafe_allow_html=True)
|
| 1654 |
|
| 1655 |
+
# Sidebar with modern styling
|
| 1656 |
with st.sidebar:
|
| 1657 |
st.markdown("""
|
| 1658 |
+
<div class="sidebar-content">
|
| 1659 |
+
<h2 style='text-align: center; color: #2c3e50;'>📋 Instructions</h2>
|
| 1660 |
+
<p style='color: #4a5568;'>Follow these steps to evaluate your teaching demo:</p>
|
| 1661 |
+
<ol style='color: #4a5568;'>
|
| 1662 |
+
<li>Upload your teaching demo video</li>
|
| 1663 |
+
<li>Wait for the analysis to complete</li>
|
| 1664 |
+
<li>Review your detailed evaluation</li>
|
| 1665 |
<li>Download the report</li>
|
| 1666 |
</ol>
|
| 1667 |
</div>
|
| 1668 |
""", unsafe_allow_html=True)
|
| 1669 |
|
| 1670 |
+
st.markdown("""
|
| 1671 |
+
<div class="sidebar-content">
|
| 1672 |
+
<h3 style='color: #2c3e50;'>📁 File Requirements</h3>
|
| 1673 |
+
<p style='color: #4a5568;'><strong>Supported formats:</strong> MP4, AVI, MOV<br>
|
| 1674 |
+
<strong>Maximum file size:</strong> 500MB</p>
|
| 1675 |
+
</div>
|
| 1676 |
+
""", unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1677 |
|
| 1678 |
if uploaded_file:
|
| 1679 |
+
# Add a modern processing animation
|
| 1680 |
st.markdown("""
|
| 1681 |
+
<div class="status-processing">
|
| 1682 |
+
<h3>🔄 Processing Your Video</h3>
|
| 1683 |
+
<p>Please wait while we analyze your teaching demo...</p>
|
| 1684 |
</div>
|
| 1685 |
""", unsafe_allow_html=True)
|
| 1686 |
+
|
| 1687 |
+
# Display results using stored evaluation
|
| 1688 |
+
st.markdown("""
|
| 1689 |
+
<div class="status-complete">
|
| 1690 |
+
<h3>✅ Analysis Complete!</h3>
|
| 1691 |
+
<p>Review your detailed evaluation below</p>
|
| 1692 |
+
</div>
|
| 1693 |
+
""", unsafe_allow_html=True)
|
| 1694 |
+
|
| 1695 |
+
# Wrap the evaluation display in a card
|
| 1696 |
+
st.markdown('<div class="card">', unsafe_allow_html=True)
|
| 1697 |
+
display_evaluation(st.session_state.evaluation_results)
|
| 1698 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
| 1699 |
+
|
| 1700 |
+
# Modern download button
|
| 1701 |
+
st.markdown("""
|
| 1702 |
+
<div class="card" style="text-align: center;">
|
| 1703 |
+
<h3 style='color: #2c3e50;'>📥 Download Your Report</h3>
|
| 1704 |
+
</div>
|
| 1705 |
+
""", unsafe_allow_html=True)
|
| 1706 |
+
|
| 1707 |
+
if st.download_button(
|
| 1708 |
+
"Download Full Report",
|
| 1709 |
+
json.dumps(st.session_state.evaluation_results, indent=2),
|
| 1710 |
+
"evaluation_report.json",
|
| 1711 |
+
"application/json",
|
| 1712 |
+
help="Download the complete evaluation report in JSON format"
|
| 1713 |
+
):
|
| 1714 |
+
st.success("Report downloaded successfully!")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1715 |
|
| 1716 |
except Exception as e:
|
| 1717 |
st.error(f"Application error: {str(e)}")
|