feat(chat): 首条消息建会话(D-v4)+上传门禁(B5/F1)+Esc焦点(C1)+抽屉未保存守卫(B6)
This commit is contained in:
@@ -485,8 +485,9 @@ async function api(method, url, body) {
|
||||
|
||||
// ---------- 会话列表 ----------
|
||||
async function refreshSessions() {
|
||||
const list = await api('GET', '/api/sessions?user_id=default');
|
||||
const box = document.getElementById('session-list');
|
||||
if (draftProject === null) { box.innerHTML = ''; return; } // 未选项目 → 无历史
|
||||
const list = await api('GET', '/api/sessions?user_id=default&project=' + encodeURIComponent(draftProject));
|
||||
box.innerHTML = '';
|
||||
list.sort((a, b) => (b.updated_at || '').localeCompare(a.updated_at || ''));
|
||||
for (const s of list) {
|
||||
@@ -561,6 +562,7 @@ function renderProjectSwitcher() {
|
||||
psMenu.innerHTML = html;
|
||||
psMenu.querySelectorAll('.ps-item[data-name]').forEach(it => {
|
||||
it.onclick = () => {
|
||||
if (!guardDrawerDirty()) return;
|
||||
currentProject = it.dataset.name === '' ? null : it.dataset.name;
|
||||
applyProjectContext();
|
||||
renderProjectSwitcher();
|
||||
@@ -570,6 +572,8 @@ function renderProjectSwitcher() {
|
||||
});
|
||||
psMenu.querySelectorAll('.ps-item[data-manage]').forEach(it => {
|
||||
it.onclick = () => {
|
||||
if (!guardDrawerDirty()) return;
|
||||
lastTriggerEl = it;
|
||||
toggleProjectSwitcher(false);
|
||||
openProjectDrawer(drawerSelected);
|
||||
};
|
||||
@@ -590,6 +594,7 @@ function openProjectDrawer(projectName) {
|
||||
}
|
||||
|
||||
function closeProjectDrawer() {
|
||||
if (!guardDrawerDirty()) return;
|
||||
drawerOpen = false;
|
||||
drawer.classList.remove('open');
|
||||
}
|
||||
@@ -622,9 +627,32 @@ function loadDrawerForm(projectName) {
|
||||
tplEl.value = ''; wrEl.value = ''; rulesEl.value = ''; codeEl.value = ''; designEl.value = '';
|
||||
delBtn.style.display = 'none';
|
||||
}
|
||||
// B6:记录字段快照用于脏检测
|
||||
drawerSnapshot = drawerSnapshotNow();
|
||||
renderDrawerList();
|
||||
}
|
||||
|
||||
// B6:抽屉未保存变更守卫
|
||||
function drawerSnapshotNow() {
|
||||
return [
|
||||
document.getElementById('pf-name').value.trim(),
|
||||
document.getElementById('pf-display').value.trim(),
|
||||
document.getElementById('pf-template').value.trim(),
|
||||
document.getElementById('pf-write').value.trim(),
|
||||
document.getElementById('pf-rules').value.trim(),
|
||||
document.getElementById('pf-code').value.trim(),
|
||||
document.getElementById('pf-design').value.trim(),
|
||||
].join('\u0001');
|
||||
}
|
||||
function isDrawerDirty() {
|
||||
if (!drawerOpen || drawerSnapshot === null) return false;
|
||||
return drawerSnapshotNow() !== drawerSnapshot;
|
||||
}
|
||||
function guardDrawerDirty() {
|
||||
if (isDrawerDirty()) return confirm('有未保存的修改,确定放弃?');
|
||||
return true;
|
||||
}
|
||||
|
||||
function renderDrawerList() {
|
||||
let html = '<div class="drawer-item' + (drawerSelected === null ? ' active' : '') + '" data-name="">'
|
||||
+ '<span class="di-new">+</span><span class="di-name">新建项目</span></div>';
|
||||
@@ -636,6 +664,7 @@ function renderDrawerList() {
|
||||
box.innerHTML = html;
|
||||
box.querySelectorAll('.drawer-item').forEach(it => {
|
||||
it.onclick = () => {
|
||||
if (!guardDrawerDirty()) return;
|
||||
const n = it.dataset.name === '' ? null : it.dataset.name;
|
||||
loadDrawerForm(n);
|
||||
};
|
||||
@@ -754,7 +783,21 @@ async function loadSession(id) {
|
||||
|
||||
async function send() {
|
||||
const text = inputEl.value.trim();
|
||||
if (!text || !sid) return;
|
||||
if (!text) return;
|
||||
if (!sid) {
|
||||
// 首条消息:先落库会话(D-v4)
|
||||
try {
|
||||
const d = await api('POST', '/api/sessions', { user_id: 'default', project: draftProject || null });
|
||||
sid = d.session_id;
|
||||
activeProject = draftProject || null;
|
||||
badge.textContent = '会话: ' + (d.name || '新会话');
|
||||
try { localStorage.setItem('genesis_session', sid); } catch (e) { /* 忽略 */ }
|
||||
await refreshSessions();
|
||||
} catch (e) {
|
||||
addMsg('error', '创建会话失败:' + esc(String(e)));
|
||||
return;
|
||||
}
|
||||
}
|
||||
inputEl.value = '';
|
||||
addMsg('user', esc(text));
|
||||
const typing = showTyping();
|
||||
@@ -789,7 +832,7 @@ document.getElementById('send').addEventListener('click', send);
|
||||
inputEl.addEventListener('keydown', e => { if (e.key === 'Enter') send(); });
|
||||
document.getElementById('new-chat').addEventListener('click', () => newSession().catch(e => addMsg('error', esc(String(e)))));
|
||||
document.getElementById('sidebar-toggle').addEventListener('click', () => toggleSidebar());
|
||||
document.getElementById('ps-current').addEventListener('click', (e) => { e.stopPropagation(); toggleProjectSwitcher(); });
|
||||
document.getElementById('ps-current').addEventListener('click', (e) => { e.stopPropagation(); lastTriggerEl = e.currentTarget; toggleProjectSwitcher(); });
|
||||
document.getElementById('drawer-close').addEventListener('click', closeProjectDrawer);
|
||||
document.getElementById('pf-save').addEventListener('click', saveDrawerProject);
|
||||
document.getElementById('pf-delete').addEventListener('click', deleteDrawerProject);
|
||||
@@ -801,14 +844,15 @@ document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
if (psMenu.classList.contains('open')) toggleProjectSwitcher(false);
|
||||
else if (drawerOpen) closeProjectDrawer();
|
||||
if (lastTriggerEl) { lastTriggerEl.focus(); lastTriggerEl = null; }
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('upload-btn').addEventListener('click', async () => {
|
||||
if (!sid) { addMsg('error', '请先发送一条消息以创建会话'); inputEl.focus(); return; }
|
||||
let ft = document.getElementById('file-type').value;
|
||||
if (activeProject || currentProject) ft = 'requirements';
|
||||
if (activeProject || draftProject) ft = 'requirements';
|
||||
const file = document.getElementById('file-input').files[0];
|
||||
if (!sid) { addMsg('error', '请先创建会话'); return; }
|
||||
if (!file) { addMsg('error', '请选择文件'); return; }
|
||||
const fd = new FormData();
|
||||
fd.append('file_type', ft);
|
||||
@@ -820,14 +864,17 @@ document.getElementById('upload-btn').addEventListener('click', async () => {
|
||||
const d = await r.json();
|
||||
if (!r.ok) throw new Error(d.detail?.message || r.status);
|
||||
statusEl.textContent = '';
|
||||
addMsg('user', '[上传] ' + ft + ':' + d.file_name);
|
||||
addMsg('progress', '<span class="progress-item ok">上传完成:' + esc(d.file_name) + '(' + d.size + 'B)</span>');
|
||||
document.getElementById('file-input').value = '';
|
||||
// B5:上传成功后同步 activeProject 并刷新侧边栏状态
|
||||
const rec = await api('GET', '/api/sessions/' + sid);
|
||||
activeProject = rec.project || null;
|
||||
badge.textContent = '会话: ' + (rec.name || '新会话');
|
||||
await refreshSessions();
|
||||
document.getElementById('file-input').value = ''; // 仅成功时清空
|
||||
addMsg('user', '[上传] ' + ft + ':' + d.file_name);
|
||||
addMsg('progress', '<span class="progress-item ok">上传完成:' + esc(d.file_name) + '(' + d.size + 'B)</span>');
|
||||
} catch (e) {
|
||||
statusEl.textContent = '';
|
||||
// F1:失败保留 file-input 值,展示后端明细
|
||||
addMsg('error', '上传失败:' + esc(String(e)));
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user