feat(chat): 前端 chat_ws.js 实时渲染进度/错误(保留重载兜底)
This commit is contained in:
@@ -119,6 +119,13 @@ def create_app(
|
||||
raise _error(404, "NOT_FOUND", "chat_state.js 不存在")
|
||||
return FileResponse(p, media_type="application/javascript")
|
||||
|
||||
@app.get("/chat_ws.js")
|
||||
def chat_ws_js():
|
||||
p = static_dir / "chat_ws.js"
|
||||
if not p.exists():
|
||||
raise _error(404, "NOT_FOUND", "chat_ws.js 不存在")
|
||||
return FileResponse(p, media_type="application/javascript")
|
||||
|
||||
# ---------- 项目配置 ----------
|
||||
|
||||
@app.post("/api/projects")
|
||||
|
||||
@@ -454,8 +454,10 @@
|
||||
</div>
|
||||
|
||||
<script src="/chat_state.js"></script>
|
||||
<script src="/chat_ws.js"></script>
|
||||
<script>
|
||||
let sid = null;
|
||||
let progressWs = null;
|
||||
let draftProject = null; // 下一空白会话将绑定/已绑的项目(原 currentProject)
|
||||
let activeProject = null; // 当前已落库会话绑定的项目(来自后端)
|
||||
let projects = [];
|
||||
@@ -765,6 +767,7 @@ function renderEmptyOrWelcome() {
|
||||
|
||||
// ---------- 会话创建/加载 ----------
|
||||
async function newSession() {
|
||||
if (progressWs) { try { progressWs.close(); } catch (e) {} } progressWs = null;
|
||||
sid = null;
|
||||
activeProject = null;
|
||||
// 保留 draftProject(当前顶栏已选项目),使新会话继承项目绑定(§4.2)
|
||||
@@ -784,6 +787,11 @@ async function loadSession(id) {
|
||||
draftProject = rec.project || null; // 载入既有会话时,顶栏与侧边栏过滤对齐该会话项目
|
||||
applyProjectContext();
|
||||
badge.textContent = '会话: ' + (rec.name || '新会话');
|
||||
if (progressWs) { try { progressWs.close(); } catch (e) {} }
|
||||
progressWs = GenesisWS.connectProgressWs(id, {
|
||||
onProgress: (e) => GenesisWS.applyProgressEvent(e, (role, text) => addMsg(role, text)),
|
||||
onClose: () => {},
|
||||
});
|
||||
const msgs = await api('GET', '/api/chat/' + id + '/messages');
|
||||
chatEl.innerHTML = '';
|
||||
for (const m of msgs) {
|
||||
@@ -816,6 +824,11 @@ async function send() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (progressWs) { try { progressWs.close(); } catch (e) {} }
|
||||
progressWs = GenesisWS.connectProgressWs(sid, {
|
||||
onProgress: (e) => GenesisWS.applyProgressEvent(e, (role, text) => addMsg(role, text)),
|
||||
onClose: () => {},
|
||||
});
|
||||
inputEl.value = '';
|
||||
addMsg('user', esc(text));
|
||||
const typing = showTyping();
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
(function (root, factory) {
|
||||
const api = factory();
|
||||
if (typeof module !== 'undefined' && module.exports) module.exports = api;
|
||||
else root.GenesisWS = api;
|
||||
})(typeof self !== 'undefined' ? self : this, function () {
|
||||
function connectProgressWs(sid, handlers) {
|
||||
const proto = location.protocol === 'https:' ? 'wss' : 'ws';
|
||||
let ws;
|
||||
try {
|
||||
ws = new WebSocket(`${proto}://${location.host}/api/sessions/${encodeURIComponent(sid)}/ws`);
|
||||
} catch (err) {
|
||||
// 连接失败(如无 WS 依赖 / 代理拦截)静默降级;现有持久化 + 重载兜底仍可见进度
|
||||
if (handlers.onClose) handlers.onClose();
|
||||
return null;
|
||||
}
|
||||
ws.onmessage = (ev) => {
|
||||
let e;
|
||||
try { e = JSON.parse(ev.data); } catch { return; }
|
||||
if (handlers.onProgress) handlers.onProgress(e);
|
||||
};
|
||||
ws.onerror = () => { try { ws.close(); } catch {} };
|
||||
ws.onclose = () => handlers.onClose && handlers.onClose();
|
||||
return ws;
|
||||
}
|
||||
function applyProgressEvent(e, render) {
|
||||
if (e.type === 'progress') render('progress', `${e.step}: ${e.detail || ''}`);
|
||||
else if (e.type === 'error') render('error', e.detail || '错误');
|
||||
}
|
||||
return { connectProgressWs, applyProgressEvent };
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert');
|
||||
const { applyProgressEvent } = require('../src/genesis/server/static/chat_ws.js');
|
||||
|
||||
test('applyProgressEvent 渲染 progress 角色', () => {
|
||||
const got = [];
|
||||
applyProgressEvent({ type: 'progress', step: 'parse', detail: '完成' }, (role, text) => got.push([role, text]));
|
||||
assert.strictEqual(got.length, 1);
|
||||
assert.strictEqual(got[0][0], 'progress');
|
||||
assert.ok(got[0][1].includes('parse'));
|
||||
});
|
||||
|
||||
test('applyProgressEvent 渲染 error 角色', () => {
|
||||
const got = [];
|
||||
applyProgressEvent({ type: 'error', detail: '炸了' }, (role, text) => got.push([role, text]));
|
||||
assert.strictEqual(got[0][0], 'error');
|
||||
assert.ok(got[0][1].includes('炸了'));
|
||||
});
|
||||
Reference in New Issue
Block a user