zhuyuan-ice-gl-zy001/QODER-CN-DEV-20260807/state-wake-20260812/bridge-chat/main2
2026-08-20 00:01:53 +08:00

110 lines
3.6 KiB
Text
Executable file
Raw Permalink 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.

// chat.js —— 光湖桥聊天主逻辑(只依赖 window.GHB,与壳无关)
(() => {
const $ = (id) => document.getElementById(id);
const stream = $('stream');
const statusLine = $('status-line');
const peerInput = $('peer');
const textInput = $('text');
const sendBtn = $('send');
const connDot = $('conn');
const nodeIdEl = $('node-id');
const fmtTime = (ts) => new Date(ts || Date.now()).toLocaleTimeString('zh-CN', { hour12: false });
function sysMsg(text) {
const d = document.createElement('div');
d.className = 'msg sys';
d.textContent = text;
stream.appendChild(d);
stream.scrollTop = stream.scrollHeight;
}
function bubble({ who, from, text, msgId }) {
const d = document.createElement('div');
d.className = `msg ${who}`;
if (msgId) d.dataset.msgId = msgId;
const meta = document.createElement('span');
meta.className = 'meta';
meta.textContent = who === 'me' ? `我 · ${fmtTime()}` : `${from} · ${fmtTime()}`;
const body = document.createElement('span');
body.textContent = text;
d.appendChild(meta);
d.appendChild(body);
if (who === 'me') {
const tick = document.createElement('span');
tick.className = 'tick';
tick.textContent = '…';
d.appendChild(tick);
}
stream.appendChild(d);
stream.scrollTop = stream.scrollHeight;
return d;
}
function setStatus(text, isErr) {
statusLine.textContent = text || '';
statusLine.classList.toggle('err', !!isErr);
}
function markReceipt(msgId, verified) {
const d = stream.querySelector(`[data-msg-id="${msgId}"]`);
if (!d) return;
const tick = d.querySelector('.tick');
if (!tick) return;
tick.textContent = verified ? '✓ 已送达·已验签' : '✓ 已送达';
tick.classList.toggle('ok', verified);
}
function doSend() {
const to = peerInput.value.trim();
const text = textInput.value.trim();
if (!to) { setStatus('请先填对方节点号', true); return; }
if (!text) return;
bubble({ who: 'me', text, msgId: null }); // 占位,sent 事件后补 msg_id
pendingSend = { text };
window.GHB.send(to, text);
textInput.value = '';
setStatus(`正在发送 → ${to} …`);
}
let pendingSend = null;
// ── 事件绑定 ──
window.GHB.on('connected', (ev) => {
nodeIdEl.textContent = `我的节点号:${ev.node_id}`;
connDot.className = 'conn on';
sysMsg(`已接入光湖桥,节点号 ${ev.node_id}(宿主:${window.GHB.kind})`);
});
window.GHB.on('message', (ev) => {
bubble({ who: 'peer', from: ev.from, text: ev.text, msgId: ev.msg_id });
peerInput.placeholder = ev.from; // 记住最近来信的对端
setStatus('');
});
window.GHB.on('sent', (ev) => {
// 给最后一个"我"的气泡补上 msg_id 以便回执对账
const mine = stream.querySelectorAll('.msg.me');
const last = mine[mine.length - 1];
if (last && !last.dataset.msgId) last.dataset.msgId = ev.msg_id;
setStatus(`已交给光湖桥 → ${ev.to}`);
pendingSend = null;
});
window.GHB.on('receipt', (ev) => {
markReceipt(ev.msg_id, ev.verified);
setStatus(ev.verified ? '对方已确认,回执验签通过' : '对方已确认');
});
window.GHB.on('error', (ev) => setStatus(`错误:${ev.message}`, true));
// 壳层自检钩子:自动填入并发送,让 UI 真实渲染完整链路
window.GHB.on('__autosend', (ev) => {
peerInput.value = ev.to;
textInput.value = ev.text;
doSend();
});
sendBtn.addEventListener('click', doSend);
textInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') doSend(); });
})();