chat implementiert

This commit is contained in:
2026-03-01 20:21:35 +00:00
parent 277bdc78d1
commit fe3a25fa3a
13 changed files with 360 additions and 29 deletions

View File

@@ -1,13 +1,19 @@
# Stage 1: Hugo bauen # Stage 1: Hugo bauen
FROM klakegg/hugo:alpine AS builder FROM klakegg/hugo:alpine AS builder
# WICHTIG: Kopiere das gesamte Verzeichnis, nicht nur den Inhalt # Kopiere das gesamte Verzeichnis
COPY . /src COPY . /src
# Bauen der Seite # Bauen der Seite
RUN hugo RUN hugo
# Stage 2: Nginx ausliefern # Stage 2: Nginx ausliefern
FROM nginx:alpine FROM nginx:alpine
# Kopieren der generierten Seite
# 1. Nginx Konfigurationsdatei kopieren (wichtig für Reverse Proxy)
#COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY default.conf.template /etc/nginx/conf.d/default.conf.template
# 2. Kopieren der generierten Seite
COPY --from=builder /src/public /usr/share/nginx/html COPY --from=builder /src/public /usr/share/nginx/html
# Nginx läuft auf Port 80 # Nginx läuft auf Port 80
EXPOSE 80 EXPOSE 80

View File

@@ -7,7 +7,10 @@ services:
HUGO_BASEURL: https://${DOMAIN_MAIN}/ # Nimmt den Wert aus deiner .env HUGO_BASEURL: https://${DOMAIN_MAIN}/ # Nimmt den Wert aus deiner .env
container_name: webpage container_name: webpage
restart: unless-stopped restart: unless-stopped
#environment: command: /bin/sh -c "envsubst '\$$TOKEN' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf && exec nginx -g 'daemon off;'"
#command: /bin/sh -c "envsubst < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf && exec nginx -g 'daemon off;'"
environment:
- TOKEN=${OPEN_WEBUI_TOKEN}
# - TITLE=${TITLE} # - TITLE=${TITLE}
# - DOMAIN_MAIN=${DOMAIN_MAIN} # - DOMAIN_MAIN=${DOMAIN_MAIN}
# - NGINX_ENVSUBST_OUTPUT_DIR=/usr/share/nginx/html # - NGINX_ENVSUBST_OUTPUT_DIR=/usr/share/nginx/html

View File

@@ -1,7 +1,7 @@
baseURL = "/" baseURL = "/"
languageCode = "de" languageCode = "de"
defaultContentLanguage = "de" defaultContentLanguage = "de"
defaultContentLanguageInSubdir = true defaultContentLanguageInSubdir = false
[languages] [languages]
[languages.de] [languages.de]

8
content/de/chat.md Normal file
View File

@@ -0,0 +1,8 @@
---
title: "AI Demo Lab"
flag: "🇩🇪"
welcome_message: "Hallo! Ich bin die Demo-KI von Ground Zero Lab. Wie kann ich dir helfen?"
placeholder: "Nachricht eingeben..."
sent_btn: Senden
layout: "chat"
---

8
content/en/chat.md Normal file
View File

@@ -0,0 +1,8 @@
---
title: "AI Demo Lab"
flag: "🇬🇧"
welcome_message: "Hello! I am the demo AI from Ground Zero Lab. How can I help you?"
placeholder: "Enter message..."
sent_btn: Send
layout: "chat"
---

8
content/es/chat.md Normal file
View File

@@ -0,0 +1,8 @@
---
title: "AI Demo Lab"
flag: "🇪🇸"
welcome_message: "Здравствуйте! Я демонстрационный ИИ Ground Zero Lab. Чем могу помочь?"
placeholder: "Введите сообщение..."
sent_btn: Отправить
layout: "chat"
---

8
content/fr/chat.md Normal file
View File

@@ -0,0 +1,8 @@
---
title: "AI Demo Lab"
flag: "🇫🇷"
welcome_message: "Bonjour ! Je suis l'IA de démonstration de Ground Zero Lab. Comment puis-je vous aider ?"
placeholder: "Entrez un message..."
sent_btn: Envoyer
layout: "chat"
---

8
content/ru/chat.md Normal file
View File

