Files
wechatAiclaw/public/chat.html
2026-03-11 00:22:41 +08:00

191 lines
8.7 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" />
<title>实时消息 - WS 同步展示</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
:root {
color-scheme: light dark;
--bg: #0f172a;
--card: #020617;
--border: #1e293b;
--accent: #22c55e;
--accent-soft: rgba(34, 197, 94, 0.12);
--text: #e5e7eb;
--muted: #9ca3af;
--radius: 16px;
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
background: radial-gradient(circle at top, #1e293b 0, #020617 55%, #000 100%);
font-family: system-ui, -apple-system, sans-serif;
color: var(--text);
}
.nav {
display: flex;
align-items: center;
gap: 16px;
padding: 12px 24px;
border-bottom: 1px solid var(--border);
background: rgba(2, 6, 23, 0.95);
}
.nav a {
color: var(--muted);
text-decoration: none;
font-size: 14px;
}
.nav a:hover { color: var(--accent); }
.nav a.current { color: var(--accent); font-weight: 500; }
.container {
max-width: 720px;
margin: 0 auto;
padding: 24px;
}
.card {
border-radius: var(--radius);
border: 1px solid var(--border);
background: radial-gradient(circle at top, rgba(15, 23, 42, 0.9), #020617);
padding: 20px;
margin-bottom: 20px;
}
.card-title { font-size: 14px; font-weight: 600; margin-bottom: 16px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em; }
.small-label { font-size: 12px; color: var(--muted); margin-bottom: 8px; }
.key-row { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
.key-row input { max-width: 280px; padding: 8px 12px; border: 1px solid var(--border); border-radius: 8px; background: rgba(15,23,42,0.9); color: var(--text); }
.msg-list {
max-height: 360px;
overflow: auto;
border: 1px solid var(--border);
border-radius: 10px;
background: rgba(15, 23, 42, 0.6);
padding: 8px;
margin-bottom: 16px;
}
.msg-item { font-size: 12px; padding: 8px 10px; border-bottom: 1px solid var(--border); }
.msg-item:last-child { border-bottom: none; }
.msg-item .from { color: var(--accent); margin-right: 8px; }
.msg-item.out { opacity: 0.9; }
.msg-item.out .from { color: #94a3b8; }
.msg-item .time { font-size: 11px; color: var(--muted); margin-left: 8px; }
.form-row { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; margin-bottom: 10px; }
.form-row label { display: block; font-size: 12px; color: var(--muted); margin-bottom: 4px; }
.form-row input { padding: 8px 12px; border: 1px solid var(--border); border-radius: 8px; background: rgba(15,23,42,0.9); color: var(--text); font-size: 13px; }
.form-row input[type="text"] { min-width: 180px; }
.form-row input[name="content"] { flex: 1; min-width: 200px; }
.primary { padding: 10px 20px; border-radius: 8px; background: var(--accent); color: #000; border: none; cursor: pointer; font-weight: 500; }
.secondary { padding: 8px 16px; border-radius: 8px; background: rgba(30,41,59,0.9); border: 1px solid var(--border); color: var(--text); cursor: pointer; font-size: 13px; }
</style>
</head>
<body>
<nav class="nav">
<a href="index.html">登录</a>
<a href="manage.html">客户与消息管理</a>
<a href="chat.html" class="current">实时消息</a>
<a href="models.html">模型管理</a>
</nav>
<div class="container">
<div class="card">
<div class="card-title">实时展示 WS 返回的消息</div>
<p class="small-label">后端通过 GetSyncMsg 连接微信服务,收到消息后写入存储;此处拉取并展示,非传统聊天界面。</p>
<div class="key-row">
<label for="key">账号 key</label>
<input type="text" id="key" placeholder="与登录页一致" autocomplete="off" />
</div>
<button type="button" class="secondary" id="btn-refresh-msg" style="margin-bottom:12px">刷新消息列表</button>
<div id="message-list" class="msg-list"></div>
</div>
<div class="card">
<div class="card-title">发送消息</div>
<div class="form-row">
<div>
<label for="send-to">对方 wxid / 用户名</label>
<input type="text" id="send-to" name="to" placeholder="wxid_xxx" />
</div>
<div style="flex:1;min-width:200px">
<label for="send-content">内容</label>
<input type="text" id="send-content" name="content" placeholder="消息内容" />
</div>
<button type="button" class="primary" id="btn-send-msg">发送</button>
</div>
</div>
</div>
<script>
const $ = (id) => document.getElementById(id);
const API_BASE = 'http://localhost:8000';
const KEY_STORAGE = 'wechat_key';
function getKey() {
const k = $('key').value.trim();
if (!k) { alert('请先填写账号 key'); return null; }
return k;
}
async function callApi(path, options = {}) {
const url = API_BASE + path;
const res = await fetch(url, { ...options, headers: { 'Content-Type': 'application/json', ...(options.headers || {}) } });
let body = null;
try { body = await res.json(); } catch (_) {}
if (!res.ok) throw new Error((body && (body.detail || body.message)) || res.statusText || '请求失败');
return body;
}
async function loadMessages() {
const key = $('key').value.trim();
if (!key) { $('message-list').innerHTML = '<p class="small-label">请先填写账号 key。</p>'; return; }
try {
const data = await callApi('/api/messages?key=' + encodeURIComponent(key) + '&limit=80');
const list = data.items || [];
// 按时间正序排列,最新在底部,便于看完整对话
const sorted = [...list].sort((a, b) => (a.CreateTime || 0) - (b.CreateTime || 0));
$('message-list').innerHTML = sorted.length ? sorted.map(m => {
const isOut = m.direction === 'out';
const from = isOut ? ('我 → ' + (m.ToUserName || '')) : (m.FromUserName || m.from || m.MsgId || '-').toString().slice(0, 32);
const content = (m.Content || m.content || m.MsgType || '').toString().slice(0, 200);
const time = m.CreateTime ? (typeof m.CreateTime === 'number' ? new Date(m.CreateTime * 1000).toLocaleTimeString('zh-CN', { hour12: false }) : m.CreateTime) : '';
return '<div class="msg-item' + (isOut ? ' out' : '') + '"><span class="from">' + from + '</span>' + content + (time ? ' <span class="time">' + time + '</span>' : '') + '</div>';
}).join('') : '<p class="small-label">暂无对话。请确保已登录且后端 WS 已连接 GetSyncMsg发送的消息也会在此展示。</p>';
} catch (e) { $('message-list').innerHTML = '<p class="small-label">加载失败: ' + e.message + '</p>'; }
}
async function sendMessage() {
const key = getKey();
if (!key) return;
const to = $('send-to').value.trim();
const content = $('send-content').value.trim();
if (!to || !content) { alert('请填写对方用户名和内容'); return; }
try {
await callApi('/api/send-message', { method: 'POST', body: JSON.stringify({ key, to_user_name: to, content }) });
$('send-content').value = '';
loadMessages();
} catch (e) { alert('发送失败: ' + e.message); }
}
$('btn-refresh-msg').addEventListener('click', loadMessages);
$('btn-send-msg').addEventListener('click', sendMessage);
$('key').addEventListener('change', function() { try { localStorage.setItem(KEY_STORAGE, this.value.trim()); } catch (_) {} });
if (typeof localStorage !== 'undefined' && localStorage.getItem(KEY_STORAGE)) $('key').value = localStorage.getItem(KEY_STORAGE);
loadMessages();
(function wsStatusCheck() {
let wasConnected = false;
const CHECK_MS = 8000;
const t = setInterval(async () => {
try {
const r = await fetch(API_BASE + '/api/ws-status', { cache: 'no-store' });
const d = await r.json().catch(() => ({}));
if (d && d.connected) wasConnected = true;
if (wasConnected && d && d.connected === false) {
clearInterval(t);
alert('连接已断开,请重新登录');
window.location.href = 'index.html';
}
} catch (_) {}
}, CHECK_MS);
})();
</script>
</body>
</html>