Files
Assistent/templates/chat.html
SimolZimol c56f3f8648 modified: app.py
new file:   templates/agb.html
	modified:   templates/chat.html
	modified:   templates/datenschutz.html
	modified:   templates/impressum.html
	modified:   templates/kontakt.html
	modified:   templates/landing.html
2025-06-19 19:09:58 +02:00

476 lines
17 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>AI Vertriebsassistent Tech Demo für Nicolaisen Casing & Packaging GmbH</title>
<style>
body {
background: #23272f;
font-family: 'Segoe UI', Arial, sans-serif;
margin: 0;
padding: 0;
color: #e3e3e3;
}
#container {
max-width: 700px;
margin: 40px auto 0 auto;
background: #181a20;
border-radius: 12px;
box-shadow: 0 4px 24px rgba(0,0,0,0.18);
padding: 0 0 16px 0;
min-height: 80vh;
display: flex;
flex-direction: column;
}
#header {
padding: 24px 24px 0 24px;
text-align: center;
}
#header h1 {
margin: 0 0 8px 0;
font-size: 2em;
color: #00aaff;
letter-spacing: 1px;
}
#header p {
margin: 0;
color: #b0b6c3;
font-size: 1.1em;
}
#chat {
flex: 1;
padding: 32px 24px 16px 24px;
overflow-y: auto;
}
.msg {
display: flex;
margin-bottom: 18px;
}
.user {
justify-content: flex-end;
}
.ai {
justify-content: flex-start;
}
.bubble {
max-width: 75%;
padding: 14px 18px;
border-radius: 16px;
font-size: 1.05em;
line-height: 1.5;
box-shadow: 0 2px 8px rgba(0,0,0,0.10);
position: relative;
word-break: break-word;
}
.user .bubble {
background: linear-gradient(135deg, #0078fe 60%, #005bb5 100%);
color: #fff;
border-bottom-right-radius: 4px;
}
.ai .bubble {
background: #23272f;
color: #e3e3e3;
border-bottom-left-radius: 4px;
border: 1px solid #2c313a;
}
#input-area {
display: flex;
padding: 0 24px;
margin-top: 8px;
}
#input {
flex: 1;
padding: 12px;
border-radius: 8px;
border: 1px solid #353b48;
font-size: 1em;
outline: none;
margin-right: 8px;
background: #23272f;
color: #e3e3e3;
}
#send-btn {
background: #00aaff;
color: #fff;
border: none;
border-radius: 8px;
padding: 0 24px;
font-size: 1em;
cursor: pointer;
transition: background 0.2s;
}
#send-btn:hover {
background: #0078fe;
}
.think-toggle {
background: none;
border: none;
color: #00aaff;
cursor: pointer;
font-size: 1em;
margin-top: 10px;
margin-bottom: 2px;
padding: 0;
display: flex;
align-items: center;
gap: 6px;
transition: color 0.2s;
}
.think-toggle:hover {
color: #fff;
}
.think-section {
display: none;
background: linear-gradient(90deg, #23272f 80%, #00aaff22 100%);
color: #b0e0ff;
border-left: 4px solid #00aaff;
border-radius: 8px;
margin-top: 8px;
margin-bottom: 2px;
padding: 12px 16px;
font-size: 1em;
white-space: pre-line;
box-shadow: 0 2px 8px #00aaff22;
animation: fadeIn 0.3s;
position: relative;
}
.think-section::before {
content: "💡 Gedanken der KI";
display: block;
font-size: 0.95em;
color: #7fd7ff;
margin-bottom: 6px;
font-weight: bold;
letter-spacing: 0.5px;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(-8px);}
to { opacity: 1; transform: translateY(0);}
}
#footer {
text-align: center;
color: #6c7380;
font-size: 0.95em;
margin-top: 18px;
margin-bottom: 6px;
letter-spacing: 0.5px;
}
#clear-btn {
background: #353b48;
color: #fff;
border: none;
border-radius: 8px;
padding: 6px 18px;
font-size: 0.98em;
cursor: pointer;
margin-left: 10px;
margin-bottom: 8px;
transition: background 0.2s;
}
#clear-btn:hover {
background: #ff4d4d;
color: #fff;
}
#tools-bar {
display: flex;
gap: 16px;
justify-content: flex-start;
padding: 0 24px 12px 24px;
background: transparent;
}
.tool-btn {
background: #23272f;
color: #00aaff;
border: 1px solid #00aaff;
border-radius: 8px;
padding: 8px 18px;
font-size: 1em;
cursor: pointer;
margin-top: 8px;
transition: background 0.2s, color 0.2s;
display: flex;
align-items: center;
gap: 6px;
}
.tool-btn:hover {
background: #00aaff;
color: #fff;
}
#export-dialog {
position: fixed;
left: 0; top: 0; width: 100vw; height: 100vh;
background: rgba(30,40,60,0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.export-modal {
background: #181a20;
border-radius: 12px;
box-shadow: 0 4px 24px rgba(0,0,0,0.18);
padding: 32px 32px 24px 32px;
min-width: 340px;
max-width: 90vw;
color: #e3e3e3;
border: 1px solid #00aaff;
text-align: center;
}
.export-modal input {
background: #23272f;
color: #e3e3e3;
border: 1px solid #353b48;
border-radius: 6px;
padding: 10px;
margin-bottom: 10px;
}
.export-modal button {
background: #00aaff;
color: #fff;
border: none;
border-radius: 8px;
padding: 8px 18px;
font-size: 1em;
margin: 8px 6px 0 6px;
cursor: pointer;
transition: background 0.2s;
}
.export-modal button:hover {
background: #0078fe;
}
@media (max-width: 600px) {
#container { max-width: 100%; border-radius: 0; }
#chat { padding: 16px 6px 8px 6px; }
#input-area { padding: 0 6px; }
#header { padding: 16px 6px 0 6px; }
}
</style>
</head>
<body>
<div id="container">
<div id="header">
<h1>AI Vertriebsassistent Tech Demo</h1>
<p>Entwickelt von Simon Giehl für Nicolaisen Casing & Packaging GmbH<br>
<span style="color:#ffb300;font-weight:bold;">Nur für interne Testzwecke!</span></p>
</div>
<div id="chat"></div>
<div id="input-area">
<input id="input" type="text" placeholder="Nachricht eingeben..." autocomplete="off">
<button id="send-btn" onclick="send()">Senden</button>
</div>
<div id="tools-bar">
<button class="tool-btn" onclick="showExportDialog()">
<span style="font-size:1.2em;">📊</span> Export/Tabellen-Tool
</button>
<button class="tool-btn" onclick="clearChat()">
<span style="font-size:1.2em;">🧹</span> Kontext löschen
</button>
</div>
<div id="export-dialog" style="display:none;">
<div class="export-modal">
<b>Export/Tabellen-Tool</b><br>
<input id="export-query" type="text" placeholder="z.B. alle Kunden mit Kaliber 58" style="width:70%;margin:8px 0;">
<button onclick="exportTable()">Exportieren als CSV</button>
<button onclick="hideExportDialog()">Schließen</button>
<div id="export-result" style="margin-top:10px;"></div>
</div>
</div>
<div id="footer">
&copy; 2025 Simon Giehl Tech Demo für Nicolaisen Casing & Packaging GmbH.
</div>
</div>
<script>
// Chatverlauf im Local Storage speichern und laden
function saveChatHistory() {
const chat = document.getElementById('chat');
const history = [];
chat.querySelectorAll('.msg').forEach(msgDiv => {
const role = msgDiv.classList.contains('user') ? 'user' : 'ai';
const bubble = msgDiv.querySelector('.bubble');
let text = '';
let thoughts = '';
if (bubble) {
// Extrahiere Antworttext
const spans = bubble.querySelectorAll('span');
if (spans.length > 0) text = spans[spans.length-1].innerText;
// Extrahiere Gedanken, falls vorhanden
const thinkDiv = bubble.querySelector('.think-section');
if (thinkDiv) thoughts = thinkDiv.innerText;
}
history.push({role, text, thoughts});
});
localStorage.setItem('chatHistory', JSON.stringify(history));
}
function loadChatHistory() {
const history = JSON.parse(localStorage.getItem('chatHistory') || '[]');
history.forEach(msg => addMessage(msg.role, msg.text, msg.thoughts));
}
function clearChat() {
document.getElementById('chat').innerHTML = '';
localStorage.removeItem('chatHistory');
}
function addMessage(role, text, thoughts) {
const chat = document.getElementById('chat');
const msgDiv = document.createElement('div');
msgDiv.className = 'msg ' + role;
let bubble = document.createElement('div');
bubble.className = 'bubble';
if (role === 'ai' && thoughts) {
// Button zum Ein-/Ausblenden der Gedanken
const toggleBtn = document.createElement('button');
toggleBtn.className = 'think-toggle';
toggleBtn.innerHTML = '<span style="font-size:1.2em;">💡</span> Gedanken anzeigen';
toggleBtn.onclick = function() {
thinkDiv.style.display = thinkDiv.style.display === 'block' ? 'none' : 'block';
toggleBtn.innerHTML = thinkDiv.style.display === 'block'
? '<span style="font-size:1.2em;">💡</span> Gedanken ausblenden'
: '<span style="font-size:1.2em;">💡</span> Gedanken anzeigen';
};
bubble.appendChild(toggleBtn);
// Gedankenbereich
const thinkDiv = document.createElement('div');
thinkDiv.className = 'think-section';
thinkDiv.textContent = thoughts; // <--- HIER textContent statt innerText
bubble.appendChild(thinkDiv);
}
// Antworttext
const textSpan = document.createElement('span');
textSpan.textContent = text; // <--- HIER textContent statt innerText
bubble.appendChild(textSpan);
msgDiv.appendChild(bubble);
chat.appendChild(msgDiv);
chat.scrollTop = chat.scrollHeight;
saveChatHistory();
}
function send() {
let input = document.getElementById('input');
let msg = input.value.trim();
if (!msg) return;
addMessage('user', msg);
input.value = '';
input.focus();
// AI-Stream-Message vorbereiten
const aiMsgDiv = document.createElement('div');
aiMsgDiv.className = 'msg ai';
const aiBubble = document.createElement('div');
aiBubble.className = 'bubble';
const aiText = document.createElement('span');
aiText.id = 'ai-stream';
aiBubble.appendChild(aiText);
aiMsgDiv.appendChild(aiBubble);
document.getElementById('chat').appendChild(aiMsgDiv);
document.getElementById('chat').scrollTop = document.getElementById('chat').scrollHeight;
fetch('/ask_stream', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({message: msg})
})
.then(response => {
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
function read() {
reader.read().then(({done, value}) => {
if (done) {
// Nach dem Stream: <think>-Bereich extrahieren und anzeigen
const {answer, thoughts} = splitThoughtsAndAnswer(buffer);
aiText.textContent = answer; // <--- HIER textContent statt innerText
if (thoughts) {
// Gedanken-Button und Bereich einfügen
const toggleBtn = document.createElement('button');
toggleBtn.className = 'think-toggle';
toggleBtn.innerHTML = '<span style="font-size:1.2em;">💡</span> Gedanken anzeigen';
const thinkDiv = document.createElement('div');
thinkDiv.className = 'think-section';
thinkDiv.textContent = thoughts; // <--- HIER textContent statt innerText
toggleBtn.onclick = function() {
thinkDiv.style.display = thinkDiv.style.display === 'block' ? 'none' : 'block';
toggleBtn.innerHTML = thinkDiv.style.display === 'block'
? '<span style="font-size:1.2em;">💡</span> Gedanken ausblenden'
: '<span style="font-size:1.2em;">💡</span> Gedanken anzeigen';
};
aiBubble.insertBefore(toggleBtn, aiText);
aiBubble.insertBefore(thinkDiv, aiText);
}
saveChatHistory();
return;
}
buffer += decoder.decode(value, {stream: true});
aiText.textContent = buffer; // <--- HIER textContent statt innerText
document.getElementById('chat').scrollTop = document.getElementById('chat').scrollHeight;
read();
});
}
read();
});
}
// Extrahiere <think>...</think> Bereich aus dem Text
function splitThoughtsAndAnswer(text) {
const thinkMatch = text.match(/<think>([\s\S]*?)<\/think>/i);
let thoughts = '';
let answer = text;
if (thinkMatch) {
thoughts = thinkMatch[1].trim();
answer = text.replace(thinkMatch[0], '').trim();
}
return {answer, thoughts};
}
// Enter-Taste zum Senden
document.getElementById('input').addEventListener('keydown', function(e) {
if (e.key === 'Enter') send();
});
// Beim Laden: Chatverlauf wiederherstellen
window.onload = loadChatHistory;
function showExportDialog() {
document.getElementById('export-dialog').style.display = 'flex';
}
function hideExportDialog() {
document.getElementById('export-dialog').style.display = 'none';
document.getElementById('export-result').innerHTML = '';
}
function exportTable() {
const query = document.getElementById('export-query').value;
fetch('/export', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({message: query})
})
.then(response => {
if (response.ok) {
return response.blob();
}
throw new Error('Export fehlgeschlagen');
})
.then(blob => {
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = "export.csv";
a.textContent = "CSV herunterladen";
document.getElementById('export-result').innerHTML = '';
document.getElementById('export-result').appendChild(a);
})
.catch(err => {
document.getElementById('export-result').textContent = err.message;
});
}
</script>
</body>
</html>