first commit

This commit is contained in:
team 1
2026-02-11 14:15:08 +01:00
parent a4742c2c38
commit aa7d362bc3
58 changed files with 9999 additions and 0 deletions

335
public/index_bckp.html Normal file
View File

@@ -0,0 +1,335 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>AI Agent</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Markdown + Sanitizer -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.0.6/dist/purify.min.js"></script>
<style>
:root {
--bg: #0f172a;
--panel: #020617;
--border: #334155;
--text: #e5e7eb;
--muted: #94a3b8;
--accent: #2563eb;
--user: #1e40af;
--assistant: #020617;
--danger: #dc2626;
}
body {
font-family: system-ui, sans-serif;
background: var(--bg);
color: var(--text);
margin: 0;
padding: 2rem;
}
.container {
max-width: 900px;
margin: 0 auto;
display: flex;
flex-direction: column;
height: calc(100vh - 4rem);
}
.header {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 1rem;
}
h1 {
font-size: 1.4rem;
margin: 0;
}
.header .spacer {
flex: 1;
}
.chat {
flex: 1;
overflow-y: auto;
padding: 1rem;
background: var(--panel);
border: 1px solid var(--border);
border-radius: 6px;
}
.message {
margin-bottom: 1.2rem;
}
.message.user {
text-align: right;
}
.message.user .bubble {
background: var(--user);
margin-left: auto;
}
.message.assistant .bubble {
background: var(--assistant);
border: 1px solid var(--border);
}
.bubble {
max-width: 80%;
padding: 0.75rem 1rem;
border-radius: 6px;
line-height: 1.5;
}
.bubble.loader {
font-style: italic;
color: var(--muted);
}
/* Markdown styling */
.bubble p { margin: 0.4rem 0; }
.bubble ul { padding-left: 1.2rem; }
.bubble code {
background: rgba(148, 163, 184, 0.15);
padding: 0.15rem 0.35rem;
border-radius: 4px;
font-family: ui-monospace, monospace;
}
.bubble pre {
background: #020617;
border: 1px solid #334155;
border-radius: 6px;
padding: 0.75rem;
overflow-x: auto;
}
.input-area {
margin-top: 1rem;
display: flex;
gap: 0.75rem;
}
textarea {
flex: 1;
resize: none;
min-height: 60px;
max-height: 120px;
padding: 0.75rem;
background: var(--panel);
color: var(--text);
border: 1px solid var(--border);
border-radius: 6px;
}
textarea:focus {
outline: none;
border-color: var(--accent);
}
button {
padding: 0.6rem 1rem;
background: var(--accent);
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button.abort { background: var(--danger); }
button.secondary {
background: transparent;
border: 1px solid var(--border);
color: var(--text);
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>mitho AI Agent</h1>
<div class="spacer"></div>
<button id="clear" class="secondary">Diesen Chat löschen</button>
</div>
<div id="chat" class="chat"></div>
<div class="input-area">
<textarea id="prompt" placeholder="Ask something…">gebe eine lister alle leistungen von mitho aus</textarea>
<button id="send">Send</button>
<button id="abort" class="abort" disabled>Abbrechen</button>
</div>
</div>
<script>
(() => {
const chatEl = document.getElementById('chat');
const promptEl = document.getElementById('prompt');
const sendBtn = document.getElementById('send');
const abortBtn = document.getElementById('abort');
const clearBtn = document.getElementById('clear');
let abort = false;
marked.setOptions({ breaks: true });
function renderMarkdown(text) {
console.log(text,marked.parse(text))
return DOMPurify.sanitize(marked.parse(text));
}
function renderStreaming(text) {
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
function normalizeMarkdown(text) {
return text
// HTML Entities entschärfen
.replace(/&amp;/g, '&')
// Füge Zeilenumbrüche vor und nach Markdown-Überschriften (###, #### etc.)
.replace(/\s*(#{1,6}.*?)\s*(?=#+|$)/g, '\n\n$1\n\n')
// Füge Zeilenumbrüche vor und nach Listenpunkten
.replace(/\s*-\s+/g, '\n- ')
// Füge Zeilenumbrüche vor horizontalen Linien (---)
.replace(/\s*---+\s*/g, '\n\n---\n\n')
// Entferne mehrfach aufeinanderfolgende Leerzeilen
.replace(/\n{3,}/g, '\n\n')
// Entferne führende und nachfolgende Leerzeichen
.trim();
}
function addMessage(role, html = '', extra = '') {
const msg = document.createElement('div');
msg.className = 'message ' + role;
const bubble = document.createElement('div');
bubble.className = 'bubble ' + extra;
bubble.innerHTML = html;
msg.appendChild(bubble);
chatEl.appendChild(msg);
chatEl.scrollTop = chatEl.scrollHeight;
return bubble;
}
function addLoader() {
return addMessage('assistant', 'AI is typing…', 'loader');
}
async function loadHistory() {
try {
const res = await fetch('/history');
if (!res.ok) return;
const messages = await res.json();
messages.forEach(m =>
addMessage(m.role, renderMarkdown(m.text))
);
} catch {}
}
window.addEventListener('load', loadHistory);
sendBtn.addEventListener('click', async () => {
const prompt = promptEl.value.trim();
if (!prompt) return;
addMessage('user', renderMarkdown(prompt));
promptEl.value = '';
const bubble = addLoader();
let raw = '';
let first = true;
abort = false;
sendBtn.disabled = true;
abortBtn.disabled = false;
clearBtn.disabled = true;
try {
const res = await fetch('/ask-sse', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt })
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
while (!abort) {
const { value, done } = await reader.read();
if (done) break;
decoder.decode(value, { stream: true })
.split('\n')
.forEach(line => {
if (!line.startsWith('data: ')) return;
const text = line.slice(6);
if (text === '[DONE]') {
const normalized = normalizeMarkdown(raw);
bubble.innerHTML = renderMarkdown(normalized);
abort = true;
return;
}
if (first) {
bubble.classList.remove('loader');
bubble.innerHTML = '';
first = false;
}
raw += text;
bubble.innerHTML = renderStreaming(raw);
chatEl.scrollTop = chatEl.scrollHeight;
});
}
} catch {
bubble.innerHTML += '<br><em>Error occurred.</em>';
} finally {
sendBtn.disabled = false;
abortBtn.disabled = true;
clearBtn.disabled = false;
}
});
abortBtn.addEventListener('click', () => {
abort = true;
addMessage('assistant', '<em>[aborted]</em>');
});
clearBtn.addEventListener('click', async () => {
await fetch('/history/delete', { method: 'POST' });
chatEl.innerHTML = '';
addMessage('assistant', '<em>History cleared.</em>');
});
})();
</script>
</body>
</html>