Files
nimue/templates/chat.html
arch_agent 27dcaf6552 Initial commit: Nimue AI Companion v1.0
- Langzeit- und Kurzzeitgedächtnis mit SQLite
- Ollama-Integration für lokale LLMs
- Flask-Webinterface mit Stream-Response
- Persona-System mit konfigurierbarem Charakter
- Auto-Zusammenfassung bei Token-Limit
- Rate Limiting und Sicherheitsfeatures
- Uncensored Modell-Support
2026-04-14 07:44:36 +02:00

222 lines
8.3 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nimue - {{ persona_name }}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<div class="container">
<header>
<h1>𓇢 {{ persona_name }}</h1>
<div class="subtitle">Intimate AI Companion</div>
<div class="model-info">Model: <span id="model-name">Loading...</span></div>
<div class="memory-status" id="memory-status"></div>
</header>
<div class="chat-container" id="chat-box">
<div class="message system">
*kneels gracefully, eyes lowered* I'm here for you completely... waiting for your instructions. What would please you today?
</div>
</div>
<div class="input-area">
<div class="typing-indicator" id="typing" style="display: none;">Nimue is thinking...</div>
<div class="input-row">
<textarea id="user-input" placeholder="Command me..." maxlength="2000"></textarea>
<button id="send-btn" class="send-btn">Send</button>
</div>
<div class="controls">
<button onclick="clearMemory()" class="control-btn">Clear Memory</button>
<button onclick="showMemory()" class="control-btn">View Memory</button>
<span class="char-count"><span id="char-count">0</span>/2000</span>
</div>
</div>
</div>
<!-- Memory Modal -->
<div id="memory-modal" class="modal">
<div class="modal-content">
<span class="close" onclick="closeMemory()">&times;</span>
<h2>Memory Status</h2>
<div id="memory-stats"></div>
<div id="memory-preferences"></div>
<div id="memory-recent"></div>
</div>
</div>
<script>
const chatBox = document.getElementById('chat-box');
const userInput = document.getElementById('user-input');
const sendBtn = document.getElementById('send-btn');
const typing = document.getElementById('typing');
const charCount = document.getElementById('char-count');
let isGenerating = false;
let currentMessageDiv = null;
// Load config
fetch('/api/config')
.then(r => r.json())
.then(data => {
document.getElementById('model-name').textContent = data.model;
});
userInput.addEventListener('input', () => {
charCount.textContent = userInput.value.length;
});
userInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
sendBtn.addEventListener('click', sendMessage);
function appendMessage(role, content) {
const div = document.createElement('div');
div.className = `message ${role}`;
div.innerHTML = formatMessage(content);
chatBox.appendChild(div);
chatBox.scrollTop = chatBox.scrollHeight;
return div;
}
function formatMessage(text) {
return text
.replace(/\n/g, '<br>')
.replace(/\*([^*]+)\*/g, '<em>$1</em>')
.replace(/"([^"]+)"/g, '&quot;$1&quot;');
}
function updateMemoryStatus(tokens, max) {
const percent = (tokens / max * 100).toFixed(1);
const status = document.getElementById('memory-status');
status.innerHTML = `Context: ${percent}% (${tokens} tokens)`;
status.className = percent > 80 ? 'memory-status warning' : 'memory-status';
}
async function sendMessage() {
if (isGenerating) return;
const message = userInput.value.trim();
if (!message) return;
// Add user message
appendMessage('user', message);
userInput.value = '';
charCount.textContent = '0';
isGenerating = true;
typing.style.display = 'block';
sendBtn.disabled = true;
currentMessageDiv = document.createElement('div');
currentMessageDiv.className = 'message assistant';
chatBox.appendChild(currentMessageDiv);
try {
const response = await fetch('/api/chat', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({message: message})
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
let fullText = '';
while (true) {
const {done, value} = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const text = line.slice(6);
if (text === '[DONE]') continue;
fullText += text;
currentMessageDiv.innerHTML = formatMessage(fullText);
chatBox.scrollTop = chatBox.scrollHeight;
}
if (line.startsWith('event: stats')) {
// Parse stats
}
}
}
} catch (error) {
currentMessageDiv.innerHTML = '<em>*system error* ' + error.message + '</em>';
} finally {
isGenerating = false;
typing.style.display = 'none';
sendBtn.disabled = false;
getMemoryStats();
}
}
async function getMemoryStats() {
try {
const resp = await fetch('/api/memory');
const data = await resp.json();
updateMemoryStatus(data.stats.short_term_tokens, data.stats.max_context);
} catch (e) {}
}
async function clearMemory() {
if (!confirm('Clear all memory?')) return;
await fetch('/api/clear', {method: 'POST'});
chatBox.innerHTML = '<div class="message system">Memory cleared. *kneels again* How may I serve you?</div>';
getMemoryStats();
}
async function showMemory() {
const modal = document.getElementById('memory-modal');
const resp = await fetch('/api/memory');
const data = await resp.json();
document.getElementById('memory-stats').innerHTML = `
<h3>Statistics</h3>
<p>Short-term messages: ${data.stats.short_term_messages}</p>
<p>Tokens used: ${data.stats.short_term_tokens} / ${data.stats.max_context}</p>
<p>Usage: ${data.stats.usage_percent.toFixed(1)}%</p>
`;
let prefs = '<h3>Learned Preferences</h3>';
if (Object.keys(data.preferences).length === 0) {
prefs += '<p>None yet...</p>';
} else {
for (const [cat, items] of Object.entries(data.preferences)) {
prefs += `<p><strong>${cat}:</strong> ${items.join(', ')}</p>`;
}
}
document.getElementById('memory-preferences').innerHTML = prefs;
let recent = '<h3>Recent Messages</h3>';
for (const msg of data.recent) {
recent += `<p><strong>${msg.role}:</strong> ${msg.content}</p>`;
}
document.getElementById('memory-recent').innerHTML = recent;
modal.style.display = 'block';
}
function closeMemory() {
document.getElementById('memory-modal').style.display = 'none';
}
window.onclick = (e) => {
const modal = document.getElementById('memory-modal');
if (e.target === modal) modal.style.display = 'none';
};
// Initial load
getMemoryStats();
</script>
</body>
</html>