APP-Backend / src /views /settings /settings-view.js
Luca448's picture
Savestate: Update DeepSeek v4 Flash ID to static 0731 version to resolve OpenRouter 400 error
6617164
Raw History Blame Contribute Delete
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();
});
}
}
}