Files
TrulyMEM-TrueHumanMEM-local/graph_memory_tui/web/templates/index.html
JianFeeeee 4bc5bd3e41 feat: Create TUI-like web interface
- Redesign web interface to match TUI layout
- Left panel: message history and input
- Right panel: config and operation log
- Dark theme with terminal style
- Add keyboard shortcuts (Ctrl+Enter)
- Add real-time status updates
- Improve API endpoints
- Update documentation
2026-04-10 16:08:33 +08:00

409 lines
11 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="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Graph Memory TUI - Web Interface</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Courier New', monospace;
background: #1a1a1a;
color: #e0e0e0;
height: 100vh;
display: flex;
flex-direction: column;
}
/* Header */
.header {
background: #2d2d2d;
padding: 10px 20px;
border-bottom: 2px solid #4a9eff;
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 {
color: #4a9eff;
font-size: 18px;
}
.header .status {
color: #888;
font-size: 12px;
}
/* Main Container */
.container {
display: flex;
flex: 1;
overflow: hidden;
}
/* Left Panel - Chat */
.left-panel {
flex: 1;
display: flex;
flex-direction: column;
border-right: 1px solid #333;
}
/* Message History */
.message-history {
flex: 1;
overflow-y: auto;
padding: 20px;
background: #0d0d0d;
}
.message {
margin-bottom: 15px;
padding: 10px;
border-radius: 5px;
}
.message.user {
background: #1e3a5f;
border-left: 3px solid #4a9eff;
}
.message.assistant {
background: #2d2d2d;
border-left: 3px solid #50c878;
}
.message .role {
font-weight: bold;
margin-bottom: 5px;
font-size: 12px;
color: #888;
}
.message .content {
white-space: pre-wrap;
line-height: 1.5;
}
.message .timestamp {
font-size: 10px;
color: #666;
margin-top: 5px;
}
/* Input Box */
.input-box {
padding: 15px;
background: #1a1a1a;
border-top: 1px solid #333;
}
.input-box textarea {
width: 100%;
background: #0d0d0d;
color: #e0e0e0;
border: 1px solid #4a9eff;
padding: 10px;
font-family: 'Courier New', monospace;
font-size: 14px;
resize: none;
height: 60px;
}
.input-box textarea:focus {
outline: none;
border-color: #50c878;
}
.input-box .hint {
font-size: 11px;
color: #666;
margin-top: 5px;
}
/* Right Panel - Config & Logs */
.right-panel {
width: 350px;
background: #1a1a1a;
display: flex;
flex-direction: column;
overflow-y: auto;
}
.panel-section {
padding: 15px;
border-bottom: 1px solid #333;
}
.panel-section h3 {
color: #4a9eff;
font-size: 14px;
margin-bottom: 10px;
padding-bottom: 5px;
border-bottom: 1px solid #333;
}
/* Config */
.config-item {
margin-bottom: 15px;
}
.config-item label {
display: block;
font-size: 12px;
color: #888;
margin-bottom: 5px;
}
.config-item input {
width: 100%;
background: #0d0d0d;
color: #e0e0e0;
border: 1px solid #333;
padding: 8px;
font-family: 'Courier New', monospace;
font-size: 12px;
}
.config-item input:focus {
outline: none;
border-color: #4a9eff;
}
.config-hint {
font-size: 11px;
color: #666;
font-style: italic;
}
/* Operation Log */
.operation-log {
flex: 1;
overflow-y: auto;
}
.log-entry {
padding: 8px;
margin-bottom: 8px;
background: #0d0d0d;
border-left: 2px solid #50c878;
font-size: 11px;
}
.log-entry .time {
color: #666;
}
.log-entry .tool {
color: #4a9eff;
font-weight: bold;
}
/* Buttons */
.btn {
background: #4a9eff;
color: #fff;
border: none;
padding: 8px 15px;
cursor: pointer;
font-family: 'Courier New', monospace;
font-size: 12px;
margin-top: 10px;
}
.btn:hover {
background: #50c878;
}
/* Scrollbar */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #1a1a1a;
}
::-webkit-scrollbar-thumb {
background: #333;
}
::-webkit-scrollbar-thumb:hover {
background: #4a9eff;
}
</style>
</head>
<body>
<div class="header">
<h1>Graph Memory TUI - Web Interface</h1>
<div class="status">Connected | Database: SQLite</div>
</div>
<div class="container">
<!-- Left Panel -->
<div class="left-panel">
<div class="message-history" id="messageHistory">
<div class="message assistant">
<div class="role">Assistant</div>
<div class="content">欢迎使用 Graph Memory TUI Web界面
这是一个让AI拥有长期记忆的图记忆系统。
功能:
• 记忆检索 - 查找历史信息
• 记忆写入 - 保存重要信息
• 记忆管理 - 删除、归档记忆
请先在右侧配置API Key然后开始对话。</div>
<div class="timestamp">System</div>
</div>
</div>
<div class="input-box">
<textarea id="userInput" placeholder="输入消息... (Ctrl+Enter发送)"></textarea>
<div class="hint">按 Ctrl+Enter 发送消息</div>
</div>
</div>
<!-- Right Panel -->
<div class="right-panel">
<div class="panel-section">
<h3>━━ 配置 ━━</h3>
<div class="config-item">
<label>API Key:</label>
<input type="text" id="apiKey" placeholder="sk-xxxxxxxxxxxxx">
</div>
<div class="config-item">
<label>模型:</label>
<input type="text" id="model" value="deepseek-chat">
</div>
<div class="config-item">
<label>Base URL:</label>
<input type="text" id="baseUrl" value="https://api.deepseek.com">
</div>
<button class="btn" onclick="saveConfig()">保存配置</button>
<div class="config-hint">配置会自动保存到本地</div>
</div>
<div class="panel-section">
<h3>━━ 数据库状态 ━━</h3>
<div id="dbStatus">加载中...</div>
<button class="btn" onclick="refreshStatus()">刷新状态</button>
</div>
<div class="panel-section operation-log">
<h3>━━ 操作日志 ━━</h3>
<div id="operationLog"></div>
</div>
</div>
</div>
<script>
// Message handling
const messageHistory = document.getElementById('messageHistory');
const userInput = document.getElementById('userInput');
function addMessage(role, content) {
const msg = document.createElement('div');
msg.className = `message ${role}`;
msg.innerHTML = `
<div class="role">${role === 'user' ? 'You' : 'Assistant'}</div>
<div class="content">${content}</div>
<div class="timestamp">${new Date().toLocaleTimeString()}</div>
`;
messageHistory.appendChild(msg);
messageHistory.scrollTop = messageHistory.scrollHeight;
}
function addLog(tool, result) {
const log = document.getElementById('operationLog');
const entry = document.createElement('div');
entry.className = 'log-entry';
entry.innerHTML = `
<div class="time">${new Date().toLocaleTimeString()}</div>
<div class="tool">${tool}</div>
<div>${result}</div>
`;
log.insertBefore(entry, log.firstChild);
}
// Send message
async function sendMessage() {
const message = userInput.value.trim();
if (!message) return;
addMessage('user', message);
userInput.value = '';
try {
const response = await fetch('/api/chat', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({message})
});
const data = await response.json();
addMessage('assistant', data.response);
addLog('chat', 'Message sent');
} catch (error) {
addMessage('assistant', `Error: ${error.message}`);
}
}
// Config
async function saveConfig() {
const config = {
api_key: document.getElementById('apiKey').value,
model: document.getElementById('model').value,
base_url: document.getElementById('baseUrl').value
};
try {
const response = await fetch('/api/config', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(config)
});
const data = await response.json();
addLog('config', 'Configuration saved');
alert('配置已保存!');
} catch (error) {
alert('保存失败:' + error.message);
}
}
// Status
async function refreshStatus() {
try {
const response = await fetch('/api/memory/introspect');
const data = await response.json();
document.getElementById('dbStatus').innerHTML = `
<div>实体数量: ${data.entity_count}</div>
<div>关系数量: ${data.relation_count}</div>
`;
addLog('introspect', 'Status refreshed');
} catch (error) {
document.getElementById('dbStatus').innerHTML = 'Error: ' + error.message;
}
}
// Keyboard shortcuts
userInput.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 'Enter') {
sendMessage();
}
});
// Initialize
refreshStatus();
</script>
</body>
</html>