@@ -0,0 +1,8 @@
---
title: "AI Demo Lab"
flag: "🇷🇺"
welcome_message: "Здравствуйте! Я демонстрационный ИИ Ground Zero Lab. Чем могу помочь?"
placeholder: "Введите сообщение..."
sent_btn: Отправить
layout: "chat"
---

44
default.conf.template Normal file
View File

@@ -0,0 +1,44 @@
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# 1. Deine statischen Hugo-Dateien
location / {
try_files $uri $uri/ =404;
}
# 2. Der API-Proxy (für den Button-Klick)
location /api/ {
proxy_pass http://open-webui:8080/api/;
proxy_set_header Host $host;
proxy_set_header Authorization "Bearer ${TOKEN}";
}
# 3. Der Chat-Proxy (für das Iframe)
location /c/ {
proxy_pass http://open-webui:8080/c/;
proxy_set_header Host $host;
proxy_set_header Authorization "Bearer ${TOKEN}";
# Wichtig, damit das Iframe nicht blockiert wird
proxy_hide_header X-Frame-Options;
proxy_hide_header Content-Security-Policy;
}
# 4. DIE NEUEN PFADE (Damit das Design/Layout lädt)
location /_app/ {
proxy_pass http://open-webui:8080/_app/;
proxy_set_header Host $host;
}
location /assets/ {
proxy_pass http://open-webui:8080/assets/;
proxy_set_header Host $host;
}
location /static/ {
proxy_pass http://open-webui:8080/static/;
proxy_set_header Host $host;
}
}

108
layouts/_default/chat.html Normal file
View File

@@ -0,0 +1,108 @@
<!DOCTYPE html>
<html lang="{{ .Lang }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Demo Chat | {{ .Title }}</title>
<link rel="stylesheet" href="{{ "style.css" | relURL }}">
</head>
<body>
<header>
<nav class="container">
<a href="/" class="logo">🏗 Ground Zero Lab</a>
<div class="nav-right">
<div class="theme-header-btn">
<button class="lang-btn" id="langBtn">
{{ .Params.flag }}
</button>
<div class="lang-dropdown" id="langDropdown">
{{ range .Site.Languages }}
<a href="#" class="theme-option" data-lang="{{ .Lang }}">
{{ if eq .Lang "de" }}🇩🇪 Deutsch{{ end }}
{{ if eq .Lang "en" }}🇬🇧 English{{ end }}
{{ if eq .Lang "fr" }}🇫🇷 Français{{ end }}
{{ if eq .Lang "es" }}🇪🇸 Español{{ end }}
{{ if eq .Lang "ru" }}🇷🇺 Русский{{ end }}
</a>
{{ end }}
</div>
</div>
<div class="theme-header-btn">
<button class="theme-btn" id="themeBtn" title="Design wählen">
<span class="theme-icon-active">🌓</span>
</button>
<div class="theme-dropdown" id="themeDropdown">
<button class="theme-option" data-theme="setlight">☀️ Hell</button>
<button class="theme-option" data-theme="setdark">🌙 Dunkel</button>
<button class="theme-option" data-theme="setauto">🌓 Auto</button>
</div>
</div>
</div>
</nav>
</header>
<main class="chat-container">
<div id="chat-messages">
<div class="message ai-message">{{ .Params.welcome_message }}</div>
</div>
<div class="chat-input-area">
<input type="text" id="user-input" placeholder="{{ .Params.placeholder }}" onkeypress="if(event.key === 'Enter') sendMessage()">
<button class="send-btn" onclick="sendMessage()">{{ .Params.sent_btn }} </button>
</div>
</main>
<script>
const chatMessages = document.getElementById('chat-messages');
const userInput = document.getElementById('user-input');
async function sendMessage() {
const text = userInput.value.trim();
if (!text) return;
// User Nachricht anzeigen
appendMessage('user', text);
userInput.value = '';
try {
// Hier sprechen wir deinen Nginx /api/ Proxy an
const response = await fetch('/api/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// Der Token wird vom Nginx Server-seitig gesetzt,
// oder du sendest ihn hier mit, falls Nginx ihn nicht fix setzt.
},
body: JSON.stringify({
"model": "arcee-ai/trinity-large-preview:free",
"messages": [{"role": "user", "content": text}],
"stream": false // Erstmal ohne Streaming für einfachere Logik
})
});
const data = await response.json();
const aiText = data.choices[0].message.content;
appendMessage('ai', aiText);
} catch (err) {
appendMessage('ai', 'Fehler: Verbindung zum Server fehlgeschlagen.');
console.error(err);
}
}
function appendMessage(role, text) {
const msgDiv = document.createElement('div');
msgDiv.className = `message ${role}-message`;
msgDiv.innerText = text;
chatMessages.appendChild(msgDiv);
chatMessages.scrollTop = chatMessages.scrollHeight;
}
</script>
<script src="/script.js"></script>
</body>
</html>

