""" BreakPilot Studio - Dashboard/Startansicht Modul Funktionen: - Startansicht mit Kacheln zu allen Modulen - Schnellzugriff auf die wichtigsten Funktionen - Uebersicht ueber aktuelle Aktivitaeten """ class DashboardModule: """Modul fuer die Startansicht/Dashboard.""" @staticmethod def get_css() -> str: """CSS fuer das Dashboard-Modul.""" return """ /* ============================================= DASHBOARD MODULE - Startansicht ============================================= */ /* Panel Layout */ .panel-dashboard { display: flex; flex-direction: column; height: 100%; background: var(--bp-bg); overflow-y: auto; } /* Dashboard Header */ .dashboard-header { padding: 32px 40px 24px; background: var(--bp-surface); border-bottom: 1px solid var(--bp-border); } .dashboard-welcome { margin-bottom: 8px; } .dashboard-welcome h1 { font-size: 28px; font-weight: 700; color: var(--bp-text); margin: 0; } .dashboard-welcome p { font-size: 14px; color: var(--bp-text-muted); margin-top: 8px; } /* Dashboard Content */ .dashboard-content { padding: 32px 40px; flex: 1; } /* Section Titles */ .dashboard-section-title { font-size: 14px; font-weight: 600; color: var(--bp-text-muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 20px; } /* Module Cards Grid */ .dashboard-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 24px; margin-bottom: 48px; } /* Module Card */ .dashboard-card { background: var(--bp-surface); border: 1px solid var(--bp-border); border-radius: 16px; padding: 24px; cursor: pointer; transition: all 0.3s ease; position: relative; overflow: hidden; } .dashboard-card:hover { transform: translateY(-4px); box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15); border-color: var(--bp-primary); } .dashboard-card::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 4px; background: var(--bp-primary); opacity: 0; transition: opacity 0.3s; } .dashboard-card:hover::before { opacity: 1; } .dashboard-card-icon { width: 56px; height: 56px; border-radius: 14px; display: flex; align-items: center; justify-content: center; font-size: 28px; margin-bottom: 16px; background: var(--bp-primary-soft); } .dashboard-card-title { font-size: 18px; font-weight: 600; color: var(--bp-text); margin-bottom: 8px; } .dashboard-card-description { font-size: 13px; color: var(--bp-text-muted); line-height: 1.5; margin-bottom: 16px; } .dashboard-card-footer { display: flex; justify-content: space-between; align-items: center; padding-top: 16px; border-top: 1px solid var(--bp-border-subtle); } .dashboard-card-action { font-size: 13px; font-weight: 500; color: var(--bp-primary); display: flex; align-items: center; gap: 6px; } .dashboard-card-action::after { content: '→'; transition: transform 0.2s; } .dashboard-card:hover .dashboard-card-action::after { transform: translateX(4px); } .dashboard-card-badge { font-size: 11px; padding: 4px 10px; border-radius: 12px; background: var(--bp-accent-soft); color: var(--bp-accent); font-weight: 500; } .dashboard-card-badge.new { background: rgba(59, 130, 246, 0.1); color: #3b82f6; } .dashboard-card-badge.beta { background: rgba(245, 158, 11, 0.1); color: #f59e0b; } /* Card Color Variants */ .dashboard-card.worksheets .dashboard-card-icon { background: rgba(59, 130, 246, 0.1); color: #3b82f6; } .dashboard-card.correction .dashboard-card-icon { background: rgba(16, 185, 129, 0.1); color: #10b981; } .dashboard-card.jitsi .dashboard-card-icon { background: rgba(139, 92, 246, 0.1); color: #8b5cf6; } .dashboard-card.letters .dashboard-card-icon { background: rgba(236, 72, 153, 0.1); color: #ec4899; } .dashboard-card.messenger .dashboard-card-icon { background: rgba(20, 184, 166, 0.1); color: #14b8a6; } .dashboard-card.klausur-korrektur .dashboard-card-icon { background: rgba(168, 85, 247, 0.1); color: #a855f7; } /* Quick Stats */ .dashboard-stats { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 16px; margin-bottom: 48px; } .dashboard-stat { background: var(--bp-surface); border: 1px solid var(--bp-border); border-radius: 12px; padding: 20px; display: flex; align-items: center; gap: 16px; } .dashboard-stat-icon { width: 48px; height: 48px; border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 24px; background: var(--bp-surface-elevated); } .dashboard-stat-content { flex: 1; } .dashboard-stat-value { font-size: 24px; font-weight: 700; color: var(--bp-text); } .dashboard-stat-label { font-size: 12px; color: var(--bp-text-muted); } /* Recent Activity */ .dashboard-activity { background: var(--bp-surface); border: 1px solid var(--bp-border); border-radius: 16px; padding: 24px; } .dashboard-activity-title { font-size: 16px; font-weight: 600; margin-bottom: 20px; color: var(--bp-text); } .dashboard-activity-list { list-style: none; padding: 0; margin: 0; } .dashboard-activity-item { display: flex; align-items: center; gap: 16px; padding: 14px 0; border-bottom: 1px solid var(--bp-border-subtle); } .dashboard-activity-item:last-child { border-bottom: none; } .dashboard-activity-icon { width: 36px; height: 36px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 16px; background: var(--bp-surface-elevated); } .dashboard-activity-content { flex: 1; } .dashboard-activity-text { font-size: 14px; color: var(--bp-text); } .dashboard-activity-time { font-size: 12px; color: var(--bp-text-muted); } /* Empty State */ .dashboard-empty { text-align: center; padding: 48px; color: var(--bp-text-muted); } .dashboard-empty-icon { font-size: 48px; margin-bottom: 16px; opacity: 0.5; } /* ============================================= AI PROMPT INPUT ============================================= */ .dashboard-ai-prompt { background: var(--bp-surface); border: 1px solid var(--bp-border); border-radius: 16px; padding: 20px; margin-bottom: 32px; } .dashboard-ai-prompt-header { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; } .dashboard-ai-prompt-icon { width: 40px; height: 40px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 20px; background: linear-gradient(135deg, #6C1B1B, #991b1b); color: white; } .dashboard-ai-prompt-title { font-size: 16px; font-weight: 600; color: var(--bp-text); } .dashboard-ai-prompt-subtitle { font-size: 12px; color: var(--bp-text-muted); } .dashboard-ai-prompt-input-container { display: flex; gap: 12px; align-items: flex-end; } .dashboard-ai-prompt-input { flex: 1; min-height: 44px; max-height: 120px; padding: 12px 16px; border-radius: 12px; border: 1px solid var(--bp-border); background: var(--bp-bg); color: var(--bp-text); font-size: 14px; font-family: inherit; resize: none; outline: none; transition: border-color 0.2s; } .dashboard-ai-prompt-input:focus { border-color: var(--bp-primary); } .dashboard-ai-prompt-input::placeholder { color: var(--bp-text-muted); } .dashboard-ai-prompt-send { width: 44px; height: 44px; border-radius: 12px; border: none; background: linear-gradient(135deg, #6C1B1B, #991b1b); color: white; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 18px; transition: all 0.2s; } .dashboard-ai-prompt-send:hover { transform: scale(1.05); box-shadow: 0 4px 12px rgba(108, 27, 27, 0.4); } .dashboard-ai-prompt-send:disabled { opacity: 0.5; cursor: not-allowed; transform: none; } .dashboard-ai-prompt-send.loading { animation: pulse 1s infinite; } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.6; } } /* AI Response */ .dashboard-ai-response { margin-top: 16px; padding: 16px; background: var(--bp-bg); border-radius: 12px; border: 1px solid var(--bp-border-subtle); display: none; } .dashboard-ai-response.active { display: block; } .dashboard-ai-response-header { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; color: var(--bp-text-muted); font-size: 12px; } .dashboard-ai-response-text { color: var(--bp-text); font-size: 14px; line-height: 1.6; white-space: pre-wrap; } .dashboard-ai-response-text code { background: var(--bp-surface-elevated); padding: 2px 6px; border-radius: 4px; font-family: monospace; } .dashboard-ai-response-text pre { background: var(--bp-surface-elevated); padding: 12px; border-radius: 8px; overflow-x: auto; margin: 12px 0; } .dashboard-ai-response-text pre code { background: transparent; padding: 0; } /* Model Selector */ .dashboard-ai-model-selector { display: flex; align-items: center; gap: 8px; margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--bp-border-subtle); } .dashboard-ai-model-label { font-size: 12px; color: var(--bp-text-muted); } .dashboard-ai-model-select { padding: 6px 12px; border-radius: 8px; border: 1px solid var(--bp-border); background: var(--bp-bg); color: var(--bp-text); font-size: 12px; cursor: pointer; } .dashboard-ai-model-select:focus { outline: none; border-color: var(--bp-primary); } """ @staticmethod def get_html() -> str: """HTML fuer das Dashboard-Modul.""" return """

Willkommen bei BreakPilot Studio

Waehle ein Modul, um zu beginnen

🤖
KI-Assistent
Fragen Sie Ihren lokalen Ollama-Assistenten
🤖 Antwort
Modell:
Module
📝
Arbeitsblaetter Studio
Arbeitsblaetter hochladen, neu aufbauen und in Lerneinheiten organisieren. Generiere Mindmaps, Multiple Choice Tests und mehr.
Klausurkorrektur
Klausuren hochladen und automatisch korrigieren lassen. OCR-Erkennung und AI-gestuetzte Bewertung.
🎥
Videokonferenz
Elterngespraeche, Klassenkonferenzen und Schulungen per Video. Integrierte Jitsi-Konferenzen.
✉️
Elternbriefe
Elternbriefe mit rechtssicherer Sprache verfassen. GFK-Analyse und Legal Assistant inklusive.
💬
Messenger
Kontakte verwalten, Nachrichten senden, CSV Import/Export. DSGVO-konforme Elternkommunikation mit Vorlagen.
🎓
Abiturklausuren
Abitur-Klausuren mit 15-Punkte-System. NiBiS-Aufgaben, Erwartungshorizont, Gutachten und Fairness-Analyse.
Letzte Aktivitaeten
  • 📝
    Willkommen bei BreakPilot Studio!
    Gerade eben
""" @staticmethod def get_js() -> str: """JavaScript fuer das Dashboard-Modul.""" return """ // ============================================= // DASHBOARD MODULE - Startansicht // ============================================= let dashboardInitialized = false; function loadDashboardModule() { if (dashboardInitialized) { console.log('Dashboard module already initialized'); return; } console.log('Loading Dashboard Module...'); // Load recent activity from localStorage loadRecentActivity(); dashboardInitialized = true; console.log('Dashboard Module loaded successfully'); } function loadRecentActivity() { const activityList = document.getElementById('dashboard-activity-list'); if (!activityList) return; // Get stored activity const stored = localStorage.getItem('bp-activity'); if (!stored) return; try { const activities = JSON.parse(stored); if (!activities.length) return; activityList.innerHTML = activities.slice(0, 5).map(act => `
  • ${act.icon || '📄'}
    ${act.text}
    ${formatActivityTime(act.time)}
  • `).join(''); } catch (e) { console.error('Error loading activity:', e); } } function addActivity(icon, text) { const stored = localStorage.getItem('bp-activity'); let activities = []; try { activities = stored ? JSON.parse(stored) : []; } catch (e) {} activities.unshift({ icon: icon, text: text, time: Date.now() }); // Keep only last 20 activities = activities.slice(0, 20); localStorage.setItem('bp-activity', JSON.stringify(activities)); } function formatActivityTime(timestamp) { const diff = Date.now() - timestamp; const minutes = Math.floor(diff / 60000); const hours = Math.floor(diff / 3600000); const days = Math.floor(diff / 86400000); if (minutes < 1) return 'Gerade eben'; if (minutes < 60) return `Vor ${minutes} Minute${minutes > 1 ? 'n' : ''}`; if (hours < 24) return `Vor ${hours} Stunde${hours > 1 ? 'n' : ''}`; return `Vor ${days} Tag${days > 1 ? 'en' : ''}`; } // Show Dashboard Panel function showDashboardPanel() { console.log('showDashboardPanel called'); hideAllPanels(); const panel = document.getElementById('panel-dashboard'); if (panel) { panel.style.display = 'flex'; loadDashboardModule(); console.log('Dashboard panel shown'); } else { console.error('panel-dashboard not found'); } } // Open Klausur-Service (External Microservice) function openKlausurService() { // Pass auth token to klausur-service const token = localStorage.getItem('auth_token'); const url = window.location.port === '8000' ? 'http://localhost:8086' : window.location.origin.replace(':8000', ':8086'); // Open in new tab const klausurWindow = window.open(url, '_blank'); // Try to pass token via postMessage after window loads if (klausurWindow && token) { setTimeout(() => { try { klausurWindow.postMessage({ type: 'AUTH_TOKEN', token: token }, url); } catch (e) { console.log('Could not pass token to klausur-service:', e); } }, 1000); } addActivity('🎓', 'Klausur-Service geoeffnet'); } // ============================================= // AI PROMPT FUNCTIONS // ============================================= let aiPromptAbortController = null; function handleAiPromptKeydown(event) { if (event.key === 'Enter' && !event.shiftKey) { event.preventDefault(); sendAiPrompt(); } } function autoResizeTextarea(textarea) { textarea.style.height = 'auto'; textarea.style.height = Math.min(textarea.scrollHeight, 120) + 'px'; } async function sendAiPrompt() { const input = document.getElementById('ai-prompt-input'); const sendBtn = document.getElementById('ai-prompt-send'); const responseDiv = document.getElementById('ai-response'); const responseText = document.getElementById('ai-response-text'); const responseModel = document.getElementById('ai-response-model'); const modelSelect = document.getElementById('ai-model-select'); const prompt = input?.value?.trim(); if (!prompt) return; const model = modelSelect?.value || 'llama3.2:latest'; // Show loading state sendBtn.disabled = true; sendBtn.classList.add('loading'); sendBtn.textContent = '⏳'; responseDiv.classList.add('active'); responseText.textContent = 'Denke nach...'; responseModel.textContent = model; // Cancel previous request if exists if (aiPromptAbortController) { aiPromptAbortController.abort(); } aiPromptAbortController = new AbortController(); try { // Determine Ollama endpoint based on current host let ollamaUrl = 'http://localhost:11434/api/generate'; if (window.location.hostname === 'macmini') { ollamaUrl = 'http://macmini:11434/api/generate'; } const response = await fetch(ollamaUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: model, prompt: prompt, stream: true }), signal: aiPromptAbortController.signal }); if (!response.ok) { throw new Error(`Ollama error: ${response.status}`); } // Stream the response const reader = response.body.getReader(); const decoder = new TextDecoder(); let fullResponse = ''; while (true) { const { done, value } = await reader.read(); if (done) break; const chunk = decoder.decode(value); const lines = chunk.split('\\n').filter(l => l.trim()); for (const line of lines) { try { const data = JSON.parse(line); if (data.response) { fullResponse += data.response; responseText.textContent = fullResponse; } } catch (e) { // Ignore JSON parse errors for partial chunks } } } // Format the response responseText.innerHTML = formatAiResponse(fullResponse); // Log activity addActivity('🤖', 'KI-Anfrage: ' + prompt.substring(0, 50) + (prompt.length > 50 ? '...' : '')); } catch (error) { if (error.name === 'AbortError') { responseText.textContent = 'Anfrage abgebrochen.'; } else { console.error('AI Prompt error:', error); responseText.textContent = '❌ Fehler: ' + error.message + '\\n\\nBitte prüfen Sie, ob Ollama läuft (http://localhost:11434)'; } } finally { sendBtn.disabled = false; sendBtn.classList.remove('loading'); sendBtn.textContent = '➤'; aiPromptAbortController = null; } } function formatAiResponse(text) { // Basic markdown-like formatting let formatted = text // Escape HTML .replace(/&/g, '&') .replace(//g, '>') // Code blocks .replace(/```(\\w+)?\\n([\\s\\S]*?)```/g, '
    $2
    ') // Inline code .replace(/`([^`]+)`/g, '$1') // Bold .replace(/\\*\\*([^*]+)\\*\\*/g, '$1') // Italic .replace(/\\*([^*]+)\\*/g, '$1') // Line breaks .replace(/\\n/g, '
    '); return formatted; } // Load available models from Ollama async function loadOllamaModels() { const modelSelect = document.getElementById('ai-model-select'); if (!modelSelect) return; try { let ollamaUrl = 'http://localhost:11434/api/tags'; if (window.location.hostname === 'macmini') { ollamaUrl = 'http://macmini:11434/api/tags'; } const response = await fetch(ollamaUrl); if (!response.ok) return; const data = await response.json(); if (data.models && data.models.length > 0) { modelSelect.innerHTML = data.models.map(m => `` ).join(''); } } catch (error) { console.log('Could not load Ollama models:', error.message); } } // Initialize AI prompt on dashboard load const originalLoadDashboardModule = loadDashboardModule; loadDashboardModule = function() { originalLoadDashboardModule(); loadOllamaModels(); }; """