1. 微信Bot对话记录时间按北京时间渲染(ImBotHub/WechatHistory 的 formatTime 显式 +08:00 + Asia/Shanghai); 2. 新增 /app/api/chat/media/relay 媒体中转端点(白名单 agnES 域名, 经香港代理代取), 改写 _wechat_send_media 用平台中转地址,微信/iLink 可内联播放图片/视频; 3. 删除 014_clean_test_data.sql:该迁移每次重启重跑并 DELETE token_usage_logs/ messages/conversations/knowledge 等,导致 Token 驾驶舱数据每次重启被清空; 4. 002_init_data.sql 移除 market_apps 自动播种(原 WHERE COUNT=0 在用户删空后重启复活), 技能市场交由用户后台维护,重启不再复活。
106 lines
4.6 KiB
TypeScript
106 lines
4.6 KiB
TypeScript
// @ts-nocheck
|
|
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
|
import { imUserAPI } from '@/api';
|
|
|
|
const WechatHistory: React.FC = () => {
|
|
const [loading, setLoading] = useState(true);
|
|
const [messages, setMessages] = useState<any[]>([]);
|
|
const [toast, setToast] = useState<{ msg: string; type: 'success' | 'error' } | null>(null);
|
|
const bottomRef = useRef<HTMLDivElement>(null);
|
|
|
|
const showToast = (msg: string, type: 'success' | 'error' = 'success') => {
|
|
setToast({ msg, type });
|
|
setTimeout(() => setToast(null), 3000);
|
|
};
|
|
|
|
const load = useCallback(async () => {
|
|
setLoading(true);
|
|
try {
|
|
const res = await imUserAPI.history();
|
|
if ((res as any)?.success && (res as any).data) {
|
|
setMessages((res as any).data.messages || []);
|
|
} else {
|
|
showToast('加载失败', 'error');
|
|
}
|
|
} catch (e) {
|
|
showToast('加载失败', 'error');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => { load(); }, [load]);
|
|
useEffect(() => { bottomRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages]);
|
|
|
|
const formatTime = (t: string) => {
|
|
if (!t) return '-';
|
|
// 后端按北京时间存储(无时区标记的 ISO),此处显式按 Asia/Shanghai 渲染,
|
|
// 避免浏览器本地时区导致对话时间错乱(与微信 Bot 绑定页一致)。
|
|
const iso = /[Z+]/.test(t) ? t : t + '+08:00';
|
|
const d = new Date(iso);
|
|
if (isNaN(d.getTime())) return t;
|
|
return d.toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai', hour12: false });
|
|
};
|
|
|
|
return (
|
|
<div style={{ minHeight: '100%', background: '#FFFFFF', color: '#1A1F36', padding: '32px' }}>
|
|
<div style={{ maxWidth: '860px', margin: '0 auto' }}>
|
|
{/* Header */}
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '24px' }}>
|
|
<div>
|
|
<h1 style={{ fontSize: '24px', fontWeight: 700, margin: 0, color: '#1A1F36' }}>微信对话记录</h1>
|
|
<p style={{ color: '#6B7280', fontSize: '14px', margin: '6px 0 0 0' }}>
|
|
你通过个人微信与七虎AI 的对话历史,按时间顺序记录,可随时回看(仅本人可见,按账号隔离)。
|
|
</p>
|
|
</div>
|
|
<button onClick={load} disabled={loading} style={btnStyle}>{loading ? '刷新中…' : '刷新'}</button>
|
|
</div>
|
|
|
|
{/* Toast */}
|
|
{toast && (
|
|
<div style={{
|
|
position: 'fixed', top: '20px', right: '20px', zIndex: 9999,
|
|
padding: '12px 24px', borderRadius: '8px',
|
|
background: toast.type === 'success' ? '#10b981' : '#ef4444',
|
|
color: '#fff', fontSize: '14px', boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
|
|
}}>{toast.msg}</div>
|
|
)}
|
|
|
|
{/* 记录列表 */}
|
|
<div style={{ background: '#F8F9FC', borderRadius: '12px', padding: '16px', minHeight: '420px' }}>
|
|
{messages.length === 0 ? (
|
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '380px', color: '#9CA3AF', fontSize: '14px', textAlign: 'center', padding: '0 24px' }}>
|
|
{loading ? '加载中…' : '暂无对话记录。请先在「微信Bot」中绑定并发送消息,对话记录会显示在这里。'}
|
|
</div>
|
|
) : (
|
|
messages.map((m, i) => (
|
|
<div key={i} style={{ display: 'flex', justifyContent: m.role === 'user' ? 'flex-end' : 'flex-start', marginBottom: '14px' }}>
|
|
<div style={{ maxWidth: '78%' }}>
|
|
<div style={{ fontSize: '11px', color: '#9CA3AF', marginBottom: '4px', textAlign: m.role === 'user' ? 'right' : 'left' }}>
|
|
{m.role === 'user' ? '我(微信)' : '七虎AI'} · {formatTime(m.created_at)}
|
|
</div>
|
|
<div style={{
|
|
padding: '10px 14px', borderRadius: '12px', fontSize: '14px', lineHeight: '1.6',
|
|
whiteSpace: 'pre-wrap', wordBreak: 'break-word',
|
|
background: m.role === 'user' ? '#5B5FED' : '#FFFFFF',
|
|
color: m.role === 'user' ? '#fff' : '#1A1F36',
|
|
border: m.role === 'user' ? 'none' : '1px solid #E5E7EB',
|
|
}}>{m.content}</div>
|
|
</div>
|
|
</div>
|
|
))
|
|
)}
|
|
<div ref={bottomRef} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const btnStyle: React.CSSProperties = {
|
|
padding: '8px 20px', background: '#5B5FED', border: 'none', borderRadius: '6px',
|
|
color: '#fff', fontSize: '14px', fontWeight: 500, cursor: 'pointer',
|
|
};
|
|
|
|
export default WechatHistory;
|