View File

@@ -105,7 +105,7 @@
<div class="demo-section"> <div class="demo-section">
<p class="text">{{ .Params.demo_text }}</p> <p class="text">{{ .Params.demo_text }}</p>
<div class="demo-buttons"> <div class="demo-buttons">
<a href="#" class="demo-button">🚧 {{ .Params.demo_btn1 }}</a> <button onclick="startDemoChat()" >🚧 {{ .Params.demo_btn1 }}</button>
<a href="#" class="demo-button">🚧 {{ .Params.demo_btn2 }}</a> <a href="#" class="demo-button">🚧 {{ .Params.demo_btn2 }}</a>
<a href="#" class="demo-button">🚧 {{ .Params.demo_btn3 }}</a> <a href="#" class="demo-button">🚧 {{ .Params.demo_btn3 }}</a>
</div> </div>

View File

@@ -119,3 +119,44 @@ 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.');
}
}

View File

@@ -258,23 +258,54 @@ nav {
} }
/* ========================================== /* ==========================================
4. BUTTONS 4. BUTTONS & INPUTS (Neu gestylt)
========================================== */ ========================================== */
.cta-button {
display: inline-block; /* Basis Styling für alle Buttons (CTA, Demo, Send) */
background-color: var(--accent); .cta-button,
color: var(--accent-text); .send-btn,
padding: 1rem 2rem; .demo-button,
text-decoration: none; .demo-buttons button {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.8rem 1.8rem;
border-radius: 50px; border-radius: 50px;
font-weight: bold; text-decoration: none;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); font-weight: 600;
transition: all 0.3s ease; font-size: 0.95rem;
transition: all 0.2s ease;
cursor: pointer;
border: none;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
} }
.cta-button:hover { /* Haupt-Buttons (Akzentfarbe) */
transform: translateY(-2px); .cta-button,
.send-btn,
.demo-buttons button {
background-color: var(--accent);
color: var(--accent-text);
}
.cta-button:hover,
.send-btn:hover,
.demo-buttons button:hover {
background-color: var(--accent-dark); background-color: var(--accent-dark);
transform: translateY(-2px);
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.1);
}
/* Sekundär-Buttons (Grau/Alt) */
.demo-button {
background-color: var(--bg-section-alt);
color: var(--text-main);
border: 1px solid var(--border-color);
}
.demo-button:hover {
background-color: var(--border-color);
transform: translateY(-2px);
} }
.demo-buttons { .demo-buttons {
@@ -286,18 +317,22 @@ nav {
margin-top: 2rem; margin-top: 2rem;
} }
.demo-button { /* Eingabefeld (Chat) */
background-color: var(--accent); #user-input {
color: var(--accent-text); flex: 1;
padding: 0.6rem 1.4rem; padding: 12px 16px;
text-decoration: none; border: 1px solid var(--border-input);
border-radius: 25px; border-radius: 8px;
transition: all 0.3s ease; background: var(--bg-input);
color: var(--text-input);
font-size: 1rem;
transition: all 0.2s ease;
} }
.demo-button:hover { #user-input:focus {
background-color: var(--accent-dark); outline: none;
transform: translateY(-2px); border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
} }
/* ========================================== /* ==========================================
@@ -308,7 +343,7 @@ section {
background-color: var(--bg-body); background-color: var(--bg-body);
} }
.section-dark { .section-dark .chat-input-area{
background-color: var(--bg-section-alt); background-color: var(--bg-section-alt);
} }
@@ -371,6 +406,60 @@ h2 {
} }
/* 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 6. FOOTER
========================================== */ ========================================== */