final edit sys-promt and markdown image
This commit is contained in:
459
static/chat.css
Normal file
459
static/chat.css
Normal file
@@ -0,0 +1,459 @@
|
||||
/* --- Basis Layout für die Chat Seite --- */
|
||||
.chat-main {
|
||||
display: flex;
|
||||
height: calc(100vh - 42px);
|
||||
margin-top: 45px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Kleiner visueller Indikator (zwei Punkte oder Linien) */
|
||||
.resizer::after {
|
||||
content: "⋮";
|
||||
color: var(--text-muted);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* --- Artikel Sidebar --- */
|
||||
.chat-article {
|
||||
/* width: 40%; Breite des Artikels
|
||||
max-width: 800px;*/
|
||||
height: 100%;
|
||||
background: var(--bg-section-alt);
|
||||
color: var(--text-main);
|
||||
overflow-y: auto; /* Erlaubt vertikales Scrollen */
|
||||
overflow-x: hidden; /* Verhindert horizontales Wackeln */
|
||||
|
||||
/* Positionierung der Scrollbar erzwingen */
|
||||
direction: rtl; /* Left-to-Right (Standard) setzt Scrollbar nach rechts */
|
||||
/* Optional: Ein schöneres Design für die Scrollbar (Webkit) */
|
||||
scrollbar-gutter: stable; /* Verhindert das Springen des Inhalts beim Erscheinen */
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* --- Artikel-Formatierung --- */
|
||||
.article-content {
|
||||
direction: ltr; /* Left-to-Right: Textfluss wieder normal */
|
||||
padding: 1rem; /* Hier kommt das eigentliche Padding für den Text hin */
|
||||
/* 1. Blocksatz */
|
||||
text-align: justify;
|
||||
/* Verhindert große Lücken im Blocksatz durch Silbentrennung */
|
||||
hyphens: auto;
|
||||
line-height: 1.6; /* Angenehmer Zeilenabstand */
|
||||
color: var(--text-main);
|
||||
}
|
||||
|
||||
/* 2. Abstand zwischen Absätzen */
|
||||
.article-content p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1.5rem; /* Etwa 1,5 Zeilen Platz nach jedem Absatz */
|
||||
}
|
||||
|
||||
/* Letzten Absatz ohne Abstand nach unten, falls ein Footer folgt */
|
||||
.article-content p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* 3. Überschriften-Abstände (damit sie klar vom Text getrennt sind) */
|
||||
.article-content h1,
|
||||
.article-content h2,
|
||||
.article-content h3 {
|
||||
margin-top: 2.5rem;
|
||||
margin-bottom: 1rem;
|
||||
text-align: left; /* Überschriften bleiben linksbündig */
|
||||
}
|
||||
|
||||
/* 4. Listen-Formatierung */
|
||||
.article-content ul,
|
||||
.article-content ol {
|
||||
margin-bottom: 1.5rem;
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
.article-content li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
|
||||
/* --- Chat Container --- */
|
||||
.chat-container {
|
||||
flex: 1; /* Nimmt den verfügbaren Platz ein */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background: var(--card-bg);
|
||||
border-right: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
|
||||
/* --- System-Konfiguration Styling --- */
|
||||
.system-config {
|
||||
background-color: var(--bg-section-alt);
|
||||
border: 1px dashed var(--accent);
|
||||
border-radius: 12px;
|
||||
padding: 1.2rem;
|
||||
margin-bottom: 2rem;
|
||||
width: 100%;
|
||||
/*align-self: stretch;*/
|
||||
box-shadow: 0 4px 12px var(--card-shadow);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.system-config strong {
|
||||
display: block;
|
||||
color: var(--accent);
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05rem;
|
||||
}
|
||||
|
||||
.system-config p {
|
||||
margin-bottom: 0.8rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Die Textarea für den Prompt */
|
||||
.system-textarea {
|
||||
width: 100%;
|
||||
min-height: 100px;
|
||||
background: var(--bg-input);
|
||||
color: var(--text-main);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
font-family: inherit;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.5;
|
||||
resize: vertical;
|
||||
outline: none;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.system-textarea:focus { border-color: var(--accent); }
|
||||
.system-config.locked { display: none; }
|
||||
|
||||
/* Eingabefeld (Chat) */
|
||||
#user-input {
|
||||
flex: 1;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid var(--border-input);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-input);
|
||||
color: var(--text-input);
|
||||
font-size: 1rem;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
#user-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
|
||||
}
|
||||
|
||||
#chat-messages {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.message {
|
||||
max-width: 80%;
|
||||
padding: 12px 16px;
|
||||
border-radius: 15px;
|
||||
line-height: 1.5;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.user-message {
|
||||
align-self: flex-end;
|
||||
background-color: var(--accent);
|
||||
color: var(--accent-text);
|
||||
border-bottom-right-radius: 5px;
|
||||
}
|
||||
|
||||
.ai-message {
|
||||
align-self: flex-start;
|
||||
background-color: var(--bg-section-alt);
|
||||
color: var(--text-main);
|
||||
border-bottom-left-radius: 5px;
|
||||
}
|
||||
|
||||
.chat-input-area {
|
||||
padding: 20px;
|
||||
background-color: var(--card-bg);
|
||||
border-top: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* View-Klassen */
|
||||
.view-chat-only .chat-article { display: none; }
|
||||
.view-chat-only .chat-container { display: flex; width: 100%; }
|
||||
|
||||
/* --- View-Logik: Article Only --- */
|
||||
.view-article-only .chat-container,
|
||||
.view-article-only .resizer {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.view-article-only .chat-article {
|
||||
display: block !important;
|
||||
width: 100% !important; /* Erzwingt volle Breite */
|
||||
flex: 1 1 100% !important; /* Nimmt den gesamten Flex-Platz ein */
|
||||
max-width: none !important;
|
||||
border-right: none;
|
||||
padding-right: 20px; /* Ein bisschen Platz zum Rand lassen */
|
||||
}
|
||||
|
||||
.view-split .chat-article {
|
||||
display: block;
|
||||
flex: 0 0 40%; /* WICHTIG: Verhindert, dass das CSS die Breite erzwingt */
|
||||
/* width: ; Das ist jetzt nur noch der Startwert */
|
||||
max-width: 80%; /* Verhindert, dass man den Chat ganz wegdrückt */
|
||||
min-width: 10%; /* Verhindert, dass der Artikel verschwindet */
|
||||
}
|
||||
|
||||
.view-split .chat-container {
|
||||
display: flex;
|
||||
flex: 1 1 0; /* Nimmt sich dynamisch den Rest */
|
||||
min-width: 0; /* Verhindert das "Rausplatzen" bei langem Text */
|
||||
}
|
||||
|
||||
.resizing {
|
||||
cursor: col-resize;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.resizer {
|
||||
width: 6px;
|
||||
cursor: col-resize;
|
||||
background-color: var(--border-color);
|
||||
transition: background 0.3s;
|
||||
cursor: col-resize;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
.resizer:hover {
|
||||
background-color: var(--accent); /* Färbt sich beim Drüberfahren ein */
|
||||
}
|
||||
|
||||
|
||||
/* Button Deaktiviert-Status */
|
||||
.send-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
filter: grayscale(1);
|
||||
}
|
||||
|
||||
/* Die "KI schreibt..." Blase */
|
||||
.typing-indicator {
|
||||
align-self: flex-start;
|
||||
background-color: var(--bg-section-alt);
|
||||
padding: 12px 16px;
|
||||
border-radius: 15px;
|
||||
border-bottom-left-radius: 5px;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.typing-indicator span {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: var(--accent);
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
animation: bounce 1.4s infinite ease-in-out both;
|
||||
}
|
||||
|
||||
.typing-indicator span:nth-child(1) { animation-delay: -0.32s; }
|
||||
.typing-indicator span:nth-child(2) { animation-delay: -0.16s; }
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 80%, 100% { transform: scale(0); }
|
||||
40% { transform: scale(1.0); }
|
||||
}
|
||||
|
||||
/* Bilder im Artikel und Chat begrenzen */
|
||||
.article-content img,
|
||||
.ai-message img {
|
||||
max-width: 100%; /* Nie breiter als der Text */
|
||||
height: auto; /* Proportional bleiben */
|
||||
border-radius: 8px; /* Sieht moderner aus */
|
||||
margin: 1rem 0;
|
||||
box-shadow: 0 4px 10px var(--card-shadow);
|
||||
}
|
||||
|
||||
/* Links hervorheben */
|
||||
.article-content a,
|
||||
.ai-message a {
|
||||
color: var(--accent);
|
||||
text-decoration: underline;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.article-content a:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* Videos responsiv machen */
|
||||
.article-content video,
|
||||
.article-content iframe {
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
/*
|
||||
.nav-links { display: none;}
|
||||
*/
|
||||
/* AUẞERHALB des Media Queries (für Desktop) */
|
||||
.nav-links {
|
||||
display: flex; /* Auf Desktop immer zeigen */
|
||||
gap: 2rem;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.close-edit-btn {
|
||||
display: none !important;
|
||||
}
|
||||
/* Mobile Hamburger & Menu */
|
||||
@media (max-width: 600px) {
|
||||
.hamburger {
|
||||
display: flex !important; /* Hamburger nur auf Chat-Mobile zeigen */
|
||||
}
|
||||
.nav-links {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 40px; /* Abstand nach unten vom Hamburger */
|
||||
left: 10px; /* Richtet das Menü am linken Rand des Hamburgers aus */
|
||||
right: auto; /* Verhindert das "Ziehen" nach rechts */
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
/* padding: 1rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 10px 20px var(--card-shadow);*/
|
||||
|
||||
border-radius: 8px;
|
||||
min-width: 180px;
|
||||
box-shadow: 0 10px 25px var(--card-shadow);
|
||||
z-index: 2000;
|
||||
/*padding: 0.5rem 0;*/
|
||||
|
||||
}
|
||||
.nav-links.mobile-open {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/*gap: 10px;*/
|
||||
}
|
||||
|
||||
.resizer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.chat-main {
|
||||
/* WICHTIG: Nutzt die volle Höhe abzüglich Header,
|
||||
aber ohne aus dem Viewport zu ragen */
|
||||
height: calc(100dvh - 45px); /* dvh = dynamic viewport height */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.view-split {
|
||||
flex-direction: column !important;
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
.view-split .chat-article {
|
||||
width: 100% !important;
|
||||
max-width: none !important;
|
||||
min-width: 100% !important;
|
||||
/* Wir geben dem Artikel eine feste, aber begrenzte Höhe */
|
||||
height: 35% !important;
|
||||
flex: 0 0 35% !important;
|
||||
border-bottom: 2px solid var(--border-color);
|
||||
}
|
||||
|
||||
.view-split .chat-container {
|
||||
width: 100% !important;
|
||||
max-width: none !important;
|
||||
min-width: 100% !important;
|
||||
/* Der Chat nimmt den restlichen Platz ein (65%) */
|
||||
height: 65% !important;
|
||||
flex: 1 1 65% !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden; /* Verhindert, dass der Container wächst */
|
||||
}
|
||||
|
||||
#system-prompt-container {
|
||||
/* Verhindert, dass das Mitwachsen der Textarea den Chat-Scroll triggert */
|
||||
contain: layout;
|
||||
}
|
||||
|
||||
#chat-messages {
|
||||
/* flex: 1; Drückt die Eingabezeile nach unten, aber hält sie im Bild */
|
||||
display: block !important;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.message {
|
||||
/* Da wir oben display: block nutzen, brauchen wir margin für die Abstände */
|
||||
margin-bottom: 15px;
|
||||
clear: both;
|
||||
display: block;
|
||||
/*width: fit-content;*/
|
||||
}
|
||||
/*
|
||||
.user-message {
|
||||
margin-left: auto;
|
||||
}*/
|
||||
|
||||
.user-message { float: right; }
|
||||
.ai-message { float: left; }
|
||||
.system-message {
|
||||
float: none;
|
||||
width: 100%;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.chat-input-area {
|
||||
/* Sicherstellen, dass die Eingabezeile immer sichtbar bleibt */
|
||||
flex-shrink: 0;
|
||||
padding: 10px 15px; /* Etwas kompakter auf Mobile */
|
||||
background: var(--card-bg);
|
||||
}
|
||||
|
||||
/* Diese Klasse wird per JS gesetzt, wenn du ins Feld klickst */
|
||||
.mobile-edit-mode {
|
||||
position: fixed !important;
|
||||
top: 45px !important;
|
||||
/*margin-top: 45px;*/
|
||||
left: 0 !important;
|
||||
max-width: none !important;
|
||||
min-width: 100% !important;
|
||||
width: 100vw !important;
|
||||
height: 75vh;
|
||||
z-index: 9999;
|
||||
background: var(--bg-body); /* Deine Hintergrundfarbe */
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
display: flex !important;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.mobile-edit-mode textarea {
|
||||
flex: 1; /* Nutzt den Platz über der Tastatur */
|
||||
font-size: 16px; /* Wichtig gegen Auto-Zoom */
|
||||
}
|
||||
}
|
||||
278
static/chat.js
Normal file
278
static/chat.js
Normal file
@@ -0,0 +1,278 @@
|
||||
|
||||
const chatMessages = document.getElementById('chat-messages');
|
||||
const userInput = document.getElementById('user-input');
|
||||
|
||||
let messageHistory = [];
|
||||
let isFirstMessage = true;
|
||||
const MAX_CHAR_LIMIT = 24000; // Dein Volumen-Limit in Zeichen (ca. 2000-3000 Token)
|
||||
|
||||
// Hilfsfunktion: Berechnet das aktuelle Volumen der History
|
||||
function getHistoryVolume() {
|
||||
return messageHistory.reduce((sum, msg) => sum + msg.content.length, 0);
|
||||
}
|
||||
|
||||
function safeScrollToBottom() {
|
||||
if (isEditingSystemPrompt) return; // Wenn wir editieren, tun wir GAR NICHTS
|
||||
|
||||
const chatMessages = document.getElementById('chat-messages');
|
||||
if (chatMessages) {
|
||||
chatMessages.scrollTo({
|
||||
top: chatMessages.scrollHeight,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function sendMessage() {
|
||||
const userInput = document.getElementById('user-input');
|
||||
const sendBtn = document.querySelector('.send-btn');
|
||||
const systemInput = document.getElementById('system-prompt-input');
|
||||
const systemContainer = document.getElementById('system-prompt-container');
|
||||
const chatMessages = document.getElementById('chat-messages');
|
||||
|
||||
const text = userInput.value.trim();
|
||||
if (!text) return;
|
||||
|
||||
// --- 1. VOLUMEN-CHECK VOR DEM SENDEN ---
|
||||
// Wir prüfen das aktuelle Volumen + die neue Nachricht
|
||||
const currentVolume = getHistoryVolume();
|
||||
if (currentVolume + text.length > MAX_CHAR_LIMIT) {
|
||||
appendMessage('system', "⚠️ Kontext-Limit erreicht. Das Volumen der Konversation ist zu groß für die Demo.");
|
||||
userInput.disabled = true;
|
||||
sendBtn.disabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// --- 1. UI SPERREN (Warte-Modus) ---
|
||||
userInput.disabled = true;
|
||||
sendBtn.disabled = true;
|
||||
const originalBtnText = sendBtn.innerText;
|
||||
sendBtn.innerText = '...';
|
||||
|
||||
// --- 2. SYSTEM-PROMPT LOGIK (Nur beim ersten Mal) ---
|
||||
if (isFirstMessage) {
|
||||
const systemText = systemInput.value.trim();
|
||||
|
||||
// 1. In die API-History pushen
|
||||
messageHistory.push({ "role": "system", "content": systemText });
|
||||
|
||||
// 2. Optisch in den Chat einfügen (als System-Nachricht)
|
||||
appendMessage('system', "⚙️ System-Prompt\n" + systemText);
|
||||
|
||||
// Editor ausblenden
|
||||
if (systemContainer) systemContainer.classList.add('locked');
|
||||
isFirstMessage = false;
|
||||
}
|
||||
|
||||
// --- 3. USER-NACHRICHT ---
|
||||
messageHistory.push({ "role": "user", "content": text });
|
||||
appendMessage('user', text);
|
||||
userInput.value = '';
|
||||
|
||||
// --- 4. LADE-ANIMATION ANZEIGEN ---
|
||||
const typingDiv = document.createElement('div');
|
||||
typingDiv.className = 'message typing-indicator';
|
||||
typingDiv.id = 'temp-typing';
|
||||
typingDiv.innerHTML = '<span></span><span></span><span></span>';
|
||||
chatMessages.appendChild(typingDiv);
|
||||
safeScrollToBottom();
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/chat/completions', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
"model": "trinity-test",
|
||||
"messages": messageHistory,
|
||||
"stream": false
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error('Server antwortet nicht');
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Animation entfernen
|
||||
const temp = document.getElementById('temp-typing');
|
||||
if (temp) temp.remove();
|
||||
|
||||
// Antwort verarbeiten
|
||||
const aiMessage = data.choices[0].message;
|
||||
messageHistory.push(aiMessage);
|
||||
appendMessage('ai', aiMessage.content);
|
||||
|
||||
// --- 3. STATUS-UPDATE FÜR DEN NUTZER ---
|
||||
const newTotalVolume = getHistoryVolume();
|
||||
const fillPercentage = Math.round((newTotalVolume / MAX_CHAR_LIMIT) * 100);
|
||||
console.log(`Kontext-Auslastung: ${fillPercentage}%`);
|
||||
|
||||
if (newTotalVolume > MAX_CHAR_LIMIT * 0.9) {
|
||||
appendMessage('system', `Achtung: Der Speicher ist zu ${fillPercentage}% voll.`);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
const temp = document.getElementById('temp-typing');
|
||||
if (temp) temp.remove();
|
||||
appendMessage('ai', 'Fehler: ' + err.message);
|
||||
console.error(err);
|
||||
} finally {
|
||||
// UI nur freigeben, wenn noch Platz ist
|
||||
if (getHistoryVolume() < MAX_CHAR_LIMIT) {
|
||||
userInput.disabled = false;
|
||||
sendBtn.disabled = false;
|
||||
sendBtn.innerText = originalBtnText;
|
||||
userInput.focus();
|
||||
} else {
|
||||
userInput.placeholder = "Kontext voll.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Erweitere deine appendMessage Funktion um den 'system' Typ
|
||||
function appendMessage(role, text) {
|
||||
const chatMessages = document.getElementById('chat-messages');
|
||||
const msgDiv = document.createElement('div');
|
||||
|
||||
// role kann 'user', 'ai' oder 'system' sein
|
||||
msgDiv.className = `message ${role}-message`;
|
||||
msgDiv.innerText = text;
|
||||
|
||||
chatMessages.appendChild(msgDiv);
|
||||
|
||||
safeScrollToBottom();
|
||||
}
|
||||
|
||||
// ======================================================
|
||||
|
||||
// Navigations-Logik für Chat
|
||||
function setView(viewMode) {
|
||||
const main = document.getElementById('chatMain'); // Sicherstellen, dass <main id="chatMain"> existiert!
|
||||
const nav = document.getElementById('navLinks'); // Konsistent mit common.js
|
||||
|
||||
if (!main) return;
|
||||
|
||||
main.classList.remove('view-chat-only', 'view-article-only', 'view-split');
|
||||
main.classList.add('view-' + viewMode);
|
||||
|
||||
if (nav) nav.classList.remove('mobile-open');
|
||||
}
|
||||
|
||||
// Auch die toggle-Funktion für den Chat-Hamburger:
|
||||
function toggleViewMenu() {
|
||||
const nav = document.getElementById('navLinks');
|
||||
if (nav) nav.classList.toggle('mobile-open');
|
||||
}
|
||||
|
||||
|
||||
// Initialisierung
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Erzwingt Split-View beim Start
|
||||
//setView('split');
|
||||
// ... dein bestehender Code ...
|
||||
const savedTheme = localStorage.getItem('theme') || 'auto';
|
||||
applyTheme(savedTheme);
|
||||
|
||||
const navLinksContainer = document.getElementById('navLinks');
|
||||
const hamburger = document.querySelector('.hamburger');
|
||||
|
||||
if (navLinksContainer && hamburger) {
|
||||
const navLinks = navLinksContainer.querySelectorAll('a');
|
||||
|
||||
navLinks.forEach(link => {
|
||||
link.addEventListener('click', () => {
|
||||
if (navLinksContainer.classList.contains('mobile-open')) {
|
||||
navLinksContainer.classList.remove('mobile-open');
|
||||
hamburger.classList.remove('active');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const resizer = document.getElementById('dragMe');
|
||||
const leftSide = document.getElementById('resizableArticle');
|
||||
//const leftSide = document.querySelector('.chat-article');
|
||||
const container = document.getElementById('chatMain');
|
||||
|
||||
if (resizer && leftSide && container) {
|
||||
resizer.addEventListener('pointerdown', function(e) {
|
||||
document.body.classList.add('resizing');
|
||||
e.preventDefault();
|
||||
|
||||
const startX = e.clientX;
|
||||
const startWidth = leftSide.getBoundingClientRect().width;
|
||||
const containerWidth = container.getBoundingClientRect().width;
|
||||
|
||||
//document.body.style.cursor = 'col-resize';
|
||||
//leftSide.style.userSelect = 'none';
|
||||
document.body.classList.add('resizing');
|
||||
|
||||
function onPointerMove(e) {
|
||||
const deltaX = e.clientX - startX;
|
||||
let newWidthPercent = ((startWidth + deltaX) / containerWidth) * 100;
|
||||
|
||||
newWidthPercent = Math.max(15, Math.min(75, newWidthPercent));
|
||||
|
||||
leftSide.style.flexBasis = newWidthPercent + '%';
|
||||
}
|
||||
|
||||
function onPointerUp() {
|
||||
document.removeEventListener('pointermove', onPointerMove);
|
||||
document.removeEventListener('pointerup', onPointerUp);
|
||||
//document.body.style.removeProperty('cursor');
|
||||
document.body.classList.remove('resizing');
|
||||
leftSide.style.removeProperty('user-select');
|
||||
}
|
||||
|
||||
document.addEventListener('pointermove', onPointerMove);
|
||||
document.addEventListener('pointerup', onPointerUp);
|
||||
});
|
||||
}
|
||||
|
||||
const systemInput = document.getElementById('system-prompt-input');
|
||||
|
||||
if (systemInput) {
|
||||
// Funktion zur Höhenanpassung
|
||||
const autoGrow = (el) => {
|
||||
el.style.height = "auto";
|
||||
el.style.height = el.scrollHeight + "px";
|
||||
};
|
||||
|
||||
// Beim Tippen mitwachsen
|
||||
systemInput.addEventListener('input', () => autoGrow(systemInput));
|
||||
|
||||
// Initial beim Laden anpassen (falls schon Text drin steht)
|
||||
autoGrow(systemInput);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
let isEditingSystemPrompt = false;
|
||||
const systemInput = document.getElementById('system-prompt-input');
|
||||
const systemContainer = document.getElementById('system-prompt-container');
|
||||
|
||||
|
||||
|
||||
if (systemInput) {
|
||||
systemInput.addEventListener('focus', () => {
|
||||
if (window.innerWidth < 600) {
|
||||
isEditingSystemPrompt = true;
|
||||
systemContainer.classList.add('mobile-edit-mode');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
});
|
||||
|
||||
const exitEditMode = () => {
|
||||
isEditingSystemPrompt = false;
|
||||
systemContainer.classList.remove('mobile-edit-mode');
|
||||
document.body.style.overflow = '';
|
||||
|
||||
// Einmaliges Auto-Grow nach dem Schließen
|
||||
systemInput.style.height = "auto";
|
||||
systemInput.style.height = systemInput.scrollHeight + "px";
|
||||
};
|
||||
|
||||
systemInput.addEventListener('blur', () => {
|
||||
// Delay, um dem Klick auf den Button Vorrang zu geben
|
||||
setTimeout(exitEditMode, 200);
|
||||
});
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
:root {
|
||||
/* --- Hell-Modus --- */
|
||||
--bg-body: #ffffff;
|
||||
--bg-section-alt: #f8f9fa;
|
||||
--bg-section-alt: #ddddec;
|
||||
--text-main: #333333;
|
||||
--text-muted: #666666;
|
||||
--header-bg: rgba(255, 255, 255, 0.95);
|
||||
@@ -15,7 +15,7 @@
|
||||
--border-color: #eeeeee;
|
||||
|
||||
/* Hero & Akzente */
|
||||
--accent: #667eea;
|
||||
--accent: #6072c5;
|
||||
--accent-text: #ffffff;
|
||||
--accent-dark: #764ba2;
|
||||
--hero-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
@@ -42,7 +42,7 @@
|
||||
--border-color: #2d334a;
|
||||
|
||||
/* Hero & Akzente (Im Darkmode etwas entspannter) */
|
||||
--accent: #39348c;
|
||||
--accent: #6e6abc;
|
||||
--accent-text: #ffffff;
|
||||
--accent-dark: #3d4593;
|
||||
--hero-gradient: linear-gradient(135deg, #1e1b4b 0%, #312e81 100%);
|
||||
@@ -212,25 +212,13 @@ nav {
|
||||
}
|
||||
|
||||
|
||||
.hamburger {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.hamburger span {
|
||||
display: block;
|
||||
width: 25px;
|
||||
height: 3px;
|
||||
background-color: var(--accent);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/* ==========================================
|
||||
3. HERO SECTION
|
||||
========================================== */
|
||||
.hero {
|
||||
min-height: 100vh;
|
||||
/*height: calc(100vh - 45px);
|
||||
margin-top: 45px;*/
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -257,6 +245,7 @@ nav {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
|
||||
/* ==========================================
|
||||
4. BUTTONS & INPUTS (Neu gestylt)
|
||||
========================================== */
|
||||
@@ -317,23 +306,7 @@ nav {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
/* Eingabefeld (Chat) */
|
||||
#user-input {
|
||||
flex: 1;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid var(--border-input);
|
||||
border-radius: 8px;
|
||||
background: var(--bg-input);
|
||||
color: var(--text-input);
|
||||
font-size: 1rem;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
#user-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent);
|
||||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
|
||||
}
|
||||
|
||||
/* ==========================================
|
||||
5. SECTIONS & CARDS
|
||||
@@ -356,7 +329,7 @@ h2 {
|
||||
}
|
||||
|
||||
.text {
|
||||
max-width: 800px;
|
||||
/*max-width: 1200px;*/
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
font-size: 1.1rem;
|
||||
@@ -364,101 +337,8 @@ h2 {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Hier ist die wichtige Änderung für das Nebeneinanderstehen */
|
||||
.projects, .tech-features {
|
||||
display: grid;
|
||||
/* Erzeugt so viele Spalten wie nebeneinander passen (mind. 300px breit) */
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 2rem;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.project-card, .tech-card {
|
||||
background-color: var(--card-bg);
|
||||
border-radius: 15px;
|
||||
padding: 2.5rem;
|
||||
box-shadow: 0 10px 30px var(--card-shadow);
|
||||
border: 1px solid var(--border-color);
|
||||
text-align: center;
|
||||
transition: transform 0.3s ease, background-color 0.3s;
|
||||
|
||||
/* Flexbox Korrektur */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start; /* Schiebt alles nach oben */
|
||||
height: 100%; /* Sorgt dafür, dass alle Karten in einer Reihe gleich hoch sind */
|
||||
}
|
||||
|
||||
.project-card h3, .tech-card h3 {
|
||||
color: var(--accent);
|
||||
margin-bottom: 1.2rem; /* Abstand zwischen Überschrift und Text */
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.project-card p, .tech-card p {
|
||||
color: var(--text-muted);
|
||||
line-height: 1.6;
|
||||
margin-bottom: 0; /* Verhindert unnötigen Platz nach unten */
|
||||
}
|
||||
|
||||
.project-card:hover, .tech-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
|
||||
/* Chat-Container Hintergrund korrigiert */
|
||||
.chat-container {
|
||||
max-width: 800px;
|
||||
margin: 100px auto 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 140px);
|
||||
background: var(--card-bg); /* Nutzt Card Background Variable */
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 10px 25px var(--card-shadow);
|
||||
border: 1px solid var(--border-color);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#chat-messages {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.message {
|
||||
max-width: 80%;
|
||||
padding: 12px 16px;
|
||||
border-radius: 15px;
|
||||
line-height: 1.5;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.user-message {
|
||||
align-self: flex-end;
|
||||
background-color: var(--accent);
|
||||
color: var(--accent-text);
|
||||
border-bottom-right-radius: 5px;
|
||||
}
|
||||
|
||||
.ai-message {
|
||||
align-self: flex-start;
|
||||
background-color: var(--bg-section-alt);
|
||||
color: var(--text-main);
|
||||
border-bottom-left-radius: 5px;
|
||||
}
|
||||
|
||||
.chat-input-area {
|
||||
padding: 20px;
|
||||
background-color: var(--card-bg);
|
||||
border-top: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* ==========================================
|
||||
6. FOOTER
|
||||
@@ -486,22 +366,35 @@ footer {
|
||||
/* ==========================================
|
||||
7. RESPONSIVENESS
|
||||
========================================== */
|
||||
/* --- Standard: Logo kurz ausblenden, lang anzeigen --- */
|
||||
|
||||
/* Standard für Desktop: Beide sichtbar */
|
||||
|
||||
.hamburger {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.hamburger span {
|
||||
display: block;
|
||||
width: 25px;
|
||||
height: 3px;
|
||||
background-color: var(--accent);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
|
||||
.logo-short { display: none;}
|
||||
.logo-full { display: inline;}
|
||||
|
||||
@media (max-width: 950px) {
|
||||
.hamburger { display: flex; }
|
||||
.nav-links {
|
||||
position: absolute;
|
||||
top: 100%; left: 0; width: 100%;
|
||||
background-color: var(--header-bg);
|
||||
flex-direction: column;
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.4s ease;
|
||||
}
|
||||
.nav-links.mobile-open { max-height: 300px; }
|
||||
.nav-links li { border-bottom: 1px solid var(--border-color); }
|
||||
/*.nav-links a { padding: 0.1rem; display: block; }*/
|
||||
.logo-full {display: none;}
|
||||
.logo-short {display: inline;}
|
||||
.hero h1 { font-size: 2.2rem; }
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -8,27 +8,6 @@ function toggleHamburger() {
|
||||
hamburger.classList.toggle('active');
|
||||
}
|
||||
|
||||
// Initialisierung
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// ... dein bestehender Code ...
|
||||
const savedTheme = localStorage.getItem('theme') || 'auto';
|
||||
applyTheme(savedTheme);
|
||||
|
||||
// Hamburger Menü schließen bei Klick auf einen Link
|
||||
const navLinksContainer = document.getElementById('navLinks');
|
||||
const navLinks = navLinksContainer.querySelectorAll('a');
|
||||
const hamburger = document.querySelector('.hamburger');
|
||||
|
||||
navLinks.forEach(link => {
|
||||
link.addEventListener('click', () => {
|
||||
// Prüfen, ob das Menü gerade offen ist
|
||||
if (navLinksContainer.classList.contains('mobile-open')) {
|
||||
navLinksContainer.classList.remove('mobile-open');
|
||||
hamburger.classList.remove('active');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/* ==========================================
|
||||
DROPDOWN LOGIC (Vereinheitlicht)
|
||||
@@ -119,44 +98,9 @@ function applyTheme(theme) {
|
||||
}
|
||||
}
|
||||
|
||||
async function startDemoChat() {
|
||||
try {
|
||||
const response = await fetch('/api/v1/chats/new', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
// WICHTIG: Wenn dein Nginx den Token nicht setzt,
|
||||
// muss er hier hin: 'Authorization': 'Bearer DEIN_TOKEN'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
"title": "Demo Chat",
|
||||
"chat": { "model": "arcee-ai/trinity-large-preview:free"}
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
console.error('API Fehlerdetails:', errorData);
|
||||
throw new Error(`Fehler: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const sessionId = data.id;
|
||||
|
||||
// --- DER KORREKTE DYNAMISCHE PFAD ---
|
||||
|
||||
// 1. Hole den aktuellen Sprachpfad (z.B. "/de/", "/en/")
|
||||
// split('/') macht aus "/de/about/" -> ["", "de", "about", ""]
|
||||
const pathSegments = window.location.pathname.split('/');
|
||||
const lang = pathSegments[1] || 'de'; // Fallback auf "de", falls kein Pfad da ist
|
||||
|
||||
// 2. Leite auf den Pfad MIT Sprache und Session-ID um
|
||||
// Das ergibt dann z.B. /de/chat/?session=...
|
||||
window.location.href = `/${lang}/chat/?session=${sessionId}`;
|
||||
//window.location.href = `/de/chat/?session=${sessionId}`;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Starten:', error);
|
||||
alert('Demo-Chat konnte nicht gestartet werden.');
|
||||
}
|
||||
}
|
||||
|
||||
73
static/index.css
Normal file
73
static/index.css
Normal file
@@ -0,0 +1,73 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* Hier ist die wichtige Änderung für das Nebeneinanderstehen */
|
||||
.projects, .tech-features {
|
||||
display: grid;
|
||||
/* Erzeugt so viele Spalten wie nebeneinander passen (mind. 300px breit) */
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 2rem;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.project-card, .tech-card {
|
||||
background-color: var(--card-bg);
|
||||
border-radius: 15px;
|
||||
padding: 2.5rem;
|
||||
box-shadow: 0 10px 30px var(--card-shadow);
|
||||
border: 1px solid var(--border-color);
|
||||
text-align: center;
|
||||
transition: transform 0.3s ease, background-color 0.3s;
|
||||
|
||||
/* Flexbox Korrektur */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start; /* Schiebt alles nach oben */
|
||||
height: 100%; /* Sorgt dafür, dass alle Karten in einer Reihe gleich hoch sind */
|
||||
}
|
||||
|
||||
.project-card h3, .tech-card h3 {
|
||||
color: var(--accent);
|
||||
margin-bottom: 1.2rem; /* Abstand zwischen Überschrift und Text */
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.project-card p, .tech-card p {
|
||||
color: var(--text-muted);
|
||||
line-height: 1.6;
|
||||
margin-bottom: 0; /* Verhindert unnötigen Platz nach unten */
|
||||
}
|
||||
|
||||
.project-card:hover, .tech-card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
@media (max-width: 950px) {
|
||||
.hamburger {
|
||||
display: flex !important; /* Hamburger nur auf Chat-Mobile zeigen */
|
||||
}
|
||||
.nav-links {
|
||||
position: absolute;
|
||||
top: 100%; left: 0; width: 100%;
|
||||
background-color: var(--header-bg);
|
||||
flex-direction: column;
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.4s ease;
|
||||
}
|
||||
.nav-links li { border-bottom: 1px solid var(--border-color); }
|
||||
|
||||
.nav-links.mobile-open {
|
||||
display: flex !important;
|
||||
position: absolute;
|
||||
max-height: 300px;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
background: var(--header-bg);
|
||||
width: 200px;
|
||||
flex-direction: column;
|
||||
box-shadow: 0 5px 15px var(--card-shadow);
|
||||
}
|
||||
}
|
||||
72
static/index.js
Normal file
72
static/index.js
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
async function startDemoChat() {
|
||||
try {
|
||||
const response = await fetch('/api/v1/chats/new', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
// WICHTIG: Wenn dein Nginx den Token nicht setzt,
|
||||
// muss er hier hin: 'Authorization': 'Bearer DEIN_TOKEN'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
"title": "Demo Chat",
|
||||
"chat": {
|
||||
//"title": "Neuer Chat",
|
||||
//"models": ["trinity-test"], // WICHTIG: Es ist ein Array!
|
||||
//"history": { "messages": {}, "children": {} }
|
||||
"model":
|
||||
//"arcee-ai/trinity-large-preview:free"
|
||||
"trinity-test"
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
console.error('API Fehlerdetails:', errorData);
|
||||
throw new Error(`Fehler: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const sessionId = data.id;
|
||||
|
||||
// --- DER KORREKTE DYNAMISCHE PFAD ---
|
||||
|
||||
// 1. Hole den aktuellen Sprachpfad (z.B. "/de/", "/en/")
|
||||
// split('/') macht aus "/de/about/" -> ["", "de", "about", ""]
|
||||
const pathSegments = window.location.pathname.split('/');
|
||||
const lang = pathSegments[1] || 'de'; // Fallback auf "de", falls kein Pfad da ist
|
||||
|
||||
// 2. Leite auf den Pfad MIT Sprache und Session-ID um
|
||||
// Das ergibt dann z.B. /de/chat/?session=...
|
||||
window.location.href = `/${lang}/chat/?session=${sessionId}`;
|
||||
//window.location.href = `/de/chat/?session=${sessionId}`;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Starten:', error);
|
||||
alert('Demo-Chat konnte nicht gestartet werden.');
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// Initialisierung
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// ... dein bestehender Code ...
|
||||
const savedTheme = localStorage.getItem('theme') || 'auto';
|
||||
applyTheme(savedTheme);
|
||||
|
||||
// Hamburger Menü schließen bei Klick auf einen Link
|
||||
const navLinksContainer = document.getElementById('navLinks');
|
||||
const navLinks = navLinksContainer.querySelectorAll('a');
|
||||
const hamburger = document.querySelector('.hamburger');
|
||||
|
||||
navLinks.forEach(link => {
|
||||
link.addEventListener('click', () => {
|
||||
// Prüfen, ob das Menü gerade offen ist
|
||||
if (navLinksContainer.classList.contains('mobile-open')) {
|
||||
navLinksContainer.classList.remove('mobile-open');
|
||||
hamburger.classList.remove('active');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user