Spaces:
Paused
Paused
Download src/views/settings/settings-view.js from Luca448/APP-Backend: direct link, hf CLI and curl.
- Browser
- Download file 31.9 kB
-
https://huggingface.co/spaces/Luca448/APP-Backend/resolve/main/src/views/settings/settings-view.js
- Command line
-
hf download hf://spaces/Luca448/APP-Backend/src/views/settings/settings-view.js
-
curl -L -o settings-view.js https://huggingface.co/spaces/Luca448/APP-Backend/resolve/main/src/views/settings/settings-view.js
31.9 kB
| import { db } from '../../db/database.js'; | |
| import { Clipboard } from '@capacitor/clipboard'; | |
| import { authService } from '../../services/auth-service.js'; | |
| import { AuthView } from '../auth/auth-view.js'; | |
| import { promptQuickSettingsTile } from '../../services/quick-settings-service.js'; | |
| export class SettingsView { | |
| constructor(containerId) { | |
| this.container = document.getElementById(containerId); | |
| } | |
| init() { | |
| this.render(); | |
| this.attachEvents(); | |
| } | |
| render() { | |
| const currentModel = db.getSetting('model') || 'gemini-3.5-flash'; | |
| const currentApiKey = db.getSetting('gemini_api_key') || ''; | |
| const groqApiKey = db.getSetting('groq_api_key') || ''; | |
| const openrouterApiKey = db.getSetting('openrouter_api_key') || ''; | |
| const deepseekApiKey = db.getSetting('deepseek_api_key') || ''; | |
| const huggingfaceApiKey = db.getSetting('huggingface_api_key') || ''; | |
| const ollamaUrl = db.getSetting('ollama_url') || 'http://localhost:11434'; | |
| const startBehavior = db.getSetting('start_behavior') || 'new'; | |
| const bgStyle = db.getSetting('background_style') || 'standard'; | |
| this.container.innerHTML = ` | |
| <div style="padding: 24px; padding-bottom: 100px; display: flex; flex-direction: column; gap: 32px; max-width: 600px; margin: 0 auto;"> | |
| <div> | |
| <h2 style="margin-bottom: 8px; font-size: 28px;">Einstellungen</h2> | |
| <p style="color: var(--text-secondary);">Passe die SchoolMind KI an deine Bedürfnisse an.</p> | |
| </div> | |
| <!-- Account & Sync --> | |
| <div class="liquid-glass-subtle" style="padding: 20px; border-radius: 20px; display: flex; flex-direction: column; gap: 12px;"> | |
| <h3 style="margin-bottom: 4px; display: flex; align-items: center; gap: 8px;"> | |
| <span style="font-size: 20px;">☁️</span> Cloud Account | |
| </h3> | |
| <p style="color: var(--text-secondary); font-size: 14px; line-height: 1.5; margin-bottom: 8px;">Sichere deine Chats und API Keys mit End-zu-End Verschlüsselung und synchronisiere sie zwischen Geräten.</p> | |
| <div id="account-status-container"></div> | |
| </div> | |
| <!-- Model Selection --> | |
| <div class="liquid-glass-subtle" style="padding: 20px; border-radius: 20px;"> | |
| <h3 style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;"> | |
| <span style="font-size: 20px;">🧠</span> KI-Modell | |
| </h3> | |
| <label style="display: block; margin-bottom: 8px; color: var(--text-secondary); font-size: 14px;">Aktives Modell</label> | |
| <select id="model-select" style="width: 100%; padding: 12px; border-radius: 12px; background: rgba(0,0,0,0.4); border: 1px solid rgba(255,255,255,0.1); color: white; font-family: var(--font-ui); font-size: 16px; margin-bottom: 16px; outline: none; appearance: none;"> | |
| <optgroup label="Google Gemini"> | |
| <option value="gemini-3.6-flash" ${currentModel === 'gemini-3.6-flash' ? 'selected' : ''}>Gemini 3.6 Flash</option> | |
| <option value="gemini-3.5-flash" ${currentModel === 'gemini-3.5-flash' ? 'selected' : ''}>Gemini 3.5 Flash</option> | |
| <option value="gemini-3.5-flash-lite" ${currentModel === 'gemini-3.5-flash-lite' ? 'selected' : ''}>Gemini 3.5 Flash Lite</option> | |
| <option value="gemini-3.1-flash-lite" ${currentModel === 'gemini-3.1-flash-lite' ? 'selected' : ''}>Gemini 3.1 Flash Lite</option> | |
| <option value="gemini-3-flash-preview" ${currentModel === 'gemini-3-flash-preview' ? 'selected' : ''}>Gemini 3 Flash Preview</option> | |
| <option value="gemini-2.5-flash" ${currentModel === 'gemini-2.5-flash' ? 'selected' : ''}>Gemini 2.5 Flash</option> | |
| <option value="gemini-2.5-flash-lite" ${currentModel === 'gemini-2.5-flash-lite' ? 'selected' : ''}>Gemini 2.5 Flash Lite</option> | |
| </optgroup> | |
| <optgroup label="Gemma & Groq"> | |
| <option value="gemma-4-31b-it" ${currentModel === 'gemma-4-31b-it' ? 'selected' : ''}>Gemma 4 31B IT</option> | |
| <option value="gemma-4-26b-a4b-it" ${currentModel === 'gemma-4-26b-a4b-it' ? 'selected' : ''}>Gemma 4 26B IT</option> | |
| <option value="groq-llama-3.3-70b-versatile" ${currentModel === 'groq-llama-3.3-70b-versatile' ? 'selected' : ''}>Llama 3.3 70B (Groq)</option> | |
| <option value="groq-llama3-70b-8192" ${currentModel === 'groq-llama3-70b-8192' ? 'selected' : ''}>llama3-70b-8192</option> | |
| </optgroup> | |
| <optgroup label="OpenRouter (Mobile App)"> | |
| <option value="openrouter-meta-llama/llama-3.1-70b-instruct" ${currentModel === 'openrouter-meta-llama/llama-3.1-70b-instruct' ? 'selected' : ''}>Meta-Llama-3.1-70B-Instruct</option> | |
| <option value="openrouter-mistralai/codestral-2501" ${currentModel === 'openrouter-mistralai/codestral-2501' ? 'selected' : ''}>codestral-latest</option> | |
| <option value="openrouter-qwen/qwen-2.5-coder-32b-instruct:free" ${currentModel === 'openrouter-qwen/qwen-2.5-coder-32b-instruct:free' ? 'selected' : ''}>qwen/qwen-2.5-coder-32b-instruct:free</option> | |
| <option value="openrouter-google/gemma-3-27b-it" ${currentModel === 'openrouter-google/gemma-3-27b-it' ? 'selected' : ''}>gemma-4-31b</option> | |
| <option value="openrouter-deepseek/deepseek-v4-flash-0731" ${currentModel === 'openrouter-deepseek/deepseek-v4-flash-0731' ? 'selected' : ''}>DeepSeek V4 Flash</option> | |
| <option value="openrouter-deepseek/deepseek-v4-pro-latest" ${currentModel === 'openrouter-deepseek/deepseek-v4-pro-latest' ? 'selected' : ''}>DeepSeek V4 Pro</option> | |
| </optgroup> | |
| <optgroup label="DeepSeek V4"> | |
| <option value="deepseek-deepseek-reasoner" ${currentModel === 'deepseek-deepseek-reasoner' ? 'selected' : ''}>DeepSeek V4 Pro</option> | |
| <option value="deepseek-deepseek-chat" ${currentModel === 'deepseek-deepseek-chat' ? 'selected' : ''}>DeepSeek V4 Flash</option> | |
| </optgroup> | |
| <optgroup label="Lokal"> | |
| <option value="local-phone-qwen" ${currentModel === 'local-phone-qwen' ? 'selected' : ''}>Lokal Handy (Qwen 1.5B)</option> | |
| <option value="local-pc-llama" ${currentModel === 'local-pc-llama' ? 'selected' : ''}>Lokal PC (Llama 3 8B)</option> | |
| </optgroup> | |
| </select> | |
| <details style="background: rgba(0,0,0,0.2); border-radius: 12px; border: 1px solid rgba(255,255,255,0.05); margin-bottom: 16px;"> | |
| <summary style="padding: 16px; cursor: pointer; color: var(--text-primary); font-size: 16px; display: flex; align-items: center; justify-content: space-between; user-select: none; list-style: none;"> | |
| <div style="display: flex; align-items: center; gap: 8px;"> | |
| <span style="font-size: 18px;">🔑</span> API-Schlüssel & Verbindungen | |
| </div> | |
| <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="details-chevron"><path d="M6 9l6 6 6-6"/></svg> | |
| </summary> | |
| <div style="padding: 0 16px 16px 16px; border-top: 1px solid rgba(255,255,255,0.05); margin-top: 8px; padding-top: 16px;"> | |
| <label style="display: block; margin-bottom: 8px; font-size: 14px;"> | |
| <a href="https://aistudio.google.com/app/apikey" target="_blank" style="color: white; text-decoration: none; display: flex; align-items: center; gap: 4px;">Gemini API Key <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg></a> | |
| </label> | |
| <div style="position: relative; margin-bottom: 16px;"> | |
| <input type="password" id="api-key-input" value="${currentApiKey}" placeholder="AIzaSy..." style="width: 100%; padding: 12px; padding-right: 48px; border-radius: 12px; background: rgba(0,0,0,0.4); border: 1px solid rgba(255,255,255,0.1); color: white; font-family: var(--font-code); font-size: 14px; outline: none;"> | |
| <button type="button" class="paste-key-btn" data-target="api-key-input" style="position: absolute; right: 8px; top: 50%; transform: translateY(-50%); background: transparent; border: none; color: var(--text-secondary); cursor: pointer; padding: 4px; display: flex; align-items: center; justify-content: center;" title="Schlüssel einfügen"> | |
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg> | |
| </button> | |
| </div> | |
| <label style="display: block; margin-bottom: 8px; font-size: 14px;"> | |
| <a href="https://app.tavily.com/home" target="_blank" style="color: white; text-decoration: none; display: flex; align-items: center; gap: 4px;">Tavily API Key (Für Deep Research) <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg></a> | |
| </label> | |
| <div style="position: relative; margin-bottom: 16px;"> | |
| <input type="password" id="tavily-key-input" value="${db.getSetting('tavily_api_key') || ''}" placeholder="tvly-..." style="width: 100%; padding: 12px; padding-right: 48px; border-radius: 12px; background: rgba(0,0,0,0.4); border: 1px solid rgba(255,255,255,0.1); color: white; font-family: var(--font-code); font-size: 14px; outline: none;"> | |
| <button type="button" class="paste-key-btn" data-target="tavily-key-input" style="position: absolute; right: 8px; top: 50%; transform: translateY(-50%); background: transparent; border: none; color: var(--text-secondary); cursor: pointer; padding: 4px; display: flex; align-items: center; justify-content: center;" title="Schlüssel einfügen"> | |
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg> | |
| </button> | |
| </div> | |
| <label style="display: block; margin-bottom: 8px; font-size: 14px;"> | |
| <a href="https://console.groq.com/keys" target="_blank" style="color: white; text-decoration: none; display: flex; align-items: center; gap: 4px;">Groq API Key <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg></a> | |
| </label> | |
| <div style="position: relative; margin-bottom: 16px;"> | |
| <input type="password" id="groq-key-input" value="${groqApiKey}" placeholder="gsk_..." style="width: 100%; padding: 12px; padding-right: 48px; border-radius: 12px; background: rgba(0,0,0,0.4); border: 1px solid rgba(255,255,255,0.1); color: white; font-family: var(--font-code); font-size: 14px; outline: none;"> | |
| <button type="button" class="paste-key-btn" data-target="groq-key-input" style="position: absolute; right: 8px; top: 50%; transform: translateY(-50%); background: transparent; border: none; color: var(--text-secondary); cursor: pointer; padding: 4px; display: flex; align-items: center; justify-content: center;" title="Schlüssel einfügen"> | |
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg> | |
| </button> | |
| </div> | |
| <label style="display: block; margin-bottom: 8px; font-size: 14px;"> | |
| <a href="https://openrouter.ai/keys" target="_blank" style="color: white; text-decoration: none; display: flex; align-items: center; gap: 4px;">OpenRouter API Key <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg></a> | |
| </label> | |
| <div style="position: relative; margin-bottom: 16px;"> | |
| <input type="password" id="openrouter-key-input" value="${openrouterApiKey}" placeholder="sk-or-v1-..." style="width: 100%; padding: 12px; padding-right: 48px; border-radius: 12px; background: rgba(0,0,0,0.4); border: 1px solid rgba(255,255,255,0.1); color: white; font-family: var(--font-code); font-size: 14px; outline: none;"> | |
| <button type="button" class="paste-key-btn" data-target="openrouter-key-input" style="position: absolute; right: 8px; top: 50%; transform: translateY(-50%); background: transparent; border: none; color: var(--text-secondary); cursor: pointer; padding: 4px; display: flex; align-items: center; justify-content: center;" title="Schlüssel einfügen"> | |
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg> | |
| </button> | |
| </div> | |
| <label style="display: block; margin-bottom: 8px; font-size: 14px;"> | |
| <a href="https://platform.deepseek.com/api_keys" target="_blank" style="color: white; text-decoration: none; display: flex; align-items: center; gap: 4px;">DeepSeek API Key <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg></a> | |
| </label> | |
| <div style="position: relative; margin-bottom: 16px;"> | |
| <input type="password" id="deepseek-key-input" value="${deepseekApiKey}" placeholder="sk-..." style="width: 100%; padding: 12px; padding-right: 48px; border-radius: 12px; background: rgba(0,0,0,0.4); border: 1px solid rgba(255,255,255,0.1); color: white; font-family: var(--font-code); font-size: 14px; outline: none;"> | |
| <button type="button" class="paste-key-btn" data-target="deepseek-key-input" style="position: absolute; right: 8px; top: 50%; transform: translateY(-50%); background: transparent; border: none; color: var(--text-secondary); cursor: pointer; padding: 4px; display: flex; align-items: center; justify-content: center;" title="Schlüssel einfügen"> | |
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg> | |
| </button> | |
| </div> | |
| <label style="display: block; margin-bottom: 8px; font-size: 14px;"> | |
| <a href="https://huggingface.co/settings/tokens" target="_blank" style="color: white; text-decoration: none; display: flex; align-items: center; gap: 4px;">Hugging Face API Token <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg></a> | |
| </label> | |
| <div style="position: relative; margin-bottom: 16px;"> | |
| <input type="password" id="huggingface-key-input" value="${huggingfaceApiKey}" placeholder="hf_..." style="width: 100%; padding: 12px; padding-right: 48px; border-radius: 12px; background: rgba(0,0,0,0.4); border: 1px solid rgba(255,255,255,0.1); color: white; font-family: var(--font-code); font-size: 14px; outline: none;"> | |
| <button type="button" class="paste-key-btn" data-target="huggingface-key-input" style="position: absolute; right: 8px; top: 50%; transform: translateY(-50%); background: transparent; border: none; color: var(--text-secondary); cursor: pointer; padding: 4px; display: flex; align-items: center; justify-content: center;" title="Schlüssel einfügen"> | |
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg> | |
| </button> | |
| </div> | |
| <label style="display: block; margin-bottom: 8px; font-size: 14px;"> | |
| <a href="https://serpapi.com/manage-api-key" target="_blank" style="color: white; text-decoration: none; display: flex; align-items: center; gap: 4px;">SerpAPI Key (Optional) <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg></a> | |
| </label> | |
| <div style="position: relative; margin-bottom: 16px;"> | |
| <input type="password" id="serpapi-key-input" value="${db.getSetting('serpapi_key') || ''}" placeholder="Dein SerpAPI Key..." style="width: 100%; padding: 12px; padding-right: 48px; border-radius: 12px; background: rgba(0,0,0,0.4); border: 1px solid rgba(255,255,255,0.1); color: white; font-family: var(--font-code); font-size: 14px; outline: none;"> | |
| <button type="button" class="paste-key-btn" data-target="serpapi-key-input" style="position: absolute; right: 8px; top: 50%; transform: translateY(-50%); background: transparent; border: none; color: var(--text-secondary); cursor: pointer; padding: 4px; display: flex; align-items: center; justify-content: center;" title="Schlüssel einfügen"> | |
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg> | |
| </button> | |
| </div> | |
| <label style="display: block; margin-bottom: 8px; font-size: 14px;"> | |
| <a href="https://www.deepl.com/pro-api?cta=header-pro-api" target="_blank" style="color: white; text-decoration: none; display: flex; align-items: center; gap: 4px;">DeepL API Key (Schnelleingabe) <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg></a> | |
| </label> | |
| <div style="position: relative; margin-bottom: 16px;"> | |
| <input type="password" id="deepl-key-input" value="${db.getSetting('deepl_api_key') || ''}" placeholder="Dein DeepL API Free Key..." style="width: 100%; padding: 12px; padding-right: 48px; border-radius: 12px; background: rgba(0,0,0,0.4); border: 1px solid rgba(255,255,255,0.1); color: white; font-family: var(--font-code); font-size: 14px; outline: none;"> | |
| <button type="button" class="paste-key-btn" data-target="deepl-key-input" style="position: absolute; right: 8px; top: 50%; transform: translateY(-50%); background: transparent; border: none; color: var(--text-secondary); cursor: pointer; padding: 4px; display: flex; align-items: center; justify-content: center;" title="Schlüssel einfügen"> | |
| <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg> | |
| </button> | |
| </div> | |
| <label style="display: block; margin-bottom: 8px; color: var(--text-secondary); font-size: 14px;">Ollama URL (für lokale KI)</label> | |
| <div style="position: relative;"> | |
| <input type="text" id="ollama-url-input" value="${ollamaUrl}" placeholder="http://localhost:11434" style="width: 100%; padding: 12px; padding-right: 48px; border-radius: 12px; background: rgba(0,0,0,0.4); border: 1px solid rgba(255,255,255,0.1); color: white; font-family: var(--font-code); font-size: 14px; outline: none;"> | |
| </div> | |
| </div> | |
| </details> | |
| </div> | |
| <!-- Start Behavior --> | |
| <div class="liquid-glass-subtle" style="padding: 20px; border-radius: 20px;"> | |
| <h3 style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;"> | |
| <span style="font-size: 20px;">🚀</span> Startverhalten | |
| </h3> | |
| <label style="display: block; margin-bottom: 8px; color: var(--text-secondary); font-size: 14px;">Beim Öffnen der App</label> | |
| <select id="start-behavior-select" style="width: 100%; padding: 12px; border-radius: 12px; background: rgba(0,0,0,0.4); border: 1px solid rgba(255,255,255,0.1); color: white; font-family: var(--font-ui); font-size: 16px; outline: none; appearance: none;"> | |
| <option value="new" ${startBehavior === 'new' ? 'selected' : ''}>Immer einen neuen Chat starten</option> | |
| <option value="last" ${startBehavior === 'last' ? 'selected' : ''}>Zuletzt aktiven Chat öffnen</option> | |
| </select> | |
| </div> | |
| <!-- Background Style --> | |
| <div class="liquid-glass-subtle" style="padding: 20px; border-radius: 20px;"> | |
| <h3 style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;"> | |
| <span style="font-size: 20px;">🎨</span> Hintergrund-Stil | |
| </h3> | |
| <select id="background-style-select" style="width: 100%; padding: 12px; border-radius: 12px; background: rgba(0,0,0,0.4); border: 1px solid rgba(255,255,255,0.1); color: white; font-family: var(--font-ui); font-size: 16px; outline: none; appearance: none;"> | |
| <option value="standard" ${bgStyle === 'standard' ? 'selected' : ''}>Standard</option> | |
| <option value="fluid-dark" ${bgStyle === 'fluid-dark' ? 'selected' : ''}>Fluid Dark (Animiert)</option> | |
| </select> | |
| </div> | |
| <!-- Style Profile --> | |
| <div class="liquid-glass-subtle" style="padding: 20px; border-radius: 20px;"> | |
| <h3 style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;"> | |
| <span style="font-size: 20px;">✍️</span> Schreibstil-Profil | |
| </h3> | |
| <p style="color: var(--text-secondary); font-size: 14px; margin-bottom: 12px; line-height: 1.5;">Dein persönlicher Stil (Note 2, 12. Klasse) ist aktiv und steuert jede Antwort der KI.</p> | |
| <textarea id="style-profile-input" style="width: 100%; min-height: 120px; background: rgba(0,0,0,0.4); padding: 16px; border-radius: 12px; border: 1px solid rgba(255,255,255,0.1); font-family: var(--font-code); font-size: 12px; color: var(--text-secondary); outline: none; resize: vertical;">${db.getSetting('style_profile') || ''}</textarea> | |
| </div> | |
| <!-- OS Integration --> | |
| <div class="liquid-glass-subtle" style="padding: 20px; border-radius: 20px;"> | |
| <h3 style="margin-bottom: 16px; display: flex; align-items: center; gap: 8px;"> | |
| <span style="font-size: 20px;">⚙️</span> System-Integration | |
| </h3> | |
| <p style="color: var(--text-secondary); font-size: 14px; margin-bottom: 12px; line-height: 1.5;">Füge Schnelleingabe zu deinen Android Quick Settings hinzu (Android 13+).</p> | |
| <button id="add-quick-tile-btn" class="liquid-glass" style="padding: 12px 16px; border-radius: 12px; color: white; font-size: 14px; font-weight: 500; cursor: pointer; transition: opacity 0.2s; border: 1px solid rgba(255, 255, 255, 0.1); width: 100%;"> | |
| Zu Schnelleinstellungen hinzufügen | |
| </button> | |
| </div> | |
| <button id="save-settings-btn" class="liquid-glass" style="padding: 16px; border-radius: 16px; color: var(--primary-accent); font-size: 16px; font-weight: 600; cursor: pointer; transition: opacity 0.2s; border: 1px solid rgba(0, 122, 255, 0.3);"> | |
| Einstellungen speichern | |
| </button> | |
| </div> | |
| `; | |
| } | |
| attachEvents() { | |
| const saveBtn = document.getElementById('save-settings-btn'); | |
| // Attach paste button listeners | |
| document.querySelectorAll('.paste-key-btn').forEach(btn => { | |
| btn.addEventListener('click', async (e) => { | |
| e.preventDefault(); | |
| const targetId = btn.getAttribute('data-target'); | |
| const input = document.getElementById(targetId); | |
| if (!input) return; | |
| // Force focus into the input field so the OS knows we are in typing mode | |
| input.focus(); | |
| try { | |
| // 1. Try native Capacitor clipboard (works reliably if inside Android/iOS App wrapper) | |
| let text = ''; | |
| try { | |
| const result = await Clipboard.read(); | |
| text = result.value; | |
| } catch(err) { | |
| // 2. Fallback to Web API (works in secure contexts) | |
| if (navigator.clipboard && navigator.clipboard.readText) { | |
| text = await navigator.clipboard.readText(); | |
| } | |
| } | |
| if (text) { | |
| input.value = text.trim(); | |
| const originalHTML = btn.innerHTML; | |
| btn.innerHTML = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--success)" stroke-width="2"><polyline points="20 6 9 17 4 12"></polyline></svg>`; | |
| setTimeout(() => { btn.innerHTML = originalHTML; }, 2000); | |
| } else { | |
| // 3. Fallback: just trigger native paste event | |
| document.execCommand('paste'); | |
| } | |
| } catch (error) { | |
| console.warn('Konnte nicht einfügen', error); | |
| document.execCommand('paste'); | |
| } | |
| }); | |
| }); | |
| if (!saveBtn) return; | |
| saveBtn.addEventListener('click', () => { | |
| const model = document.getElementById('model-select').value; | |
| const apiKey = document.getElementById('api-key-input').value.trim(); | |
| const tavilyKey = document.getElementById('tavily-key-input').value.trim(); | |
| const groqKey = document.getElementById('groq-key-input').value.trim(); | |
| const orKey = document.getElementById('openrouter-key-input').value.trim(); | |
| const dsKey = document.getElementById('deepseek-key-input').value.trim(); | |
| const hfKey = document.getElementById('huggingface-key-input').value.trim(); | |
| const serpApiKey = document.getElementById('serpapi-key-input').value.trim(); | |
| const deeplKey = document.getElementById('deepl-key-input').value.trim(); | |
| const ollamaUrl = document.getElementById('ollama-url-input').value.trim(); | |
| const startBehavior = document.getElementById('start-behavior-select').value; | |
| const bgStyle = document.getElementById('background-style-select').value; | |
| const styleProfile = document.getElementById('style-profile-input').value.trim(); | |
| db.setSetting('model', model); | |
| db.setSetting('style_profile', styleProfile); | |
| db.setSetting('start_behavior', startBehavior); | |
| db.setSetting('background_style', bgStyle); | |
| document.body.dataset.bgStyle = bgStyle; | |
| if (apiKey) db.setSetting('gemini_api_key', apiKey); | |
| if (tavilyKey !== undefined) db.setSetting('tavily_api_key', tavilyKey); | |
| if (groqKey) db.setSetting('groq_api_key', groqKey); | |
| if (orKey) db.setSetting('openrouter_api_key', orKey); | |
| if (dsKey) db.setSetting('deepseek_api_key', dsKey); | |
| if (hfKey) db.setSetting('huggingface_api_key', hfKey); | |
| if (serpApiKey) db.setSetting('serpapi_key', serpApiKey); | |
| if (deeplKey) db.setSetting('deepl_api_key', deeplKey); | |
| if (ollamaUrl) db.setSetting('ollama_url', ollamaUrl); | |
| // Visual feedback | |
| const originalText = saveBtn.innerText; | |
| saveBtn.innerText = 'Gespeichert! ✓'; | |
| saveBtn.style.color = 'var(--success)'; | |
| saveBtn.style.borderColor = 'rgba(48, 209, 88, 0.3)'; | |
| setTimeout(() => { | |
| saveBtn.innerText = originalText; | |
| saveBtn.style.color = 'var(--primary-accent)'; | |
| saveBtn.style.borderColor = 'rgba(0, 122, 255, 0.3)'; | |
| }, 2000); | |
| }); | |
| this.updateAccountStatus(); | |
| authService.onAuthStateChange(() => { | |
| this.updateAccountStatus(); | |
| }); | |
| const addTileBtn = document.getElementById('add-quick-tile-btn'); | |
| if (addTileBtn) { | |
| addTileBtn.addEventListener('click', async () => { | |
| const originalText = addTileBtn.innerText; | |
| addTileBtn.innerText = 'Wird angefragt...'; | |
| const success = await promptQuickSettingsTile(); | |
| if (success) { | |
| addTileBtn.innerText = 'Hinzugefügt ✓'; | |
| addTileBtn.style.color = 'var(--success)'; | |
| } else { | |
| addTileBtn.innerText = 'Nicht unterstützt oder Fehler'; | |
| addTileBtn.style.color = '#ff3b30'; | |
| } | |
| setTimeout(() => { | |
| addTileBtn.innerText = originalText; | |
| addTileBtn.style.color = 'white'; | |
| }, 3000); | |
| }); | |
| } | |
| } | |
| updateAccountStatus() { | |
| const container = this.container.querySelector('#account-status-container'); | |
| if (!container) return; | |
| if (authService.user) { | |
| const email = authService.user.email; | |
| const isUnlocked = authService.isCryptoUnlocked(); | |
| container.innerHTML = ` | |
| <div style="background: rgba(0,0,0,0.3); padding: 12px 16px; border-radius: 12px; border: 1px solid rgba(255,255,255,0.05); display: flex; justify-content: space-between; align-items: center;"> | |
| <div> | |
| <div style="font-size: 14px; color: white; font-weight: 500;">Eingeloggt als</div> | |
| <div style="font-size: 13px; color: var(--text-secondary);">${email}</div> | |
| <div style="font-size: 12px; margin-top: 4px; color: ${isUnlocked ? '#34c759' : '#ff9f0a'};"> | |
| ${isUnlocked ? '🔒 E2E Verschlüsselung aktiv' : '🔓 Keys gesperrt (Passwort benötigt)'} | |
| </div> | |
| </div> | |
| <button id="auth-action-btn" class="liquid-glass" style="padding: 8px 16px; border-radius: 10px; font-size: 14px; cursor: pointer;"> | |
| ${isUnlocked ? 'Abmelden' : 'Entsperren'} | |
| </button> | |
| </div> | |
| `; | |
| container.querySelector('#auth-action-btn').addEventListener('click', () => { | |
| if (isUnlocked) { | |
| authService.signOut(); | |
| } else { | |
| const authView = new AuthView(); | |
| authView.show(); | |
| } | |
| }); | |
| } else { | |
| container.innerHTML = ` | |
| <button id="auth-action-btn" class="liquid-glass" style="width: 100%; padding: 14px; border-radius: 12px; font-size: 15px; font-weight: 500; cursor: pointer; color: white;"> | |
| Anmelden oder Registrieren | |
| </button> | |
| `; | |
| container.querySelector('#auth-action-btn').addEventListener('click', () => { | |
| const authView = new AuthView(); | |
| authView.show(); | |
| }); | |
| } | |
| } | |
| } | |