|
|
<!DOCTYPE html> |
|
|
<html lang="en" dir="ltr"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Educational Activity Intelligence Analyzer</title> |
|
|
<link rel="preconnect" href="https://fonts.googleapis.com"> |
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"> |
|
|
|
|
|
<style> |
|
|
|
|
|
*, *::before, *::after { |
|
|
box-sizing: border-box; |
|
|
margin: 0; |
|
|
padding: 0; |
|
|
} |
|
|
|
|
|
|
|
|
:root { |
|
|
|
|
|
--color-primary: #7c3aed; |
|
|
--color-primary-hover: #6d28d9; |
|
|
--color-secondary: #3b82f6; |
|
|
--color-accent: #8b5cf6; |
|
|
--color-success: #10b981; |
|
|
--color-warning: #f59e0b; |
|
|
--color-error: #ef4444; |
|
|
|
|
|
|
|
|
--color-background: #faf7ff; |
|
|
--color-surface: #ffffff; |
|
|
--color-surface-elevated: #ffffff; |
|
|
--color-border: #e9d5ff; |
|
|
--color-border-focus: #7c3aed; |
|
|
--color-text-primary: #1f2937; |
|
|
--color-text-secondary: #4b5563; |
|
|
--color-text-muted: #6b7280; |
|
|
|
|
|
|
|
|
--font-family-primary: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; |
|
|
|
|
|
|
|
|
--spacing-xs: 0.25rem; |
|
|
--spacing-sm: 0.5rem; |
|
|
--spacing-md: 0.75rem; |
|
|
--spacing-lg: 1rem; |
|
|
--spacing-xl: 1.25rem; |
|
|
--spacing-2xl: 1.5rem; |
|
|
--spacing-3xl: 2rem; |
|
|
|
|
|
|
|
|
--radius-sm: 0.375rem; |
|
|
--radius-md: 0.5rem; |
|
|
--radius-lg: 0.75rem; |
|
|
--radius-xl: 1rem; |
|
|
|
|
|
|
|
|
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); |
|
|
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); |
|
|
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); |
|
|
--shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); |
|
|
|
|
|
|
|
|
--transition-fast: 150ms ease-in-out; |
|
|
--transition-normal: 250ms ease-in-out; |
|
|
--transition-slow: 350ms ease-in-out; |
|
|
|
|
|
|
|
|
--container-max-width: 1200px; |
|
|
--content-max-width: 1000px; |
|
|
} |
|
|
|
|
|
|
|
|
[dir="rtl"] { |
|
|
text-align: right; |
|
|
} |
|
|
|
|
|
[dir="rtl"] .app-container { |
|
|
direction: rtl; |
|
|
} |
|
|
|
|
|
[dir="rtl"] .api-key-compact { |
|
|
left: auto; |
|
|
right: var(--spacing-lg); |
|
|
} |
|
|
|
|
|
[dir="rtl"] .language-switcher { |
|
|
right: auto; |
|
|
left: var(--spacing-lg); |
|
|
} |
|
|
|
|
|
|
|
|
@media (prefers-color-scheme: dark) { |
|
|
:root { |
|
|
--color-background: #0f0a1a; |
|
|
--color-surface: #1e1b31; |
|
|
--color-surface-elevated: #2d2a45; |
|
|
--color-border: #2d2a45; |
|
|
--color-text-primary: #f9fafb; |
|
|
--color-text-secondary: #d1d5db; |
|
|
--color-text-muted: #9ca3af; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
html { |
|
|
font-size: 16px; |
|
|
line-height: 1.5; |
|
|
scroll-behavior: smooth; |
|
|
} |
|
|
|
|
|
body { |
|
|
font-family: var(--font-family-primary); |
|
|
background-color: var(--color-background); |
|
|
color: var(--color-text-primary); |
|
|
line-height: 1.5; |
|
|
-webkit-font-smoothing: antialiased; |
|
|
-moz-osx-font-smoothing: grayscale; |
|
|
min-height: 100vh; |
|
|
transition: all 0.3s ease; |
|
|
} |
|
|
|
|
|
|
|
|
.language-landing { |
|
|
display: block; |
|
|
max-width: 600px; |
|
|
margin: 50px auto; |
|
|
background: var(--color-surface); |
|
|
border-radius: var(--radius-xl); |
|
|
box-shadow: var(--shadow-lg); |
|
|
padding: 40px; |
|
|
text-align: center; |
|
|
} |
|
|
|
|
|
.language-landing h1 { |
|
|
font-size: 2.5rem; |
|
|
font-weight: 800; |
|
|
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-secondary) 100%); |
|
|
-webkit-background-clip: text; |
|
|
-webkit-text-fill-color: transparent; |
|
|
background-clip: text; |
|
|
margin-bottom: 15px; |
|
|
} |
|
|
|
|
|
.language-landing p { |
|
|
font-size: 1.1rem; |
|
|
color: var(--color-text-secondary); |
|
|
margin-bottom: 30px; |
|
|
} |
|
|
|
|
|
.language-selector { |
|
|
margin-bottom: 25px; |
|
|
} |
|
|
|
|
|
.language-selector label { |
|
|
display: block; |
|
|
margin-bottom: 10px; |
|
|
font-weight: 600; |
|
|
color: var(--color-text-primary); |
|
|
font-size: 1.1rem; |
|
|
} |
|
|
|
|
|
.language-selector select { |
|
|
width: 100%; |
|
|
padding: 15px 20px; |
|
|
border: 2px solid var(--color-border); |
|
|
border-radius: var(--radius-lg); |
|
|
font-size: 1.1rem; |
|
|
font-family: inherit; |
|
|
background: var(--color-surface); |
|
|
transition: all 0.2s ease; |
|
|
margin-bottom: 20px; |
|
|
} |
|
|
|
|
|
.language-selector select:focus { |
|
|
outline: none; |
|
|
border-color: var(--color-border-focus); |
|
|
box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.1); |
|
|
} |
|
|
|
|
|
.api-key-landing { |
|
|
margin-bottom: 25px; |
|
|
} |
|
|
|
|
|
.api-key-landing label { |
|
|
display: block; |
|
|
margin-bottom: 10px; |
|
|
font-weight: 600; |
|
|
color: var(--color-text-primary); |
|
|
font-size: 1.1rem; |
|
|
} |
|
|
|
|
|
.api-key-landing input { |
|
|
width: 100%; |
|
|
padding: 15px 20px; |
|
|
border: 2px solid var(--color-border); |
|
|
border-radius: var(--radius-lg); |
|
|
font-size: 1.1rem; |
|
|
background: var(--color-surface); |
|
|
transition: all 0.2s ease; |
|
|
font-family: 'Courier New', monospace; |
|
|
} |
|
|
|
|
|
.api-key-landing input:focus { |
|
|
outline: none; |
|
|
border-color: var(--color-border-focus); |
|
|
box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.1); |
|
|
} |
|
|
|
|
|
.start-btn { |
|
|
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-secondary) 100%); |
|
|
color: white; |
|
|
padding: 18px 40px; |
|
|
border: none; |
|
|
border-radius: var(--radius-lg); |
|
|
font-size: 1.2rem; |
|
|
font-weight: 700; |
|
|
cursor: pointer; |
|
|
transition: all 0.3s ease; |
|
|
width: 100%; |
|
|
} |
|
|
|
|
|
.start-btn:hover { |
|
|
transform: translateY(-2px); |
|
|
box-shadow: 0 12px 24px -8px rgba(124, 58, 237, 0.4); |
|
|
} |
|
|
|
|
|
.start-btn:disabled { |
|
|
opacity: 0.7; |
|
|
cursor: not-allowed; |
|
|
transform: none; |
|
|
} |
|
|
|
|
|
|
|
|
.cache-management { |
|
|
background: rgba(124, 58, 237, 0.05); |
|
|
border: 1px solid rgba(124, 58, 237, 0.1); |
|
|
border-radius: var(--radius-lg); |
|
|
padding: 20px; |
|
|
margin-bottom: 25px; |
|
|
text-align: center; |
|
|
} |
|
|
|
|
|
.cache-management h3 { |
|
|
color: var(--color-primary); |
|
|
margin-bottom: 15px; |
|
|
font-size: 1.1rem; |
|
|
font-weight: 600; |
|
|
} |
|
|
|
|
|
.cache-status-display { |
|
|
background: rgba(16, 185, 129, 0.1); |
|
|
border: 1px solid rgba(16, 185, 129, 0.2); |
|
|
color: var(--color-success); |
|
|
padding: 12px 16px; |
|
|
border-radius: var(--radius-md); |
|
|
margin-bottom: 15px; |
|
|
font-size: 0.9rem; |
|
|
font-weight: 500; |
|
|
} |
|
|
|
|
|
.cache-status-display.no-cache { |
|
|
background: rgba(100, 116, 139, 0.1); |
|
|
border-color: rgba(100, 116, 139, 0.2); |
|
|
color: var(--color-text-secondary); |
|
|
} |
|
|
|
|
|
.clear-cache-btn { |
|
|
background: linear-gradient(135deg, var(--color-warning) 0%, #f59e0b 100%); |
|
|
color: white; |
|
|
padding: 12px 24px; |
|
|
border: none; |
|
|
border-radius: var(--radius-md); |
|
|
font-size: 0.9rem; |
|
|
font-weight: 600; |
|
|
cursor: pointer; |
|
|
transition: all 0.3s ease; |
|
|
} |
|
|
|
|
|
.clear-cache-btn:hover { |
|
|
transform: translateY(-1px); |
|
|
box-shadow: 0 6px 12px -4px rgba(245, 158, 11, 0.4); |
|
|
} |
|
|
|
|
|
.clear-cache-btn:disabled { |
|
|
opacity: 0.5; |
|
|
cursor: not-allowed; |
|
|
transform: none; |
|
|
} |
|
|
|
|
|
|
|
|
.cache-status { |
|
|
background: rgba(16, 185, 129, 0.1); |
|
|
border: 1px solid rgba(16, 185, 129, 0.2); |
|
|
color: var(--color-success); |
|
|
padding: 8px 12px; |
|
|
border-radius: var(--radius-sm); |
|
|
margin-bottom: 15px; |
|
|
font-size: 0.85rem; |
|
|
text-align: center; |
|
|
font-weight: 500; |
|
|
display: none; |
|
|
} |
|
|
|
|
|
.cache-status.cached { |
|
|
display: block; |
|
|
} |
|
|
|
|
|
.cache-status.translating { |
|
|
background: rgba(124, 58, 237, 0.1); |
|
|
border-color: rgba(124, 58, 237, 0.2); |
|
|
color: var(--color-primary); |
|
|
} |
|
|
|
|
|
|
|
|
.translation-overlay { |
|
|
position: fixed; |
|
|
top: 0; |
|
|
left: 0; |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
background: rgba(124, 58, 237, 0.9); |
|
|
display: none; |
|
|
justify-content: center; |
|
|
align-items: center; |
|
|
z-index: 10000; |
|
|
color: white; |
|
|
text-align: center; |
|
|
} |
|
|
|
|
|
.translation-content { |
|
|
background: rgba(255, 255, 255, 0.1); |
|
|
padding: 40px; |
|
|
border-radius: var(--radius-xl); |
|
|
backdrop-filter: blur(10px); |
|
|
} |
|
|
|
|
|
.translation-spinner { |
|
|
width: 50px; |
|
|
height: 50px; |
|
|
border: 4px solid rgba(255, 255, 255, 0.3); |
|
|
border-radius: 50%; |
|
|
border-top-color: white; |
|
|
animation: spin 1s ease-in-out infinite; |
|
|
margin: 0 auto 20px; |
|
|
} |
|
|
|
|
|
|
|
|
.main-app { |
|
|
display: none; |
|
|
} |
|
|
|
|
|
|
|
|
.app-container { |
|
|
min-height: 100vh; |
|
|
max-width: var(--container-max-width); |
|
|
margin: 0 auto; |
|
|
padding: var(--spacing-lg); |
|
|
position: relative; |
|
|
} |
|
|
|
|
|
|
|
|
.language-switcher { |
|
|
position: absolute; |
|
|
top: var(--spacing-lg); |
|
|
right: var(--spacing-lg); |
|
|
display: flex; |
|
|
gap: 10px; |
|
|
align-items: center; |
|
|
z-index: 10; |
|
|
} |
|
|
|
|
|
.language-switch-btn { |
|
|
background: rgba(124, 58, 237, 0.05); |
|
|
border: 1px solid rgba(124, 58, 237, 0.1); |
|
|
border-radius: var(--radius-md); |
|
|
padding: 8px 12px; |
|
|
font-size: 12px; |
|
|
cursor: pointer; |
|
|
transition: all 0.2s ease; |
|
|
color: var(--color-primary); |
|
|
font-weight: 500; |
|
|
} |
|
|
|
|
|
.language-switch-btn:hover { |
|
|
background: rgba(124, 58, 237, 0.1); |
|
|
} |
|
|
|
|
|
.mini-clear-cache { |
|
|
background: rgba(245, 158, 11, 0.1); |
|
|
border: 1px solid rgba(245, 158, 11, 0.2); |
|
|
color: var(--color-warning); |
|
|
padding: 6px 10px; |
|
|
border-radius: var(--radius-sm); |
|
|
font-size: 11px; |
|
|
cursor: pointer; |
|
|
transition: all 0.2s ease; |
|
|
font-weight: 500; |
|
|
} |
|
|
|
|
|
.mini-clear-cache:hover { |
|
|
background: rgba(245, 158, 11, 0.2); |
|
|
} |
|
|
|
|
|
|
|
|
.api-key-compact { |
|
|
position: absolute; |
|
|
top: var(--spacing-lg); |
|
|
left: var(--spacing-lg); |
|
|
z-index: 10; |
|
|
background: var(--color-surface); |
|
|
border: 1px solid var(--color-border); |
|
|
border-radius: var(--radius-lg); |
|
|
padding: var(--spacing-md) var(--spacing-lg); |
|
|
box-shadow: var(--shadow-md); |
|
|
transition: all var(--transition-normal); |
|
|
max-width: 280px; |
|
|
} |
|
|
|
|
|
.api-key-compact:hover { |
|
|
box-shadow: var(--shadow-lg); |
|
|
} |
|
|
|
|
|
.api-key-compact-label { |
|
|
display: block; |
|
|
font-size: 0.75rem; |
|
|
font-weight: 600; |
|
|
color: var(--color-text-secondary); |
|
|
margin-bottom: var(--spacing-xs); |
|
|
text-transform: uppercase; |
|
|
letter-spacing: 0.05em; |
|
|
} |
|
|
|
|
|
.api-key-compact-input { |
|
|
width: 100%; |
|
|
padding: var(--spacing-sm) var(--spacing-md); |
|
|
border: 1px solid var(--color-border); |
|
|
border-radius: var(--radius-sm); |
|
|
font-size: 0.875rem; |
|
|
background: var(--color-background); |
|
|
color: var(--color-text-primary); |
|
|
transition: all var(--transition-fast); |
|
|
font-family: 'Courier New', monospace; |
|
|
} |
|
|
|
|
|
.api-key-compact-input:focus { |
|
|
outline: none; |
|
|
border-color: var(--color-border-focus); |
|
|
box-shadow: 0 0 0 2px rgb(124 58 237 / 0.1); |
|
|
} |
|
|
|
|
|
.api-key-compact-input::placeholder { |
|
|
color: var(--color-text-muted); |
|
|
font-size: 0.75rem; |
|
|
} |
|
|
|
|
|
|
|
|
.app-main { |
|
|
max-width: var(--content-max-width); |
|
|
margin: 0 auto; |
|
|
padding-top: var(--spacing-2xl); |
|
|
} |
|
|
|
|
|
|
|
|
.app-header { |
|
|
text-align: center; |
|
|
margin-bottom: var(--spacing-3xl); |
|
|
} |
|
|
|
|
|
.app-title { |
|
|
font-size: 2.5rem; |
|
|
font-weight: 700; |
|
|
background: linear-gradient(135deg, var(--color-primary), var(--color-secondary)); |
|
|
-webkit-background-clip: text; |
|
|
-webkit-text-fill-color: transparent; |
|
|
background-clip: text; |
|
|
margin-bottom: var(--spacing-lg); |
|
|
line-height: 1.2; |
|
|
} |
|
|
|
|
|
.app-subtitle { |
|
|
font-size: 1.125rem; |
|
|
color: var(--color-text-secondary); |
|
|
max-width: 700px; |
|
|
margin: 0 auto; |
|
|
line-height: 1.4; |
|
|
} |
|
|
|
|
|
|
|
|
.input-section { |
|
|
background: var(--color-surface); |
|
|
border-radius: var(--radius-xl); |
|
|
padding: var(--spacing-3xl); |
|
|
box-shadow: var(--shadow-md); |
|
|
margin-bottom: var(--spacing-3xl); |
|
|
transition: all var(--transition-normal); |
|
|
} |
|
|
|
|
|
.input-section:hover { |
|
|
box-shadow: var(--shadow-lg); |
|
|
} |
|
|
|
|
|
.section-title { |
|
|
font-size: 1.5rem; |
|
|
font-weight: 600; |
|
|
color: var(--color-text-primary); |
|
|
margin-bottom: var(--spacing-xl); |
|
|
text-align: center; |
|
|
} |
|
|
|
|
|
.form-group { |
|
|
margin-bottom: var(--spacing-xl); |
|
|
} |
|
|
|
|
|
.form-label { |
|
|
display: block; |
|
|
font-size: 1rem; |
|
|
font-weight: 600; |
|
|
color: var(--color-text-primary); |
|
|
margin-bottom: var(--spacing-md); |
|
|
} |
|
|
|
|
|
.form-textarea { |
|
|
width: 100%; |
|
|
min-height: 120px; |
|
|
padding: var(--spacing-lg); |
|
|
border: 2px solid var(--color-border); |
|
|
border-radius: var(--radius-lg); |
|
|
font-size: 1rem; |
|
|
font-family: var(--font-family-primary); |
|
|
background: var(--color-background); |
|
|
color: var(--color-text-primary); |
|
|
transition: all var(--transition-fast); |
|
|
resize: vertical; |
|
|
} |
|
|
|
|
|
.form-textarea:focus { |
|
|
outline: none; |
|
|
border-color: var(--color-border-focus); |
|
|
box-shadow: 0 0 0 3px rgb(124 58 237 / 0.1); |
|
|
} |
|
|
|
|
|
.form-textarea::placeholder { |
|
|
color: var(--color-text-muted); |
|
|
} |
|
|
|
|
|
.analyze-button { |
|
|
display: inline-flex; |
|
|
align-items: center; |
|
|
gap: var(--spacing-md); |
|
|
padding: var(--spacing-lg) var(--spacing-3xl); |
|
|
font-size: 1.125rem; |
|
|
font-weight: 600; |
|
|
color: white; |
|
|
background: linear-gradient(135deg, var(--color-primary), var(--color-secondary)); |
|
|
border: none; |
|
|
border-radius: var(--radius-lg); |
|
|
cursor: pointer; |
|
|
transition: all var(--transition-normal); |
|
|
box-shadow: var(--shadow-md); |
|
|
position: relative; |
|
|
overflow: hidden; |
|
|
margin: 0 auto; |
|
|
display: block; |
|
|
} |
|
|
|
|
|
.analyze-button:hover:not(:disabled) { |
|
|
transform: translateY(-2px); |
|
|
box-shadow: var(--shadow-xl); |
|
|
} |
|
|
|
|
|
.analyze-button:active { |
|
|
transform: translateY(0); |
|
|
} |
|
|
|
|
|
.analyze-button:disabled { |
|
|
opacity: 0.7; |
|
|
cursor: not-allowed; |
|
|
transform: none; |
|
|
} |
|
|
|
|
|
.button-text { |
|
|
transition: opacity var(--transition-fast); |
|
|
} |
|
|
|
|
|
.loading-spinner { |
|
|
display: none; |
|
|
width: 20px; |
|
|
height: 20px; |
|
|
border: 2px solid rgba(255, 255, 255, 0.3); |
|
|
border-top: 2px solid white; |
|
|
border-radius: 50%; |
|
|
animation: spin 1s linear infinite; |
|
|
} |
|
|
|
|
|
@keyframes spin { |
|
|
0% { transform: rotate(0deg); } |
|
|
100% { transform: rotate(360deg); } |
|
|
} |
|
|
|
|
|
|
|
|
.results-container { |
|
|
display: grid; |
|
|
gap: var(--spacing-3xl); |
|
|
margin-top: var(--spacing-3xl); |
|
|
} |
|
|
|
|
|
.agent-card { |
|
|
background: var(--color-surface); |
|
|
border: 1px solid var(--color-border); |
|
|
border-radius: var(--radius-xl); |
|
|
padding: var(--spacing-3xl); |
|
|
box-shadow: var(--shadow-md); |
|
|
transition: all var(--transition-normal); |
|
|
position: relative; |
|
|
overflow: hidden; |
|
|
} |
|
|
|
|
|
.agent-card:hover { |
|
|
box-shadow: var(--shadow-lg); |
|
|
border-color: var(--color-accent); |
|
|
} |
|
|
|
|
|
.agent-card::before { |
|
|
content: ''; |
|
|
position: absolute; |
|
|
top: 0; |
|
|
left: 0; |
|
|
right: 0; |
|
|
height: 4px; |
|
|
background: linear-gradient(90deg, var(--color-primary), var(--color-secondary)); |
|
|
} |
|
|
|
|
|
.agent-header { |
|
|
display: flex; |
|
|
align-items: center; |
|
|
gap: var(--spacing-lg); |
|
|
margin-bottom: var(--spacing-xl); |
|
|
} |
|
|
|
|
|
.agent-icon { |
|
|
width: 48px; |
|
|
height: 48px; |
|
|
background: linear-gradient(135deg, var(--color-primary), var(--color-secondary)); |
|
|
border-radius: 50%; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
color: white; |
|
|
font-weight: 700; |
|
|
font-size: 1.25rem; |
|
|
} |
|
|
|
|
|
.agent-title { |
|
|
font-size: 1.5rem; |
|
|
font-weight: 600; |
|
|
color: var(--color-text-primary); |
|
|
margin: 0; |
|
|
} |
|
|
|
|
|
.agent-subtitle { |
|
|
font-size: 0.875rem; |
|
|
color: var(--color-text-secondary); |
|
|
margin: 0; |
|
|
} |
|
|
|
|
|
.agent-content { |
|
|
background: var(--color-background); |
|
|
border-radius: var(--radius-lg); |
|
|
padding: var(--spacing-xl); |
|
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; |
|
|
font-size: 0.875rem; |
|
|
line-height: 1.6; |
|
|
color: var(--color-text-primary); |
|
|
white-space: pre-wrap; |
|
|
word-wrap: break-word; |
|
|
border: 1px solid var(--color-border); |
|
|
min-height: 100px; |
|
|
max-height: 600px; |
|
|
overflow-y: auto; |
|
|
} |
|
|
|
|
|
.agent-content:empty::before { |
|
|
content: 'Analysis will appear here...'; |
|
|
color: var(--color-text-muted); |
|
|
font-style: italic; |
|
|
font-family: var(--font-family-primary); |
|
|
} |
|
|
|
|
|
|
|
|
.agent-card.loading .agent-content { |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
min-height: 120px; |
|
|
} |
|
|
|
|
|
.agent-card.loading .agent-content::before { |
|
|
content: ''; |
|
|
width: 32px; |
|
|
height: 32px; |
|
|
border: 3px solid var(--color-border); |
|
|
border-top: 3px solid var(--color-primary); |
|
|
border-radius: 50%; |
|
|
animation: spin 1s linear infinite; |
|
|
} |
|
|
|
|
|
|
|
|
.error-message { |
|
|
background: #fef2f2; |
|
|
border: 1px solid #fecaca; |
|
|
color: var(--color-error); |
|
|
padding: var(--spacing-lg); |
|
|
border-radius: var(--radius-lg); |
|
|
margin-bottom: var(--spacing-lg); |
|
|
display: flex; |
|
|
align-items: center; |
|
|
gap: var(--spacing-md); |
|
|
} |
|
|
|
|
|
.error-icon { |
|
|
font-size: 1.25rem; |
|
|
} |
|
|
|
|
|
.error-dismiss { |
|
|
margin-left: auto; |
|
|
background: none; |
|
|
border: none; |
|
|
color: var(--color-error); |
|
|
cursor: pointer; |
|
|
font-size: 1.25rem; |
|
|
padding: var(--spacing-xs); |
|
|
border-radius: var(--radius-sm); |
|
|
} |
|
|
|
|
|
.error-dismiss:hover { |
|
|
background: rgba(239, 68, 68, 0.1); |
|
|
} |
|
|
|
|
|
|
|
|
.hidden { |
|
|
display: none !important; |
|
|
} |
|
|
|
|
|
|
|
|
.app-footer { |
|
|
text-align: center; |
|
|
padding: var(--spacing-xl); |
|
|
color: var(--color-text-secondary); |
|
|
font-size: 0.9rem; |
|
|
margin-top: var(--spacing-3xl); |
|
|
} |
|
|
|
|
|
|
|
|
@media (max-width: 768px) { |
|
|
.language-landing { |
|
|
margin: 20px auto; |
|
|
padding: 30px 20px; |
|
|
} |
|
|
|
|
|
.app-container { |
|
|
padding: var(--spacing-md); |
|
|
} |
|
|
|
|
|
.api-key-compact { |
|
|
position: static; |
|
|
margin-bottom: var(--spacing-xl); |
|
|
max-width: none; |
|
|
} |
|
|
|
|
|
.language-switcher { |
|
|
position: relative; |
|
|
top: auto; |
|
|
right: auto; |
|
|
margin-bottom: var(--spacing-xl); |
|
|
justify-content: center; |
|
|
} |
|
|
|
|
|
.app-main { |
|
|
padding-top: 0; |
|
|
} |
|
|
|
|
|
.app-title { |
|
|
font-size: 2rem; |
|
|
} |
|
|
|
|
|
.app-subtitle { |
|
|
font-size: 1rem; |
|
|
} |
|
|
|
|
|
.input-section, |
|
|
.agent-card { |
|
|
padding: var(--spacing-xl); |
|
|
} |
|
|
|
|
|
.agent-header { |
|
|
flex-direction: column; |
|
|
text-align: center; |
|
|
gap: var(--spacing-md); |
|
|
} |
|
|
|
|
|
.agent-content { |
|
|
font-size: 0.8rem; |
|
|
max-height: 400px; |
|
|
} |
|
|
} |
|
|
|
|
|
@media (max-width: 480px) { |
|
|
.language-landing h1 { |
|
|
font-size: 2rem; |
|
|
} |
|
|
|
|
|
.app-title { |
|
|
font-size: 1.8rem; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.sr-only { |
|
|
position: absolute; |
|
|
width: 1px; |
|
|
height: 1px; |
|
|
padding: 0; |
|
|
margin: -1px; |
|
|
overflow: hidden; |
|
|
clip: rect(0, 0, 0, 0); |
|
|
white-space: nowrap; |
|
|
border: 0; |
|
|
} |
|
|
|
|
|
|
|
|
.analyze-button:focus, |
|
|
.api-key-compact-input:focus, |
|
|
.form-textarea:focus { |
|
|
outline: 2px solid var(--color-primary); |
|
|
outline-offset: 2px; |
|
|
} |
|
|
|
|
|
|
|
|
@media (prefers-reduced-motion: reduce) { |
|
|
*, |
|
|
*::before, |
|
|
*::after { |
|
|
animation-duration: 0.01ms !important; |
|
|
animation-iteration-count: 1 !important; |
|
|
transition-duration: 0.01ms !important; |
|
|
scroll-behavior: auto !important; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.agent-content::-webkit-scrollbar { |
|
|
width: 8px; |
|
|
} |
|
|
|
|
|
.agent-content::-webkit-scrollbar-track { |
|
|
background: var(--color-border); |
|
|
border-radius: var(--radius-sm); |
|
|
} |
|
|
|
|
|
.agent-content::-webkit-scrollbar-thumb { |
|
|
background: var(--color-accent); |
|
|
border-radius: var(--radius-sm); |
|
|
} |
|
|
|
|
|
.agent-content::-webkit-scrollbar-thumb:hover { |
|
|
background: var(--color-primary); |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
|
|
|
<body> |
|
|
|
|
|
<div class="translation-overlay" id="translationOverlay"> |
|
|
<div class="translation-content"> |
|
|
<div class="translation-spinner"></div> |
|
|
<h2 id="translationTitle">Translating Interface...</h2> |
|
|
<p id="translationMessage">Please wait while we translate the interface to your selected language.</p> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="language-landing" id="languageLanding"> |
|
|
<h1 data-translate="app_title">Educational Activity Intelligence Analyzer</h1> |
|
|
<p data-translate="welcome_message">Welcome! Please select your preferred language and enter your API key to get started with advanced multi-agent analysis of teaching activities.</p> |
|
|
|
|
|
|
|
|
<div class="cache-status" id="cacheStatus"> |
|
|
💾 Translations cached - instant loading! |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="cache-management" id="cacheManagement"> |
|
|
<h3 data-translate="cache_management_title">🗂️ Translation Cache Management</h3> |
|
|
<div class="cache-status-display" id="cacheStatusDisplay"> |
|
|
<span data-translate="cache_status_checking">Checking cache status...</span> |
|
|
</div> |
|
|
<button class="clear-cache-btn" id="clearCacheBtn" data-translate="clear_cache_button"> |
|
|
🗑️ Clear All Cached Translations |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
<div class="language-selector"> |
|
|
<label for="languageSelect" data-translate="select_language">🌐 Select Language</label> |
|
|
<select id="languageSelect"> |
|
|
<option value="en">🇺🇸 English</option> |
|
|
<option value="es">🇪🇸 Español (Spanish)</option> |
|
|
<option value="fr">🇫🇷 Français (French)</option> |
|
|
<option value="de">🇩🇪 Deutsch (German)</option> |
|
|
<option value="zh">🇨🇳 中文 (Chinese)</option> |
|
|
<option value="ja">🇯🇵 日本語 (Japanese)</option> |
|
|
<option value="ko">🇰🇷 한국어 (Korean)</option> |
|
|
<option value="pt">🇵🇹 Português (Portuguese)</option> |
|
|
<option value="it">🇮🇹 Italiano (Italian)</option> |
|
|
<option value="ar">🇸🇦 العربية (Arabic)</option> |
|
|
<option value="ru">🇷🇺 Русский (Russian)</option> |
|
|
<option value="hi">🇮🇳 हिन्दी (Hindi)</option> |
|
|
<option value="bn">🇧🇩 বাংলা (Bengali)</option> |
|
|
<option value="ur">🇵🇰 اردو (Urdu)</option> |
|
|
<option value="tr">🇹🇷 Türkçe (Turkish)</option> |
|
|
<option value="pl">🇵🇱 Polski (Polish)</option> |
|
|
<option value="nl">🇳🇱 Nederlands (Dutch)</option> |
|
|
<option value="sv">🇸🇪 Svenska (Swedish)</option> |
|
|
<option value="da">🇩🇰 Dansk (Danish)</option> |
|
|
<option value="no">🇳🇴 Norsk (Norwegian)</option> |
|
|
<option value="fi">🇫🇮 Suomi (Finnish)</option> |
|
|
<option value="is">🇮🇸 Íslenska (Icelandic)</option> |
|
|
<option value="cs">🇨🇿 Čeština (Czech)</option> |
|
|
<option value="sk">🇸🇰 Slovenčina (Slovak)</option> |
|
|
<option value="hu">🇭🇺 Magyar (Hungarian)</option> |
|
|
<option value="ro">🇷🇴 Română (Romanian)</option> |
|
|
<option value="bg">🇧🇬 Български (Bulgarian)</option> |
|
|
<option value="hr">🇭🇷 Hrvatski (Croatian)</option> |
|
|
<option value="sr">🇷🇸 Српски (Serbian)</option> |
|
|
<option value="sl">🇸🇮 Slovenščina (Slovenian)</option> |
|
|
<option value="mk">🇲🇰 Македонски (Macedonian)</option> |
|
|
<option value="sq">🇦🇱 Shqip (Albanian)</option> |
|
|
<option value="lv">🇱🇻 Latviešu (Latvian)</option> |
|
|
<option value="lt">🇱🇹 Lietuvių (Lithuanian)</option> |
|
|
<option value="et">🇪🇪 Eesti (Estonian)</option> |
|
|
<option value="mt">🇲🇹 Malti (Maltese)</option> |
|
|
<option value="ga">🇮🇪 Gaeilge (Irish)</option> |
|
|
<option value="cy">🏴 Cymraeg (Welsh)</option> |
|
|
<option value="eu">🏴 Euskera (Basque)</option> |
|
|
<option value="ca">🏴 Català (Catalan)</option> |
|
|
<option value="gl">🏴 Galego (Galician)</option> |
|
|
<option value="el">🇬🇷 Ελληνικά (Greek)</option> |
|
|
<option value="he">🇮🇱 עברית (Hebrew)</option> |
|
|
<option value="fa">🇮🇷 فارسی (Persian)</option> |
|
|
<option value="ps">🇦🇫 پښتو (Pashto)</option> |
|
|
<option value="ku">🏴 کوردی (Kurdish)</option> |
|
|
<option value="az">🇦🇿 Azərbaycan (Azerbaijani)</option> |
|
|
<option value="kk">🇰🇿 Қазақша (Kazakh)</option> |
|
|
<option value="ky">🇰🇬 Кыргызча (Kyrgyz)</option> |
|
|
<option value="uz">🇺🇿 O'zbek (Uzbek)</option> |
|
|
<option value="tk">🇹🇲 Türkmen (Turkmen)</option> |
|
|
<option value="tg">🇹🇯 Тоҷикӣ (Tajik)</option> |
|
|
<option value="mn">🇲🇳 Монгол (Mongolian)</option> |
|
|
<option value="ka">🇬🇪 ქართული (Georgian)</option> |
|
|
<option value="hy">🇦🇲 Հայերեն (Armenian)</option> |
|
|
<option value="th">🇹🇭 ไทย (Thai)</option> |
|
|
<option value="vi">🇻🇳 Tiếng Việt (Vietnamese)</option> |
|
|
<option value="lo">🇱🇦 ລາວ (Lao)</option> |
|
|
<option value="km">🇰🇭 ខ្មែរ (Khmer)</option> |
|
|
<option value="my">🇲🇲 မြန်မာ (Myanmar)</option> |
|
|
<option value="si">🇱🇰 සිංහල (Sinhala)</option> |
|
|
<option value="ta">🇱🇰 தமிழ் (Tamil)</option> |
|
|
<option value="te">🇮🇳 తెలుగు (Telugu)</option> |
|
|
<option value="kn">🇮🇳 ಕನ್ನಡ (Kannada)</option> |
|
|
<option value="ml">🇮🇳 മലയാളം (Malayalam)</option> |
|
|
<option value="gu">🇮🇳 ગુજરાતી (Gujarati)</option> |
|
|
<option value="pa">🇮🇳 ਪੰਜਾਬੀ (Punjabi)</option> |
|
|
<option value="or">🇮🇳 ଓଡ଼ିଆ (Odia)</option> |
|
|
<option value="as">🇮🇳 অসমীয়া (Assamese)</option> |
|
|
<option value="ne">🇳🇵 नेपाली (Nepali)</option> |
|
|
<option value="dz">🇧🇹 རྫོང་ཁ (Dzongkha)</option> |
|
|
<option value="bo">🏔️ བོད་ཡིག (Tibetan)</option> |
|
|
<option value="id">🇮🇩 Bahasa Indonesia</option> |
|
|
<option value="ms">🇲🇾 Bahasa Melayu (Malay)</option> |
|
|
<option value="tl">🇵🇭 Filipino (Tagalog)</option> |
|
|
<option value="ceb">🇵🇭 Cebuano</option> |
|
|
<option value="haw">🏝️ ʻŌlelo Hawaiʻi (Hawaiian)</option> |
|
|
<option value="mi">🇳🇿 Te Reo Māori (Maori)</option> |
|
|
<option value="sm">🇼🇸 Gagana Samoa (Samoan)</option> |
|
|
<option value="to">🇹🇴 Lea Fakatonga (Tongan)</option> |
|
|
<option value="fj">🇫🇯 Na Vosa Vakaviti (Fijian)</option> |
|
|
<option value="mg">🇲🇬 Malagasy</option> |
|
|
<option value="sw">🇰🇪 Kiswahili (Swahili)</option> |
|
|
<option value="zu">🇿🇦 isiZulu (Zulu)</option> |
|
|
<option value="xh">🇿🇦 isiXhosa (Xhosa)</option> |
|
|
<option value="af">🇿🇦 Afrikaans</option> |
|
|
<option value="st">🇱🇸 Sesotho (Southern Sotho)</option> |
|
|
<option value="tn">🇧🇼 Setswana (Tswana)</option> |
|
|
<option value="ss">🇸🇿 siSwati (Swati)</option> |
|
|
<option value="ve">🇿🇦 Tshivenḓa (Venda)</option> |
|
|
<option value="ts">🇿🇦 Xitsonga (Tsonga)</option> |
|
|
<option value="nr">🇿🇦 isiNdebele (Southern Ndebele)</option> |
|
|
<option value="am">🇪🇹 አማርኛ (Amharic)</option> |
|
|
<option value="ti">🇪🇷 ትግርኛ (Tigrinya)</option> |
|
|
<option value="om">🇪🇹 Afaan Oromoo (Oromo)</option> |
|
|
<option value="so">🇸🇴 Soomaali (Somali)</option> |
|
|
<option value="ha">🇳🇬 Hausa</option> |
|
|
<option value="yo">🇳🇬 Yorùbá (Yoruba)</option> |
|
|
<option value="ig">🇳🇬 Igbo</option> |
|
|
<option value="ff">🇸🇳 Fulfulde (Fulani)</option> |
|
|
<option value="wo">🇸🇳 Wolof</option> |
|
|
<option value="bm">🇲🇱 Bamanankan (Bambara)</option> |
|
|
<option value="rn">🇧🇮 Kirundi (Rundi)</option> |
|
|
<option value="rw">🇷🇼 Kinyarwanda (Rwanda)</option> |
|
|
<option value="lg">🇺🇬 Luganda</option> |
|
|
<option value="ny">🇲🇼 Chichewa (Nyanja)</option> |
|
|
<option value="sn">🇿🇼 chiShona (Shona)</option> |
|
|
<option value="nd">🇿🇼 isiNdebele (Northern Ndebele)</option> |
|
|
</select> |
|
|
</div> |
|
|
|
|
|
<div class="api-key-landing"> |
|
|
<label for="apiKeyLanding" data-translate="api_key_label">🔑 OpenAI API Key</label> |
|
|
<input type="password" id="apiKeyLanding" placeholder="Enter your OpenAI API key" data-translate-placeholder="api_key_placeholder"> |
|
|
</div> |
|
|
|
|
|
<button class="start-btn" id="startBtn" data-translate="start_button">🚀 Start Educational Analysis</button> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="main-app" id="mainApp"> |
|
|
<div class="app-container"> |
|
|
|
|
|
<div class="language-switcher" id="languageSwitcher"> |
|
|
<div class="language-switch-btn" onclick="showLanguageLanding()"> |
|
|
<span data-translate="change_language">🌐 Change Language</span> |
|
|
</div> |
|
|
<div class="mini-clear-cache" onclick="clearAllTranslationCache()" title="Clear translation cache"> |
|
|
<span data-translate="clear_cache_mini">🗑️ Clear Cache</span> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="api-key-compact"> |
|
|
<label for="apiKey" class="api-key-compact-label" data-translate="api_key_short">API Key</label> |
|
|
<input |
|
|
type="password" |
|
|
id="apiKey" |
|
|
class="api-key-compact-input" |
|
|
data-translate-placeholder="api_key_placeholder" |
|
|
autocomplete="off" |
|
|
> |
|
|
</div> |
|
|
|
|
|
<main class="app-main"> |
|
|
<header class="app-header"> |
|
|
<h1 class="app-title" data-translate="app_title">Educational Activity Intelligence Analyzer</h1> |
|
|
<p class="app-subtitle" data-translate="app_subtitle"> |
|
|
Advanced multi-agent analysis of teaching activities using PICRAT model, Bloom's Taxonomy, and practical scenario development. |
|
|
Get comprehensive insights from three specialized AI agents to enhance your educational technology integration. |
|
|
</p> |
|
|
</header> |
|
|
|
|
|
|
|
|
<section class="input-section"> |
|
|
<h2 class="section-title" data-translate="section_title">Describe Your Teaching Activity</h2> |
|
|
<form id="analysisForm"> |
|
|
<div class="form-group"> |
|
|
<label for="activityDescription" class="form-label" data-translate="activity_label">Activity Description</label> |
|
|
<textarea |
|
|
id="activityDescription" |
|
|
class="form-textarea" |
|
|
data-translate-placeholder="activity_placeholder" |
|
|
required |
|
|
></textarea> |
|
|
</div> |
|
|
<button type="submit" class="analyze-button"> |
|
|
<span class="button-text" data-translate="analyze_button">🧠 Analyze Activity</span> |
|
|
<div class="loading-spinner" id="loadingSpinner"></div> |
|
|
</button> |
|
|
</form> |
|
|
</section> |
|
|
|
|
|
|
|
|
<section class="results-container"> |
|
|
|
|
|
<div class="agent-card" id="agent1Card"> |
|
|
<div class="agent-header"> |
|
|
<div class="agent-icon">1</div> |
|
|
<div> |
|
|
<h3 class="agent-title" data-translate="agent1_title">Deep Structured Analysis</h3> |
|
|
<p class="agent-subtitle" data-translate="agent1_subtitle">PICRAT Model • Bloom's Taxonomy • Critical Thinking Assessment</p> |
|
|
</div> |
|
|
</div> |
|
|
<div class="agent-content" id="analysisOutput1"></div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="agent-card" id="agent2Card"> |
|
|
<div class="agent-header"> |
|
|
<div class="agent-icon">2</div> |
|
|
<div> |
|
|
<h3 class="agent-title" data-translate="agent2_title">Practical Scenarios</h3> |
|
|
<p class="agent-subtitle" data-translate="agent2_subtitle">Implementation Strategies • Enhancement Opportunities • Real-world Applications</p> |
|
|
</div> |
|
|
</div> |
|
|
<div class="agent-content" id="analysisOutput2"></div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="agent-card" id="agent3Card"> |
|
|
<div class="agent-header"> |
|
|
<div class="agent-icon">3</div> |
|
|
<div> |
|
|
<h3 class="agent-title" data-translate="agent3_title">Meta-Analysis & Recommendations</h3> |
|
|
<p class="agent-subtitle" data-translate="agent3_subtitle">Synthesis • Best Practices Alignment • Strategic Recommendations</p> |
|
|
</div> |
|
|
</div> |
|
|
<div class="agent-content" id="analysisOutput3"></div> |
|
|
</div> |
|
|
</section> |
|
|
</main> |
|
|
|
|
|
<footer class="app-footer"> |
|
|
<p>Created By Shift Mind AI Labs</p> |
|
|
</footer> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
|
|
|
const rtlLanguages = ['ar', 'he', 'fa', 'ur', 'ps', 'ku']; |
|
|
|
|
|
|
|
|
let currentLanguage = 'en'; |
|
|
let currentApiKey = ''; |
|
|
|
|
|
|
|
|
const languageNames = { |
|
|
en: 'English', es: 'Spanish', fr: 'French', de: 'German', zh: 'Chinese', |
|
|
ja: 'Japanese', ko: 'Korean', pt: 'Portuguese', it: 'Italian', ar: 'Arabic', |
|
|
ru: 'Russian', hi: 'Hindi', bn: 'Bengali', ur: 'Urdu', tr: 'Turkish', |
|
|
pl: 'Polish', nl: 'Dutch', sv: 'Swedish', da: 'Danish', no: 'Norwegian', |
|
|
fi: 'Finnish', is: 'Icelandic', cs: 'Czech', sk: 'Slovak', hu: 'Hungarian', |
|
|
ro: 'Romanian', bg: 'Bulgarian', hr: 'Croatian', sr: 'Serbian', sl: 'Slovenian', |
|
|
mk: 'Macedonian', sq: 'Albanian', lv: 'Latvian', lt: 'Lithuanian', et: 'Estonian', |
|
|
mt: 'Maltese', ga: 'Irish', cy: 'Welsh', eu: 'Basque', ca: 'Catalan', |
|
|
gl: 'Galician', el: 'Greek', he: 'Hebrew', fa: 'Persian', ps: 'Pashto', |
|
|
ku: 'Kurdish', az: 'Azerbaijani', kk: 'Kazakh', ky: 'Kyrgyz', uz: 'Uzbek', |
|
|
tk: 'Turkmen', tg: 'Tajik', mn: 'Mongolian', ka: 'Georgian', hy: 'Armenian', |
|
|
th: 'Thai', vi: 'Vietnamese', lo: 'Lao', km: 'Khmer', my: 'Myanmar', |
|
|
si: 'Sinhala', ta: 'Tamil', te: 'Telugu', kn: 'Kannada', ml: 'Malayalam', |
|
|
gu: 'Gujarati', pa: 'Punjabi', or: 'Odia', as: 'Assamese', ne: 'Nepali', |
|
|
dz: 'Dzongkha', bo: 'Tibetan', id: 'Indonesian', ms: 'Malay', tl: 'Filipino', |
|
|
ceb: 'Cebuano', haw: 'Hawaiian', mi: 'Maori', sm: 'Samoan', to: 'Tongan', |
|
|
fj: 'Fijian', mg: 'Malagasy', sw: 'Swahili', zu: 'Zulu', xh: 'Xhosa', |
|
|
af: 'Afrikaans', st: 'Southern Sotho', tn: 'Tswana', ss: 'Swati', ve: 'Venda', |
|
|
ts: 'Tsonga', nr: 'Southern Ndebele', am: 'Amharic', ti: 'Tigrinya', om: 'Oromo', |
|
|
so: 'Somali', ha: 'Hausa', yo: 'Yoruba', ig: 'Igbo', ff: 'Fulani', |
|
|
wo: 'Wolof', bm: 'Bambara', rn: 'Rundi', rw: 'Rwanda', lg: 'Luganda', |
|
|
ny: 'Chichewa', sn: 'Shona', nd: 'Northern Ndebele' |
|
|
}; |
|
|
|
|
|
|
|
|
const CACHE_PREFIX = 'educational_analyzer_translations_'; |
|
|
const CACHE_VERSION = '1.0'; |
|
|
|
|
|
|
|
|
function isLanguageCached(language) { |
|
|
const cacheKey = CACHE_PREFIX + language; |
|
|
const cached = localStorage.getItem(cacheKey); |
|
|
return cached !== null; |
|
|
} |
|
|
|
|
|
|
|
|
function saveTranslationsToCache(language, translations) { |
|
|
const cacheKey = CACHE_PREFIX + language; |
|
|
const cacheData = { |
|
|
version: CACHE_VERSION, |
|
|
timestamp: Date.now(), |
|
|
translations: translations |
|
|
}; |
|
|
localStorage.setItem(cacheKey, JSON.stringify(cacheData)); |
|
|
console.log(`Translations cached for ${language}`); |
|
|
} |
|
|
|
|
|
|
|
|
function loadTranslationsFromCache(language) { |
|
|
const cacheKey = CACHE_PREFIX + language; |
|
|
const cached = localStorage.getItem(cacheKey); |
|
|
|
|
|
if (cached) { |
|
|
try { |
|
|
const cacheData = JSON.parse(cached); |
|
|
if (cacheData.version === CACHE_VERSION) { |
|
|
console.log(`Translations loaded from cache for ${language}`); |
|
|
return cacheData.translations; |
|
|
} |
|
|
} catch (error) { |
|
|
console.error('Error parsing cached translations:', error); |
|
|
} |
|
|
} |
|
|
return null; |
|
|
} |
|
|
|
|
|
|
|
|
function getCachedLanguages() { |
|
|
const cachedLanguages = []; |
|
|
for (let i = 0; i < localStorage.length; i++) { |
|
|
const key = localStorage.key(i); |
|
|
if (key && key.startsWith(CACHE_PREFIX)) { |
|
|
const language = key.replace(CACHE_PREFIX, ''); |
|
|
cachedLanguages.push(language); |
|
|
} |
|
|
} |
|
|
return cachedLanguages; |
|
|
} |
|
|
|
|
|
|
|
|
function clearAllTranslationCache() { |
|
|
const cachedLanguages = getCachedLanguages(); |
|
|
|
|
|
if (cachedLanguages.length === 0) { |
|
|
alert('No cached translations to clear.'); |
|
|
return; |
|
|
} |
|
|
|
|
|
const languageList = cachedLanguages.map(lang => languageNames[lang] || lang).join(', '); |
|
|
const confirmMessage = `Are you sure you want to clear all cached translations?\n\nCached languages: ${languageList}\n\nThis will require re-downloading translations when switching languages.`; |
|
|
|
|
|
if (confirm(confirmMessage)) { |
|
|
|
|
|
cachedLanguages.forEach(language => { |
|
|
const cacheKey = CACHE_PREFIX + language; |
|
|
localStorage.removeItem(cacheKey); |
|
|
}); |
|
|
|
|
|
|
|
|
updateCacheStatus(currentLanguage); |
|
|
updateCacheStatusDisplay(); |
|
|
|
|
|
alert(`Cache cleared successfully!\n\n${cachedLanguages.length} language(s) removed from cache.`); |
|
|
|
|
|
|
|
|
if (currentLanguage !== 'en' && cachedLanguages.includes(currentLanguage)) { |
|
|
if (confirm('Would you like to reload the current language translations?')) { |
|
|
applyLanguage(currentLanguage); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function updateCacheStatus(language) { |
|
|
const cacheStatus = document.getElementById('cacheStatus'); |
|
|
const isCached = isLanguageCached(language); |
|
|
|
|
|
if (language === 'en') { |
|
|
cacheStatus.classList.remove('cached', 'translating'); |
|
|
return; |
|
|
} |
|
|
|
|
|
if (isCached) { |
|
|
cacheStatus.textContent = '💾 Translations cached - instant loading!'; |
|
|
cacheStatus.classList.add('cached'); |
|
|
cacheStatus.classList.remove('translating'); |
|
|
} else { |
|
|
cacheStatus.textContent = '🔄 First time translation - will be cached for future use'; |
|
|
cacheStatus.classList.add('translating'); |
|
|
cacheStatus.classList.remove('cached'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function updateCacheStatusDisplay() { |
|
|
const cacheStatusDisplay = document.getElementById('cacheStatusDisplay'); |
|
|
const clearCacheBtn = document.getElementById('clearCacheBtn'); |
|
|
const cachedLanguages = getCachedLanguages(); |
|
|
|
|
|
if (cachedLanguages.length === 0) { |
|
|
cacheStatusDisplay.textContent = '📭 No cached translations'; |
|
|
cacheStatusDisplay.className = 'cache-status-display no-cache'; |
|
|
clearCacheBtn.disabled = true; |
|
|
} else { |
|
|
const languageList = cachedLanguages.map(lang => languageNames[lang] || lang).join(', '); |
|
|
cacheStatusDisplay.textContent = `💾 ${cachedLanguages.length} language(s) cached: ${languageList}`; |
|
|
cacheStatusDisplay.className = 'cache-status-display'; |
|
|
clearCacheBtn.disabled = false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function initializeApp() { |
|
|
|
|
|
const savedLanguage = localStorage.getItem('educational_analyzer_language') || 'en'; |
|
|
const savedApiKey = localStorage.getItem('educational_analyzer_api_key') || ''; |
|
|
|
|
|
currentLanguage = savedLanguage; |
|
|
currentApiKey = savedApiKey; |
|
|
|
|
|
|
|
|
document.getElementById('languageSelect').value = currentLanguage; |
|
|
document.getElementById('apiKeyLanding').value = currentApiKey; |
|
|
|
|
|
|
|
|
applyDirection(currentLanguage); |
|
|
|
|
|
|
|
|
updateCacheStatus(currentLanguage); |
|
|
updateCacheStatusDisplay(); |
|
|
|
|
|
|
|
|
if (currentApiKey && currentLanguage) { |
|
|
showMainApp(); |
|
|
} else { |
|
|
showLanguageLanding(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function applyDirection(language) { |
|
|
currentLanguage = language; |
|
|
|
|
|
|
|
|
document.documentElement.lang = language; |
|
|
document.documentElement.dir = rtlLanguages.includes(language) ? 'rtl' : 'ltr'; |
|
|
|
|
|
|
|
|
localStorage.setItem('educational_analyzer_language', language); |
|
|
|
|
|
|
|
|
updateCacheStatus(language); |
|
|
} |
|
|
|
|
|
|
|
|
async function translateText(text, targetLanguage) { |
|
|
if (!currentApiKey) { |
|
|
throw new Error('API key is required for translation'); |
|
|
} |
|
|
|
|
|
const languageName = languageNames[targetLanguage] || 'English'; |
|
|
|
|
|
const prompt = `Translate the following text to ${languageName}. Provide ONLY the exact translation without any explanations, additional information, or formatting: |
|
|
|
|
|
"${text}"`; |
|
|
|
|
|
const payload = { |
|
|
model: "gpt-4o-mini", |
|
|
messages: [{ role: "user", content: prompt }], |
|
|
max_tokens: 500, |
|
|
temperature: 0.1 |
|
|
}; |
|
|
|
|
|
const response = await fetch("https://api.openai.com/v1/chat/completions", { |
|
|
method: "POST", |
|
|
headers: { |
|
|
"Content-Type": "application/json", |
|
|
"Authorization": `Bearer ${currentApiKey}` |
|
|
}, |
|
|
body: JSON.stringify(payload) |
|
|
}); |
|
|
|
|
|
if (!response.ok) { |
|
|
const errorData = await response.json(); |
|
|
throw new Error(errorData.error?.message || "Translation API request failed"); |
|
|
} |
|
|
|
|
|
const data = await response.json(); |
|
|
return data.choices[0].message.content.trim(); |
|
|
} |
|
|
|
|
|
|
|
|
function applyCachedTranslations(translations) { |
|
|
|
|
|
Object.keys(translations.texts).forEach(originalText => { |
|
|
const translation = translations.texts[originalText]; |
|
|
const elements = document.querySelectorAll(`[data-translate]`); |
|
|
|
|
|
elements.forEach(element => { |
|
|
const originalElementText = element.getAttribute('data-original-text') || element.textContent; |
|
|
if (originalElementText === originalText) { |
|
|
element.textContent = translation; |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
Object.keys(translations.placeholders).forEach(originalPlaceholder => { |
|
|
const translation = translations.placeholders[originalPlaceholder]; |
|
|
const elements = document.querySelectorAll(`[data-translate-placeholder]`); |
|
|
|
|
|
elements.forEach(element => { |
|
|
const originalElementPlaceholder = element.getAttribute('data-original-placeholder') || element.placeholder; |
|
|
if (originalElementPlaceholder === originalPlaceholder) { |
|
|
element.placeholder = translation; |
|
|
} |
|
|
}); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
async function translateInterface(targetLanguage) { |
|
|
if (targetLanguage === 'en') { |
|
|
|
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
const cachedTranslations = loadTranslationsFromCache(targetLanguage); |
|
|
if (cachedTranslations) { |
|
|
|
|
|
console.log('Using cached translations for', targetLanguage); |
|
|
applyCachedTranslations(cachedTranslations); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
showTranslationOverlay(); |
|
|
|
|
|
try { |
|
|
|
|
|
const elements = document.querySelectorAll('[data-translate]'); |
|
|
const placeholderElements = document.querySelectorAll('[data-translate-placeholder]'); |
|
|
|
|
|
|
|
|
const textsToTranslate = []; |
|
|
const placeholdersToTranslate = []; |
|
|
const elementMap = new Map(); |
|
|
|
|
|
elements.forEach(element => { |
|
|
const originalText = element.getAttribute('data-original-text') || element.textContent; |
|
|
if (!element.getAttribute('data-original-text')) { |
|
|
element.setAttribute('data-original-text', originalText); |
|
|
} |
|
|
textsToTranslate.push(originalText); |
|
|
elementMap.set(originalText, element); |
|
|
}); |
|
|
|
|
|
placeholderElements.forEach(element => { |
|
|
const originalPlaceholder = element.getAttribute('data-original-placeholder') || element.placeholder; |
|
|
if (!element.getAttribute('data-original-placeholder')) { |
|
|
element.setAttribute('data-original-placeholder', originalPlaceholder); |
|
|
} |
|
|
placeholdersToTranslate.push(originalPlaceholder); |
|
|
elementMap.set(originalPlaceholder, element); |
|
|
}); |
|
|
|
|
|
|
|
|
const translationsCache = { |
|
|
texts: {}, |
|
|
placeholders: {} |
|
|
}; |
|
|
|
|
|
|
|
|
const batchSize = 10; |
|
|
const allTexts = [...textsToTranslate, ...placeholdersToTranslate]; |
|
|
|
|
|
for (let i = 0; i < allTexts.length; i += batchSize) { |
|
|
const batch = allTexts.slice(i, i + batchSize); |
|
|
|
|
|
|
|
|
updateTranslationProgress(i, allTexts.length); |
|
|
|
|
|
|
|
|
const translations = await Promise.all( |
|
|
batch.map(text => translateText(text, targetLanguage)) |
|
|
); |
|
|
|
|
|
|
|
|
batch.forEach((originalText, index) => { |
|
|
const element = elementMap.get(originalText); |
|
|
const translation = translations[index]; |
|
|
|
|
|
if (element.hasAttribute('data-translate')) { |
|
|
element.textContent = translation; |
|
|
translationsCache.texts[originalText] = translation; |
|
|
} else if (element.hasAttribute('data-translate-placeholder')) { |
|
|
element.placeholder = translation; |
|
|
translationsCache.placeholders[originalText] = translation; |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
saveTranslationsToCache(targetLanguage, translationsCache); |
|
|
|
|
|
|
|
|
updateCacheStatus(targetLanguage); |
|
|
updateCacheStatusDisplay(); |
|
|
|
|
|
} catch (error) { |
|
|
console.error('Translation error:', error); |
|
|
showError('Translation failed: ' + error.message); |
|
|
} finally { |
|
|
hideTranslationOverlay(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function showTranslationOverlay() { |
|
|
document.getElementById('translationOverlay').style.display = 'flex'; |
|
|
} |
|
|
|
|
|
|
|
|
function hideTranslationOverlay() { |
|
|
document.getElementById('translationOverlay').style.display = 'none'; |
|
|
} |
|
|
|
|
|
|
|
|
function updateTranslationProgress(current, total) { |
|
|
const percentage = Math.round((current / total) * 100); |
|
|
document.getElementById('translationMessage').textContent = |
|
|
`Translating interface... ${percentage}% complete (will be cached for future use)`; |
|
|
} |
|
|
|
|
|
|
|
|
async function applyLanguage(language) { |
|
|
applyDirection(language); |
|
|
|
|
|
if (language !== 'en') { |
|
|
await translateInterface(language); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function showLanguageLanding() { |
|
|
document.getElementById('languageLanding').style.display = 'block'; |
|
|
document.getElementById('mainApp').style.display = 'none'; |
|
|
} |
|
|
|
|
|
|
|
|
function showMainApp() { |
|
|
document.getElementById('languageLanding').style.display = 'none'; |
|
|
document.getElementById('mainApp').style.display = 'block'; |
|
|
|
|
|
|
|
|
document.getElementById('apiKey').value = currentApiKey; |
|
|
} |
|
|
|
|
|
|
|
|
document.getElementById('startBtn').addEventListener('click', async function() { |
|
|
const selectedLanguage = document.getElementById('languageSelect').value; |
|
|
const apiKey = document.getElementById('apiKeyLanding').value.trim(); |
|
|
|
|
|
if (!apiKey) { |
|
|
alert('Please enter your OpenAI API key'); |
|
|
return; |
|
|
} |
|
|
|
|
|
currentLanguage = selectedLanguage; |
|
|
currentApiKey = apiKey; |
|
|
|
|
|
|
|
|
localStorage.setItem('educational_analyzer_api_key', apiKey); |
|
|
|
|
|
|
|
|
await applyLanguage(selectedLanguage); |
|
|
|
|
|
|
|
|
showMainApp(); |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('languageSelect').addEventListener('change', async function() { |
|
|
const selectedLanguage = this.value; |
|
|
updateCacheStatus(selectedLanguage); |
|
|
|
|
|
if (currentApiKey) { |
|
|
await applyLanguage(selectedLanguage); |
|
|
} else { |
|
|
applyDirection(selectedLanguage); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
document.getElementById('clearCacheBtn').addEventListener('click', clearAllTranslationCache); |
|
|
|
|
|
|
|
|
document.getElementById('apiKeyLanding').addEventListener('input', function() { |
|
|
currentApiKey = this.value; |
|
|
localStorage.setItem('educational_analyzer_api_key', this.value); |
|
|
document.getElementById('apiKey').value = this.value; |
|
|
}); |
|
|
|
|
|
document.getElementById('apiKey').addEventListener('input', function() { |
|
|
currentApiKey = this.value; |
|
|
localStorage.setItem('educational_analyzer_api_key', this.value); |
|
|
document.getElementById('apiKeyLanding').value = this.value; |
|
|
}); |
|
|
|
|
|
|
|
|
const AppConfig = { |
|
|
API_BASE_URL: 'https://api.openai.com/v1/chat/completions', |
|
|
MODEL: 'gpt-4o-mini', |
|
|
MAX_TOKENS: 2000, |
|
|
TEMPERATURE: 0.7 |
|
|
}; |
|
|
|
|
|
|
|
|
const AppState = { |
|
|
apiKey: '', |
|
|
isAnalyzing: false, |
|
|
currentActivity: '', |
|
|
|
|
|
setApiKey(key) { |
|
|
this.apiKey = key; |
|
|
this.saveToLocalStorage(); |
|
|
}, |
|
|
|
|
|
setAnalyzing(analyzing) { |
|
|
this.isAnalyzing = analyzing; |
|
|
UIController.updateAnalyzingState(analyzing); |
|
|
}, |
|
|
|
|
|
saveToLocalStorage() { |
|
|
try { |
|
|
localStorage.setItem('educational_analyzer_api_key', this.apiKey); |
|
|
} catch (e) { |
|
|
console.warn('Could not save to localStorage:', e); |
|
|
} |
|
|
}, |
|
|
|
|
|
loadFromLocalStorage() { |
|
|
try { |
|
|
this.apiKey = localStorage.getItem('educational_analyzer_api_key') || ''; |
|
|
} catch (e) { |
|
|
console.warn('Could not load from localStorage:', e); |
|
|
} |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
const agentPrompts = { |
|
|
agent1: function(activity) { |
|
|
const languageName = languageNames[currentLanguage] || 'English'; |
|
|
return ` |
|
|
You are Agent 1, an educational technology expert. Your task is to conduct a DEEP, STRUCTURED ANALYSIS of the teaching activity described below using the PICRAT model (PIC + RAT) and Bloom's Taxonomy, with a strong emphasis on how effectively the activity fosters critical thinking. |
|
|
|
|
|
IMPORTANT: Write your entire response in ${languageName} language. |
|
|
|
|
|
Activity Description: |
|
|
${activity} |
|
|
|
|
|
OUTPUT FORMAT (use these exact headings in your final response): |
|
|
|
|
|
PICRAT Dimensions |
|
|
a. Passive (P) |
|
|
Provide a detailed analysis of the passive element, including examples and implications for student engagement. |
|
|
b. Interactive (I) |
|
|
Provide a detailed analysis of the interactive element, including examples and implications for student engagement. |
|
|
c. Creative (C) |
|
|
Provide a detailed analysis of the creative element, including examples and implications for student engagement. |
|
|
d. Replacement (R) |
|
|
Examine the replacement element thoroughly, highlighting how technology is used to substitute traditional methods. |
|
|
e. Amplification (A) |
|
|
Examine the amplification element thoroughly, highlighting how technology is used to enhance traditional methods. |
|
|
f. Transformation (T) |
|
|
Examine the transformation element thoroughly, highlighting how technology is used to transform learning experiences. |
|
|
|
|
|
Bloom's Taxonomy |
|
|
Identify and explain the cognitive level(s) addressed (Remembering, Understanding, Applying, Analyzing, Evaluating, Creating) with specific references to the activity. |
|
|
|
|
|
Relationship Between PICRAT & Bloom's |
|
|
Analyze how the chosen PICRAT categories align with or support the identified Bloom's level(s), illustrating their interconnectivity. |
|
|
|
|
|
Critical Thinking |
|
|
Assess to what extent this activity fosters critical thinking, providing examples of how students are encouraged to engage in higher-order thinking. |
|
|
|
|
|
Overall Comments/Recommendations |
|
|
Summarize key strengths of the activity and suggest actionable improvements to enhance its effectiveness. |
|
|
`.trim(); |
|
|
}, |
|
|
|
|
|
agent2: function(agent1Result) { |
|
|
const languageName = languageNames[currentLanguage] || 'English'; |
|
|
return ` |
|
|
You are Agent 2, a scenario developer. Based on the analysis from Agent 1, craft practical scenarios or applications that illustrate how the teaching activity can be improved or enhanced. Use Agent 1's insights verbatim as your foundation. |
|
|
|
|
|
IMPORTANT: Write your entire response in ${languageName} language. |
|
|
|
|
|
Agent 1's Analysis: |
|
|
${agent1Result} |
|
|
|
|
|
Instructions: |
|
|
1. Provide realistic classroom or online teaching scenarios. |
|
|
2. Address technology integration, engagement strategies, and cognitive depth. |
|
|
3. Include any potential pitfalls or variations for different subjects or grade levels. |
|
|
|
|
|
Output only your final scenarios. |
|
|
`.trim(); |
|
|
}, |
|
|
|
|
|
agent3: function(agent1Result, agent2Result) { |
|
|
const languageName = languageNames[currentLanguage] || 'English'; |
|
|
return ` |
|
|
You are Agent 3, a meta-reviewer. You have access to the outputs from Agent 1 and Agent 2. Provide overarching feedback and a high-level overview that ties both analyses and scenarios together. |
|
|
|
|
|
IMPORTANT: Write your entire response in ${languageName} language. |
|
|
|
|
|
Agent 1's Analysis: |
|
|
${agent1Result} |
|
|
|
|
|
Agent 2's Scenarios: |
|
|
${agent2Result} |
|
|
|
|
|
Instructions: |
|
|
1. Summarize the key insights from Agents 1 and 2. |
|
|
2. Discuss overall alignment with best practices in technology integration and higher-order thinking. |
|
|
3. Suggest any final recommendations or overarching considerations (e.g., accessibility, long-term professional growth). |
|
|
|
|
|
Output only your final feedback and overview. |
|
|
`.trim(); |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
const APIService = { |
|
|
async callGPT(prompt, apiKey) { |
|
|
const payload = { |
|
|
model: AppConfig.MODEL, |
|
|
messages: [{ role: 'user', content: prompt }], |
|
|
max_tokens: AppConfig.MAX_TOKENS, |
|
|
temperature: AppConfig.TEMPERATURE |
|
|
}; |
|
|
|
|
|
const response = await fetch(AppConfig.API_BASE_URL, { |
|
|
method: 'POST', |
|
|
headers: { |
|
|
'Content-Type': 'application/json', |
|
|
'Authorization': `Bearer ${apiKey}` |
|
|
}, |
|
|
body: JSON.stringify(payload) |
|
|
}); |
|
|
|
|
|
if (!response.ok) { |
|
|
const errorData = await response.json(); |
|
|
throw new Error(errorData.error?.message || `HTTP ${response.status}: ${response.statusText}`); |
|
|
} |
|
|
|
|
|
const data = await response.json(); |
|
|
return data.choices?.[0]?.message?.content || 'No output received.'; |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
const SimulationService = { |
|
|
getSimulatedResponse(agentName, placeholders) { |
|
|
switch (agentName) { |
|
|
case "agent1": |
|
|
return ` |
|
|
1. PICRAT Dimensions |
|
|
a. PIC Classification (Interactive) |
|
|
b. RAT Classification (Amplification) |
|
|
|
|
|
2. Bloom's Taxonomy |
|
|
- Applying, Analyzing |
|
|
|
|
|
3. Relationship Between PICRAT & Bloom's |
|
|
- Interactive use of tech supports mid-level Bloom's tasks |
|
|
|
|
|
4. Critical Thinking |
|
|
- Moderate level; students engage with real-world data but do not create or evaluate complex outputs |
|
|
|
|
|
5. Overall Comments/Recommendations |
|
|
- Strength: Encourages active student participation |
|
|
- Recommendation: Incorporate a peer-evaluation component to push into higher Bloom's levels |
|
|
`.trim(); |
|
|
case "agent2": |
|
|
return ` |
|
|
[SIMULATED Agent 2 Scenarios] |
|
|
1) Students collaborate in groups to apply the analyzed data in a new project, amplifying the original activity. |
|
|
2) Introduce a mini-debate or panel discussion, using technology for real-time data visualization and deeper analysis. |
|
|
`.trim(); |
|
|
case "agent3": |
|
|
return ` |
|
|
[SIMULATED Agent 3 Feedback & Overview] |
|
|
Overall alignment with best practices: Encourages interactive use of tech and mid-level Bloom's. |
|
|
Recommendation: Integrate a reflection stage to foster higher-order thinking and transform the activity into a more creative endeavor. |
|
|
`.trim(); |
|
|
default: |
|
|
return "[No simulation available]"; |
|
|
} |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
const UIController = { |
|
|
elements: {}, |
|
|
|
|
|
init() { |
|
|
this.elements = { |
|
|
apiKeyInput: document.getElementById('apiKey'), |
|
|
analysisForm: document.getElementById('analysisForm'), |
|
|
activityDescription: document.getElementById('activityDescription'), |
|
|
analyzeButton: document.querySelector('.analyze-button'), |
|
|
loadingSpinner: document.getElementById('loadingSpinner'), |
|
|
buttonText: document.querySelector('.button-text'), |
|
|
agent1Card: document.getElementById('agent1Card'), |
|
|
agent2Card: document.getElementById('agent2Card'), |
|
|
agent3Card: document.getElementById('agent3Card'), |
|
|
analysisOutput1: document.getElementById('analysisOutput1'), |
|
|
analysisOutput2: document.getElementById('analysisOutput2'), |
|
|
analysisOutput3: document.getElementById('analysisOutput3') |
|
|
}; |
|
|
|
|
|
this.setupEventListeners(); |
|
|
}, |
|
|
|
|
|
setupEventListeners() { |
|
|
this.elements.apiKeyInput.addEventListener('input', (e) => { |
|
|
AppState.setApiKey(e.target.value.trim()); |
|
|
}); |
|
|
|
|
|
this.elements.analysisForm.addEventListener('submit', (e) => { |
|
|
e.preventDefault(); |
|
|
App.analyzeActivity(); |
|
|
}); |
|
|
}, |
|
|
|
|
|
updateAnalyzingState(isAnalyzing) { |
|
|
if (isAnalyzing) { |
|
|
this.elements.analyzeButton.disabled = true; |
|
|
this.elements.buttonText.style.opacity = '0'; |
|
|
this.elements.loadingSpinner.style.display = 'block'; |
|
|
} else { |
|
|
this.elements.analyzeButton.disabled = false; |
|
|
this.elements.buttonText.style.opacity = '1'; |
|
|
this.elements.loadingSpinner.style.display = 'none'; |
|
|
} |
|
|
}, |
|
|
|
|
|
setAgentLoading(agentNumber, isLoading) { |
|
|
const card = this.elements[`agent${agentNumber}Card`]; |
|
|
if (isLoading) { |
|
|
card.classList.add('loading'); |
|
|
this.elements[`analysisOutput${agentNumber}`].textContent = ''; |
|
|
} else { |
|
|
card.classList.remove('loading'); |
|
|
} |
|
|
}, |
|
|
|
|
|
setAgentContent(agentNumber, content) { |
|
|
this.elements[`analysisOutput${agentNumber}`].textContent = content; |
|
|
}, |
|
|
|
|
|
clearAllOutputs() { |
|
|
for (let i = 1; i <= 3; i++) { |
|
|
this.elements[`analysisOutput${i}`].textContent = ''; |
|
|
this.elements[`agent${i}Card`].classList.remove('loading'); |
|
|
} |
|
|
}, |
|
|
|
|
|
showError(message) { |
|
|
const errorHtml = ` |
|
|
<div class="error-message"> |
|
|
<span class="error-icon">⚠️</span> |
|
|
<span class="error-text">${message}</span> |
|
|
<button class="error-dismiss" onclick="this.parentElement.remove()">×</button> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
const inputSection = document.querySelector('.input-section'); |
|
|
if (inputSection) { |
|
|
inputSection.insertAdjacentHTML('afterbegin', errorHtml); |
|
|
} |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
const ErrorHandler = { |
|
|
handleError(error, agentNumber = null) { |
|
|
console.error('Application error:', error); |
|
|
|
|
|
let userMessage = 'An unexpected error occurred. Please try again.'; |
|
|
|
|
|
if (error.message.includes('API key')) { |
|
|
userMessage = 'Invalid API key. Please check your OpenAI API key and try again.'; |
|
|
} else if (error.message.includes('network') || error.message.includes('fetch')) { |
|
|
userMessage = 'Network error. Please check your internet connection and try again.'; |
|
|
} else if (error.message.includes('rate limit')) { |
|
|
userMessage = 'Rate limit exceeded. Please wait a moment before trying again.'; |
|
|
} else if (error.message.includes('quota')) { |
|
|
userMessage = 'API quota exceeded. Please check your OpenAI account usage.'; |
|
|
} |
|
|
|
|
|
if (agentNumber) { |
|
|
UIController.setAgentContent(agentNumber, `Error: ${userMessage}`); |
|
|
} else { |
|
|
UIController.showError(userMessage); |
|
|
} |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
const InputValidator = { |
|
|
validateActivity(activity) { |
|
|
if (!activity || activity.trim().length < 10) { |
|
|
throw new Error('Please provide a more detailed description of your teaching activity (at least 10 characters).'); |
|
|
} |
|
|
return true; |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
const App = { |
|
|
init() { |
|
|
AppState.loadFromLocalStorage(); |
|
|
UIController.init(); |
|
|
|
|
|
|
|
|
if (AppState.apiKey) { |
|
|
UIController.elements.apiKeyInput.value = AppState.apiKey; |
|
|
} |
|
|
|
|
|
console.log('Educational Activity Intelligence Analyzer initialized'); |
|
|
}, |
|
|
|
|
|
async analyzeActivity() { |
|
|
if (AppState.isAnalyzing) return; |
|
|
|
|
|
try { |
|
|
const activity = UIController.elements.activityDescription.value.trim(); |
|
|
|
|
|
|
|
|
InputValidator.validateActivity(activity); |
|
|
|
|
|
|
|
|
AppState.currentActivity = activity; |
|
|
AppState.setAnalyzing(true); |
|
|
|
|
|
|
|
|
UIController.clearAllOutputs(); |
|
|
|
|
|
|
|
|
const apiKey = UIController.elements.apiKeyInput.value.trim(); |
|
|
|
|
|
|
|
|
if (apiKey) { |
|
|
AppState.setApiKey(apiKey); |
|
|
} |
|
|
|
|
|
|
|
|
await this.runAgent1(activity, apiKey); |
|
|
await this.runAgent2(apiKey); |
|
|
await this.runAgent3(apiKey); |
|
|
|
|
|
} catch (error) { |
|
|
ErrorHandler.handleError(error); |
|
|
} finally { |
|
|
AppState.setAnalyzing(false); |
|
|
} |
|
|
}, |
|
|
|
|
|
async runAgent1(activity, apiKey) { |
|
|
try { |
|
|
UIController.setAgentLoading(1, true); |
|
|
|
|
|
const prompt = agentPrompts.agent1(activity); |
|
|
|
|
|
let result; |
|
|
if (!apiKey) { |
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 1500)); |
|
|
result = SimulationService.getSimulatedResponse('agent1', { ACTIVITY: activity }); |
|
|
} else { |
|
|
result = await APIService.callGPT(prompt, apiKey); |
|
|
} |
|
|
|
|
|
AppState.agent1Result = result; |
|
|
UIController.setAgentContent(1, result); |
|
|
|
|
|
} catch (error) { |
|
|
ErrorHandler.handleError(error, 1); |
|
|
} finally { |
|
|
UIController.setAgentLoading(1, false); |
|
|
} |
|
|
}, |
|
|
|
|
|
async runAgent2(apiKey) { |
|
|
try { |
|
|
UIController.setAgentLoading(2, true); |
|
|
|
|
|
const prompt = agentPrompts.agent2(AppState.agent1Result || ''); |
|
|
|
|
|
let result; |
|
|
if (!apiKey) { |
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 1200)); |
|
|
result = SimulationService.getSimulatedResponse('agent2', { AGENT1_RESULT: AppState.agent1Result }); |
|
|
} else { |
|
|
result = await APIService.callGPT(prompt, apiKey); |
|
|
} |
|
|
|
|
|
AppState.agent2Result = result; |
|
|
UIController.setAgentContent(2, result); |
|
|
|
|
|
} catch (error) { |
|
|
ErrorHandler.handleError(error, 2); |
|
|
} finally { |
|
|
UIController.setAgentLoading(2, false); |
|
|
} |
|
|
}, |
|
|
|
|
|
async runAgent3(apiKey) { |
|
|
try { |
|
|
UIController.setAgentLoading(3, true); |
|
|
|
|
|
const prompt = agentPrompts.agent3(AppState.agent1Result || '', AppState.agent2Result || ''); |
|
|
|
|
|
let result; |
|
|
if (!apiKey) { |
|
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 1000)); |
|
|
result = SimulationService.getSimulatedResponse('agent3', { |
|
|
AGENT1_RESULT: AppState.agent1Result, |
|
|
AGENT2_RESULT: AppState.agent2Result |
|
|
}); |
|
|
} else { |
|
|
result = await APIService.callGPT(prompt, apiKey); |
|
|
} |
|
|
|
|
|
UIController.setAgentContent(3, result); |
|
|
|
|
|
} catch (error) { |
|
|
ErrorHandler.handleError(error, 3); |
|
|
} finally { |
|
|
UIController.setAgentLoading(3, false); |
|
|
} |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
initializeApp(); |
|
|
App.init(); |
|
|
}); |
|
|
</script> |
|
|
</body> |
|
|
</html> |
|
|
|
|
|
|