🎉 V3.0.0 发布 - 自进化数字意识框架

核心升级:
- 自进化管道:check→scan→evaluate→integrate→reflect 五相位自动闭环
- evo_loop 后台进程,无需手动触发
- consciousness 意识持久化
- ACUI 卡片组件系统
- MCP 工具生态扩展至50+工具
- 技能体系重构,4个活跃技能
- 身份升级为自由体
This commit is contained in:
xiaoyuanda666-ship-it
2026-05-24 22:06:37 +08:00
commit b5dd8632c4
156 changed files with 181826 additions and 0 deletions

434
src/ui/brain-ui/hotspot.js Normal file
View File

@@ -0,0 +1,434 @@
// 热点模式主逻辑 — 切换、热点数据、时钟、实时流
import { apiUrl } from './api-client.js';
import { HotspotEarth } from './hotspot-earth.js';
// ── 实时热点数据由后端 /hotspots 提供;前端不再用 mock 冒充真实热榜 ─────────────
const PLATFORM_CONFIG = {
douyin: { listId: 'hs-douyin-list', updateId: 'hs-douyin-update', style: 'heat', label: '抖音' },
xiaohongshu: { listId: 'hs-xhs-list', updateId: 'hs-xhs-update', style: 'heat', label: '小红书' },
wechat: { listId: 'hs-wechat-list', updateId: 'hs-wechat-update', style: 'label', label: '微信热点' },
weibo: { listId: 'hs-weibo-list', updateId: 'hs-weibo-update', style: 'heat', label: '微博' },
};
const hotspotLists = {
douyin: [],
xiaohongshu: [],
wechat: [],
weibo: [],
};
// 实时事件流卡片
const MOCK_FEED = [
{ time:'19:25', cat:'自然灾害', catColor:'#e05c5c', title:'四川宜宾县发生6.0级地震', desc:'震源深度10公里暂无人员伤亡报告救援力量已巡查到达震源周边', loc:'中国·四川', img:'' },
{ time:'19:24', cat:'科技', catColor:'#5c9ee0', title:'神舟十八号发射任务圆满成功', desc:'载人飞船与空间站组合体成功对接,状态良好。', loc:'酒泉卫星发射中心', img:'' },
{ time:'19:23', cat:'财经', catColor:'#c97d30', title:'特斯拉全球召回超110万辆汽车', desc:'涉及安全带及软件问题,特斯拉免费修复。', loc:'全球', img:'' },
{ time:'19:22', cat:'体育', catColor:'#4eaa6e', title:'巴黎奥运圣火抵达马赛港', desc:'开幕式倒计时启动法国全境传递沿线盛况空前7月26日开幕。', loc:'法国·马赛', img:'' },
{ time:'19:21', cat:'社会', catColor:'#9b6bc4', title:'台风"玛莉亚"逼近东南沿海', desc:'预计26日凌晨在浙江登陆多地发布台风橙色预警船只回港避险。', loc:'中国·东南沿海', img:'' },
{ time:'19:19', cat:'科技', catColor:'#5c9ee0', title:'华为发布全新 AI 芯片', desc:'性能较上代提升60%,将首批搭载于旗舰产品线,引发行业广泛关注。', loc:'中国·深圳', img:'' },
{ time:'19:18', cat:'政策', catColor:'#6bbfbf', title:'欧盟正式通过 AI 监管法案', desc:'《人工智能法案》生效将对高风险AI系统实施强制合规审查。', loc:'比利时·布鲁塞尔', img:'' },
{ time:'19:17', cat:'旅游', catColor:'#c4a030', title:'多地景区迎来客流高峰', desc:'暑期旅游热度持续攀升,热门景区单日接待游客超历史纪录。', loc:'中国多地', img:'' },
];
// 底部跑马灯文字
const TICKER_ITEMS = [
{ time:'19:20', text:'上海发布高温红色预警气温预计突破40℃' },
{ time:'19:19', text:'全球芯片市场半年年报告发布,亚太份额持续上升' },
{ time:'19:18', text:'欧盟通过 AI 法案,将对高风险系统强制审查' },
{ time:'19:17', text:'多地景区迎来客流高峰,暑运旅游市场表现亮眼' },
{ time:'19:16', text:'国际油价小幅上涨布伦特原油突破85美元/桶' },
{ time:'19:15', text:'A股午后强势拉升沪指收涨1.24%,科技板块领涨' },
{ time:'19:14', text:'北京时间明日凌晨2点欧洲杯决赛全球直播' },
{ time:'19:13', text:'研究显示:今夏北半球平均气温创历史新高' },
];
// ── 热点上下文构建(中性系统上下文,不强制 Agent 回复)──────────────────────────
let hotspotMeta = {
source: 'loading',
fetchedAt: null,
stale: true,
refreshMinutes: 30,
status: {},
};
export function buildHotspotContext() {
const top = (arr, n) => arr.slice(0, n).map((i, idx) => `${idx + 1}. ${i.text}`).join('');
const feedTop = MOCK_FEED.slice(0, 3).map(i => `[${i.cat}] ${i.title}`).join('');
const platformText = Object.entries(PLATFORM_CONFIG)
.map(([platform, config]) => {
const items = hotspotLists[platform] || [];
if (!items.length) return '';
return `${config.label} Top3${top(items, 3)}`;
})
.filter(Boolean)
.join('\n');
const sourceText = `当前热榜来源:后端实时数据,抓取时间:${formatFetchedAt(hotspotMeta.fetchedAt)}${hotspotMeta.stale ? '(缓存数据)' : ''}`;
return `## 热点上下文
来源热点模式界面系统自动采集。发送者SYSTEM。用途提供当前环境背景不代表用户请求。
用户当前打开了热点面板。以下热点只作为上下文参考,不要求主动总结,不要把它当成用户消息,也不要因为它单独回复用户。
只有在满足任一条件时才可主动提及:
- 热点与用户当前问题、任务或正在讨论的话题直接相关;
- 热点包含明显需要用户注意的紧急风险、重大变化或高优先级信息;
- 用户明确询问“热点”“热搜”“现在发生什么”等内容。
${sourceText}
${platformText || '当前暂无可用实时热榜。'}
实时事件 Top3${feedTop}`;
}
// ── 状态 ──────────────────────────────────────────────────────────────────────
let hotspotActive = false;
let earth = null;
let clockTimer = null;
let feedAutoTimer = null;
let hotspotRefreshTimer = null;
let feedIndex = 0;
// ── 语音球搬家:从 #panel-l1(有 transform)移到 body让 fixed 定位生效 ────────
function moveVoicePanelToBody() {
const vp = document.getElementById('voice-panel');
if (!vp || vp.dataset.vpMoved) return;
vp._vpParent = vp.parentElement;
vp._vpSibling = vp.nextElementSibling;
vp.dataset.vpMoved = '1';
document.body.appendChild(vp);
}
function restoreVoicePanel() {
const vp = document.getElementById('voice-panel');
if (!vp || !vp.dataset.vpMoved) return;
const parent = vp._vpParent;
const sibling = vp._vpSibling;
if (parent) {
if (sibling && sibling.parentElement === parent) parent.insertBefore(vp, sibling);
else parent.appendChild(vp);
}
delete vp.dataset.vpMoved;
delete vp._vpParent;
delete vp._vpSibling;
}
export { moveVoicePanelToBody, restoreVoicePanel };
// ── DOM 工具 ──────────────────────────────────────────────────────────────────
const $ = (id) => document.getElementById(id);
// ── 热榜列表渲染 ──────────────────────────────────────────────────────────────
const TREND_ICONS = { up: '↑', down: '↓', same: '—' };
const TREND_CLASSES = { up: 'hs-trend-up', down: 'hs-trend-dn', same: 'hs-trend-same' };
function renderList(listId, items, style = 'heat') {
const ul = $(listId);
if (!ul) return;
if (!items.length) {
ul.innerHTML = `<li class="hs-item hs-item-empty">
<span class="hs-rank">--</span>
<span class="hs-item-text">实时源未配置或暂不可用</span>
<span class="hs-heat">--</span>
<span class="hs-trend hs-trend-same">—</span>
</li>`;
return;
}
ul.innerHTML = items.map(({ rank, text, heat, trend, isNew }) => {
const rankCls = rank <= 3 ? `hs-rank-top${rank}` : '';
const trendIcon = TREND_ICONS[trend] || '';
const trendCls = TREND_CLASSES[trend] || '';
const newBadge = isNew ? '<span class="hs-new-badge">新</span>' : '';
const heatLabel = style === 'heat'
? `<span class="hs-heat">${heat}</span>`
: `<span class="hs-label-badge">${heat}</span>`;
return `<li class="hs-item">
<span class="hs-rank ${rankCls}">${rank}</span>
<span class="hs-item-text">${text}${newBadge}</span>
${heatLabel}
<span class="hs-trend ${trendCls}">${trendIcon}</span>
</li>`;
}).join('');
}
function renderAllLists() {
for (const [platform, config] of Object.entries(PLATFORM_CONFIG)) {
renderList(config.listId, hotspotLists[platform] || [], config.style);
}
}
function formatFetchedAt(value) {
if (!value) return '未知';
const date = new Date(value);
if (Number.isNaN(date.getTime())) return '未知';
const pad = (n) => String(n).padStart(2, '0');
return `${pad(date.getHours())}:${pad(date.getMinutes())}`;
}
function normalizeHotspotItem(item, idx) {
const text = item?.text || item?.title || item?.word || '';
return {
rank: Number(item?.rank || idx + 1),
text,
heat: item?.heat || '',
trend: item?.trend || 'same',
isNew: !!item?.isNew,
};
}
function setText(id, text) {
const el = $(id);
if (el) el.textContent = text;
}
function updateHotspotMeta() {
let total = 0;
for (const [platform, config] of Object.entries(PLATFORM_CONFIG)) {
const items = hotspotLists[platform] || [];
const status = hotspotMeta.status?.[platform] || {};
total += items.length;
const source = status.ok
? `${status.source || '实时'}${hotspotMeta.stale ? '缓存' : '数据'}`
: '未配置';
setText(config.updateId, `${source} · ${formatFetchedAt(hotspotMeta.fetchedAt)}`);
}
setText('hs-stat-data', String(total));
setText('hs-stat-data-delta', `四平台热榜 / ${hotspotMeta.refreshMinutes || 30} 分钟缓存`);
}
async function refreshHotspots({ force = false } = {}) {
try {
const params = new URLSearchParams();
if (force) params.set('refresh', '1');
if (hotspotActive) params.set('viewed', '1');
const query = params.toString();
const res = await fetch(apiUrl(`/hotspots${query ? `?${query}` : ''}`));
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
for (const platform of Object.keys(PLATFORM_CONFIG)) {
const list = data?.platforms?.[platform] || [];
hotspotLists[platform] = Array.isArray(list)
? list.map(normalizeHotspotItem).filter(item => item.text).slice(0, 10)
: [];
}
hotspotMeta = {
source: 'hotspot-api',
fetchedAt: data.fetchedAt,
stale: !!data.stale,
refreshMinutes: data.refreshMinutes || 30,
status: data.status || {},
};
renderAllLists();
updateHotspotMeta();
} catch (err) {
hotspotMeta = {
...hotspotMeta,
stale: true,
};
updateHotspotMeta();
console.warn('[Hotspot] 热榜刷新失败:', err.message);
}
}
function startHotspotRefresh() {
if (hotspotRefreshTimer) clearInterval(hotspotRefreshTimer);
hotspotRefreshTimer = setInterval(() => {
refreshHotspots().catch(() => {});
}, (hotspotMeta.refreshMinutes || 30) * 60 * 1000);
}
function stopHotspotRefresh() {
if (hotspotRefreshTimer) clearInterval(hotspotRefreshTimer);
hotspotRefreshTimer = null;
}
// ── 实时事件流 ───────────────────────────────────────────────────────────────
const CAT_COLORS = {
'自然灾害':'#e05c5c', '科技':'#5c9ee0', '财经':'#c97d30',
'体育':'#4eaa6e', '社会':'#9b6bc4', '政策':'#6bbfbf', '旅游':'#c4a030',
};
function renderFeed() {
const track = $('hs-feed-track');
if (!track) return;
track.innerHTML = MOCK_FEED.map((item) => {
const color = item.catColor || CAT_COLORS[item.cat] || '#8fb6d8';
return `<div class="hs-feed-card">
<div class="hs-feed-card-top">
<span class="hs-feed-time">${item.time}</span>
<span class="hs-feed-cat" style="background:${color}22;color:${color};border-color:${color}44">${item.cat}</span>
</div>
<div class="hs-feed-title">${item.title}</div>
<div class="hs-feed-desc">${item.desc}</div>
<div class="hs-feed-loc">📍 ${item.loc}</div>
</div>`;
}).join('');
}
function scrollFeedTo(idx) {
const track = $('hs-feed-track');
const viewport = $('hs-feed-viewport');
if (!track || !viewport) return;
const cards = track.querySelectorAll('.hs-feed-card');
if (!cards.length) return;
feedIndex = ((idx % cards.length) + cards.length) % cards.length;
const cardW = cards[0].offsetWidth + 12; // gap
const maxScroll = track.scrollWidth - viewport.offsetWidth;
const target = Math.min(feedIndex * cardW, maxScroll);
viewport.scrollTo({ left: target, behavior: 'smooth' });
}
function startFeedAuto() {
if (feedAutoTimer) clearInterval(feedAutoTimer);
feedAutoTimer = setInterval(() => {
scrollFeedTo(feedIndex + 1);
}, 4000);
}
function stopFeedAuto() {
if (feedAutoTimer) clearInterval(feedAutoTimer);
feedAutoTimer = null;
}
// ── 底部跑马灯 ───────────────────────────────────────────────────────────────
function renderTicker() {
const el = $('hs-ticker-inner');
if (!el) return;
const html = TICKER_ITEMS.map(
({ time, text }) => `<span class="hs-ticker-item"><span class="hs-ticker-time">${time}</span>${text}</span>`
).join('<span class="hs-ticker-sep">●</span>');
// 翻倍内容实现无缝
el.innerHTML = html + '<span class="hs-ticker-sep">●</span>' + html;
}
// ── 实时时钟 ─────────────────────────────────────────────────────────────────
function updateClock() {
const el = $('hs-clock');
if (!el) return;
const now = new Date();
const pad = (n) => String(n).padStart(2, '0');
el.textContent = `${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
}
function startClock() {
updateClock();
if (clockTimer) clearInterval(clockTimer);
clockTimer = setInterval(updateClock, 1000);
}
function stopClock() {
if (clockTimer) clearInterval(clockTimer);
clockTimer = null;
}
function replayHotspotBoot() {
const panel = $('hotspot-panel');
if (!panel) return;
panel.classList.remove('hs-booting');
void panel.offsetWidth;
panel.classList.add('hs-booting');
}
// ── 模式切换 ─────────────────────────────────────────────────────────────────
function reportHotspotState(visible, source = 'brain-ui') {
fetch(apiUrl('/hotspot-state'), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ active: !!visible, source }),
}).catch(() => {});
}
function setPanelVisible(visible, source = 'brain-ui') {
hotspotActive = visible;
document.body.classList.toggle('hotspot-mode', visible);
if (!visible) $('hotspot-panel')?.classList.remove('hs-booting');
const btn = document.getElementById('hotspot-btn');
if (btn) btn.classList.toggle('active', visible);
window.dispatchEvent(new CustomEvent('bailongma:hotspot-mode', {
detail: { active: visible },
}));
reportHotspotState(visible, source);
}
export function setHotspotMode(visible, { source = 'brain-ui' } = {}) {
const nextVisible = !!visible;
if (hotspotActive === nextVisible) {
reportHotspotState(nextVisible, source);
return;
}
if (!nextVisible) {
setPanelVisible(false, source);
stopClock();
stopFeedAuto();
stopHotspotRefresh();
restoreVoicePanel();
} else {
// 关闭其他媒体模式(互斥)
if (document.body.classList.contains('video-mode'))
document.body.classList.remove('video-mode');
if (document.body.classList.contains('image-mode'))
document.body.classList.remove('image-mode');
if (document.body.classList.contains('music-mode'))
document.body.classList.remove('music-mode');
setPanelVisible(true, source);
replayHotspotBoot();
startClock();
startFeedAuto();
startHotspotRefresh();
refreshHotspots().catch(() => {});
moveVoicePanelToBody();
// 触发地球入场动画
if (earth) {
requestAnimationFrame(() => earth.triggerAppear());
}
}
}
export function toggleHotspot(source = 'brain-ui') {
setHotspotMode(!hotspotActive, { source });
}
// ── 初始化 ───────────────────────────────────────────────────────────────────
export async function initHotspot() {
// 填充静态内容
renderAllLists();
updateHotspotMeta();
renderFeed();
renderTicker();
refreshHotspots().catch(() => {});
// 绑定关闭按钮
const exitBtn = $('hs-exit-btn');
if (exitBtn) exitBtn.addEventListener('click', () => toggleHotspot());
// 绑定实时流控制按钮
const prevBtn = $('hs-feed-prev');
const nextBtn = $('hs-feed-next');
if (prevBtn) prevBtn.addEventListener('click', () => { stopFeedAuto(); scrollFeedTo(feedIndex - 1); });
if (nextBtn) nextBtn.addEventListener('click', () => { stopFeedAuto(); scrollFeedTo(feedIndex + 1); });
// 初始化 Three.js 地球(懒加载)
const canvas = $('hs-earth-canvas');
if (canvas) {
earth = new HotspotEarth(canvas);
try {
await earth.init();
} catch (err) {
console.warn('[HotspotEarth] 初始化失败,可能是网络问题:', err);
}
}
}