feat(ui): 豆包式布局—顶栏会话名/空态有什么可以帮到你的/底部工具栏(项目+新建+上传+影响调查)/历史项⋯重命名/上传去zip
This commit is contained in:
@@ -172,7 +172,7 @@ header h1 { font-size: 16px; font-weight: 700; letter-spacing: -.02em; margin: 0
|
||||
.ps-current .ps-name { max-width: 160px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||
.ps-current .ps-caret { font-size: 10px; color: var(--text-muted); }
|
||||
.ps-menu {
|
||||
position: absolute; top: calc(100% + 6px); left: 0; min-width: 240px;
|
||||
position: absolute; bottom: calc(100% + 6px); top: auto; left: 0; min-width: 240px;
|
||||
background: var(--bg-surface); border: 1px solid var(--border-strong); border-radius: var(--r-md);
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,.4); padding: 6px; z-index: 20;
|
||||
display: none;
|
||||
@@ -410,3 +410,58 @@ label.vh { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px
|
||||
#sid-badge { max-width: 24%; }
|
||||
#proj-drawer { width: 100vw; }
|
||||
}
|
||||
|
||||
/* === 顶栏会话名 === */
|
||||
#session-title { font-size: 15px; font-weight: 700; letter-spacing: -.02em; color: var(--text-primary);
|
||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
|
||||
|
||||
/* === 聊天区改为纵向 flex(空态垂直居中) === */
|
||||
#chat { display: flex; flex-direction: column; }
|
||||
|
||||
/* === 空态(豆包式) === */
|
||||
.empty-state { flex: 1 1 auto; display: flex; flex-direction: column; align-items: center;
|
||||
justify-content: center; text-align: center; padding: 32px 16px; gap: 10px; min-height: 0; }
|
||||
.empty-state .es-logo { width: 56px; height: 56px; border-radius: 16px; display: flex; align-items: center;
|
||||
justify-content: center; font-size: 26px; font-weight: 900; color: var(--bg-base);
|
||||
background: var(--accent); box-shadow: 0 0 32px var(--accent-soft); }
|
||||
.empty-state .es-title { font-size: 24px; font-weight: 700; margin: 6px 0 0; letter-spacing: -.02em; }
|
||||
.empty-state .es-sub { font-size: 13px; color: var(--text-secondary); line-height: 1.7; margin: 0; max-width: 560px; }
|
||||
.empty-state .suggests { display: grid; grid-template-columns: repeat(2, minmax(180px, 1fr)); gap: 10px;
|
||||
margin-top: 16px; width: 100%; max-width: 620px; }
|
||||
.suggest-card { text-align: left; padding: 12px 16px; border-radius: var(--r-md);
|
||||
background: var(--bg-surface); border: 1px solid var(--border-subtle); color: var(--text-primary);
|
||||
font-size: 13px; font-family: var(--font-ui); cursor: pointer; transition: all var(--dur) var(--ease); }
|
||||
.suggest-card:hover { border-color: var(--accent); background: var(--bg-elevated); transform: translateY(-1px); }
|
||||
.suggest-card::before { content: "\2192 "; color: var(--accent); font-weight: 700; }
|
||||
|
||||
/* === 底部工具栏 === */
|
||||
#composer-toolbar { max-width: 880px; margin: 10px auto 0; display: flex; align-items: center;
|
||||
gap: 8px; flex-wrap: wrap; }
|
||||
.tb-btn { display: inline-flex; align-items: center; gap: 6px; padding: 7px 12px;
|
||||
background: transparent; border: 1px solid var(--border-strong); border-radius: var(--r-md);
|
||||
color: var(--text-secondary); font-size: 13px; font-weight: 500; font-family: var(--font-ui);
|
||||
cursor: pointer; transition: all var(--dur) var(--ease); }
|
||||
.tb-btn:hover { border-color: var(--accent); color: var(--accent); }
|
||||
.tb-btn .tb-ic { font-size: 14px; line-height: 1; }
|
||||
.tb-btn .tb-caret { font-size: 10px; color: var(--text-muted); }
|
||||
.proj-switcher { position: relative; }
|
||||
#upload-panel { max-width: 880px; margin: 10px auto 0; }
|
||||
|
||||
/* === 会话项「⋯」与重命名菜单 === */
|
||||
.sess .kebab { flex-shrink: 0; opacity: 0; transition: opacity .15s ease; background: transparent;
|
||||
border: none; color: var(--text-muted); cursor: pointer; width: 24px; height: 24px; border-radius: 6px;
|
||||
font-size: 15px; line-height: 1; display: flex; align-items: center; justify-content: center; }
|
||||
.sess:hover .kebab { opacity: .7; }
|
||||
.sess .kebab:hover { opacity: 1 !important; background: var(--bg-base); color: var(--accent); }
|
||||
.rename-input { flex: 1; min-width: 0; background: var(--bg-input); border: 1px solid var(--accent);
|
||||
border-radius: var(--r-sm); color: var(--text-primary); font-size: 13px; padding: 3px 6px;
|
||||
font-family: var(--font-ui); outline: none; }
|
||||
.sess-menu { position: fixed; z-index: 120; background: var(--bg-surface); border: 1px solid var(--border-strong);
|
||||
border-radius: var(--r-md); box-shadow: 0 8px 32px rgba(0,0,0,.5); padding: 4px; min-width: 140px; }
|
||||
.sess-menu .sm-item { display: flex; align-items: center; gap: 8px; width: 100%; padding: 8px 12px;
|
||||
background: transparent; border: none; border-radius: var(--r-sm); color: var(--text-secondary);
|
||||
font-size: 13px; font-family: var(--font-ui); cursor: pointer; text-align: left; }
|
||||
.sess-menu .sm-item:hover { background: var(--bg-elevated); color: var(--accent); }
|
||||
.sess-menu .sm-ic { color: var(--accent); }
|
||||
#sidebar.collapsed .sess .kebab { display: none; }
|
||||
@media (max-width: 640px) { .empty-state .suggests { grid-template-columns: 1fr; } }
|
||||
|
||||
@@ -20,30 +20,39 @@
|
||||
|
||||
<div id="main">
|
||||
<header>
|
||||
<h1>Genesis 概要设计书生成</h1>
|
||||
|
||||
<div class="proj-switcher" id="proj-switcher">
|
||||
<button class="ps-current" id="ps-current" type="button" title="切换项目">
|
||||
<span class="ps-label">项目</span>
|
||||
<span class="ps-name" id="ps-name">未选择</span>
|
||||
<span class="ps-caret">▾</span>
|
||||
</button>
|
||||
<div class="ps-menu" id="ps-menu" role="menu"></div>
|
||||
</div>
|
||||
<div id="rag-bar" hidden>
|
||||
<label class="rag-switch">
|
||||
<input type="checkbox" id="rag-enabled">
|
||||
<span>启用 RAG 影响调查</span>
|
||||
</label>
|
||||
<span class="rag-stats" id="rag-stats">RAG 0 片段</span>
|
||||
<button id="impact-btn" type="button" disabled>开始影响调查</button>
|
||||
</div>
|
||||
<span id="sid-badge">未创建会话</span>
|
||||
<span id="session-title" title="当前会话">未创建会话</span>
|
||||
</header>
|
||||
|
||||
<div id="chat"></div>
|
||||
|
||||
<div id="upload-bar">
|
||||
<div id="composer">
|
||||
<div id="composer-inner">
|
||||
<input id="input" placeholder="输入指令,如:生成概要设计书 / 现在什么状态?" autocomplete="off" maxlength="2000">
|
||||
<button id="send">发送</button>
|
||||
</div>
|
||||
|
||||
<div id="composer-toolbar">
|
||||
<div class="proj-switcher" id="proj-switcher">
|
||||
<button class="tb-btn" id="ps-current" type="button" title="切换项目">
|
||||
<span class="tb-ic">▤</span>
|
||||
<span class="tb-text" id="ps-name">未选择项目</span>
|
||||
<span class="tb-caret">▾</span>
|
||||
</button>
|
||||
<div class="ps-menu" id="ps-menu" role="menu"></div>
|
||||
</div>
|
||||
<button class="tb-btn" id="tb-newproject" type="button" title="新建项目"><span class="tb-ic">+</span><span class="tb-text">新建项目</span></button>
|
||||
<button class="tb-btn" id="tb-upload" type="button" title="上传资料"><span class="tb-ic">📎</span><span class="tb-text">上传资料</span></button>
|
||||
<div id="rag-bar" hidden>
|
||||
<label class="rag-switch">
|
||||
<input type="checkbox" id="rag-enabled">
|
||||
<span>启用 RAG</span>
|
||||
</label>
|
||||
<span class="rag-stats" id="rag-stats">RAG 0 片段</span>
|
||||
<button id="impact-btn" type="button" disabled>影响调查</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="upload-panel" hidden>
|
||||
<div id="upload-bar-inner">
|
||||
<span id="proj-hint"></span>
|
||||
<label for="file-type" class="vh">上传类型</label>
|
||||
@@ -53,7 +62,6 @@
|
||||
<option value="template">概要设计模板 docx(必需)</option>
|
||||
<option value="write_instruction">做成说明书 docx</option>
|
||||
<option value="rules">记入/图表规则 docx/xlsx</option>
|
||||
<option value="existing_system">既有系统 zip(追加改修)</option>
|
||||
</select>
|
||||
<label class="file-pick" for="file-input">选择文件</label>
|
||||
<input type="file" id="file-input" style="display:none">
|
||||
@@ -61,12 +69,6 @@
|
||||
<span id="upload-status"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="composer">
|
||||
<div id="composer-inner">
|
||||
<input id="input" placeholder="输入指令,如:上传了文件,生成概要设计书 / 现在什么状态?" autocomplete="off" maxlength="2000">
|
||||
<button id="send">发送</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -78,7 +80,7 @@
|
||||
<div class="drawer-body">
|
||||
<div class="drawer-list" id="drawer-list"></div>
|
||||
<div class="drawer-form" id="drawer-form">
|
||||
<div class="drawer-hint">选择左侧项目以查看与编辑,或点 <strong>+ 新建项目</strong> 创建一个。</div>
|
||||
<div class="drawer-hint">选择左侧项目以查看与编辑,或点 <strong>+ 新建项目</strong> 创建;新建时请设置该项目的资料目录。</div>
|
||||
<div class="field">
|
||||
<label>项目名(英文/唯一)</label>
|
||||
<input id="pf-name" placeholder="stock">
|
||||
@@ -87,30 +89,26 @@
|
||||
<label>显示名(可选)</label>
|
||||
<input id="pf-display" placeholder="股票系统">
|
||||
</div>
|
||||
<details class="advanced">
|
||||
<summary>高级(可选,服务器端预置路径)</summary>
|
||||
<p class="drawer-hint">这些是<strong>服务器端</strong>预置路径,普通用户无需填写,留空则在每个会话中上传对应文件。仅在管理员/CLI 集成时使用。</p>
|
||||
<div class="field">
|
||||
<label>模板 docx 路径</label>
|
||||
<input id="pf-template" placeholder="/abs/path/template.docx">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>做成说明书 docx 路径</label>
|
||||
<input id="pf-write" placeholder="/abs/path/write_instruction.docx">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>规则 docx 目录(逗号分隔)</label>
|
||||
<input id="pf-rules" placeholder="/abs/path/rules, /abs/path/diagrams">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>既有系统代码库目录</label>
|
||||
<input id="pf-code" placeholder="/abs/path/existing-system">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>既有设计文档目录</label>
|
||||
<input id="pf-design" placeholder="/abs/path/design-docs">
|
||||
</div>
|
||||
</details>
|
||||
<div class="field">
|
||||
<label>模板 docx 路径</label>
|
||||
<input id="pf-template" placeholder="/abs/path/template.docx">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>做成说明书 docx 路径</label>
|
||||
<input id="pf-write" placeholder="/abs/path/write_instruction.docx">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>规则 docx 目录(逗号分隔)</label>
|
||||
<input id="pf-rules" placeholder="/abs/path/rules, /abs/path/diagrams">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>既有系统代码库目录</label>
|
||||
<input id="pf-code" placeholder="/abs/path/existing-system">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>既有设计文档目录</label>
|
||||
<input id="pf-design" placeholder="/abs/path/design-docs">
|
||||
</div>
|
||||
<div class="drawer-actions">
|
||||
<button class="btn-save" id="pf-save">保存项目</button>
|
||||
<button class="btn-delete" id="pf-delete" hidden>删除</button>
|
||||
@@ -119,6 +117,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="sess-menu" class="sess-menu" hidden>
|
||||
<button type="button" class="sm-item" id="sm-rename"><span class="sm-ic">✎</span>重命名</button>
|
||||
</div>
|
||||
|
||||
<script src="/static/chat_state.js"></script>
|
||||
<script src="/static/chat_ws.js"></script>
|
||||
<script src="/static/chat.js" defer></script>
|
||||
|
||||
@@ -22,12 +22,14 @@ const GenesisState = window.GenesisState || {
|
||||
isDrawerDirty: (n, s) => n !== s,
|
||||
buildWelcome: (d) => d === null ? '请新建项目或者选择项目'
|
||||
: '你好!我是 Genesis 概要设计书生成 Agent。请先上传要件定义 xlsx(模板/规则/代码库已由项目「' + d + '」提供)。',
|
||||
emptyHeadline: () => '有什么可以帮到你的?',
|
||||
quickPrompts: () => ['生成概要设计书', '影响调查', '用中文生成', '现在什么状态?'],
|
||||
};
|
||||
|
||||
const chatEl = document.getElementById('chat');
|
||||
const inputEl = document.getElementById('input');
|
||||
const sendBtn = document.getElementById('send');
|
||||
const badge = document.getElementById('sid-badge');
|
||||
const sessionTitle = document.getElementById('session-title');
|
||||
const psMenu = document.getElementById('ps-menu');
|
||||
const psName = document.getElementById('ps-name');
|
||||
const drawer = document.getElementById('proj-drawer');
|
||||
@@ -76,10 +78,13 @@ async function refreshSessions() {
|
||||
'<div class="name">' + esc(s.name || '新会话') + '</div>' +
|
||||
'<div class="meta">' + esc(s.status) + proj + '</div>' +
|
||||
'</div>' +
|
||||
'<button class="kebab" title="更多" aria-label="更多操作">⋯</button>' +
|
||||
'<button class="del" title="删除会话" aria-label="删除会话 ' + esc(rawName) + '">×</button>';
|
||||
el.querySelector('.info').onclick = () => loadSession(s.session_id);
|
||||
const delBtn = el.querySelector('.del');
|
||||
delBtn.onclick = (ev) => { ev.stopPropagation(); deleteSession(s.session_id); };
|
||||
const kebab = el.querySelector('.kebab');
|
||||
kebab.onclick = (ev) => openSessMenu(ev, s.session_id, s.name || '新会话', el);
|
||||
box.appendChild(el);
|
||||
}
|
||||
}
|
||||
@@ -116,7 +121,7 @@ async function loadProjects() {
|
||||
}
|
||||
|
||||
function applyProjectContext() {
|
||||
psName.textContent = draftProject || '未选择';
|
||||
psName.textContent = draftProject || '未选择项目';
|
||||
}
|
||||
|
||||
function renderProjectSwitcher() {
|
||||
@@ -360,11 +365,75 @@ function updateUploadBar() {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 空态/欢迎渲染 ----------
|
||||
// ---------- 空态/欢迎渲染(豆包式:主标题 + 副提示 + 建议卡) ----------
|
||||
function insertPrompt(text) {
|
||||
inputEl.value = text;
|
||||
inputEl.focus();
|
||||
try { inputEl.setSelectionRange(text.length, text.length); } catch (e) { /* 忽略 */ }
|
||||
}
|
||||
|
||||
function renderEmptyOrWelcome() {
|
||||
chatEl.innerHTML = '';
|
||||
// P1‑F:欢迎文由 GenesisState.buildWelcome 统一来源
|
||||
addMsg('assistant', GenesisState.buildWelcome(draftProject));
|
||||
const wrap = document.createElement('div');
|
||||
wrap.className = 'empty-state';
|
||||
const cards = GenesisState.quickPrompts().map(t =>
|
||||
'<button class="suggest-card" type="button" data-prompt="' + esc(t) + '">' + esc(t) + '</button>'
|
||||
).join('');
|
||||
wrap.innerHTML =
|
||||
'<div class="es-logo">G</div>' +
|
||||
'<h2 class="es-title">' + esc(GenesisState.emptyHeadline()) + '</h2>' +
|
||||
'<p class="es-sub">' + GenesisState.buildWelcome(draftProject) + '</p>' +
|
||||
'<div class="suggests">' + cards + '</div>';
|
||||
chatEl.appendChild(wrap);
|
||||
wrap.querySelectorAll('.suggest-card').forEach(b => {
|
||||
b.onclick = () => insertPrompt(b.dataset.prompt);
|
||||
});
|
||||
}
|
||||
|
||||
// ---------- 会话「⋯」菜单 + 重命名 ----------
|
||||
let sessMenuTarget = null;
|
||||
function openSessMenu(ev, sidVal, name, el) {
|
||||
ev.stopPropagation();
|
||||
sessMenuTarget = { sid: sidVal, name: name, el: el };
|
||||
const m = document.getElementById('sess-menu');
|
||||
if (!m) return;
|
||||
m.hidden = false;
|
||||
const r = ev.currentTarget.getBoundingClientRect();
|
||||
m.style.top = (r.bottom + 4) + 'px';
|
||||
m.style.left = Math.max(8, r.right - 150) + 'px';
|
||||
}
|
||||
function closeSessMenu() {
|
||||
const m = document.getElementById('sess-menu');
|
||||
if (m) m.hidden = true;
|
||||
}
|
||||
function startRenameSession(el, s) {
|
||||
const nameEl = el.querySelector('.name');
|
||||
if (!nameEl) return;
|
||||
const input = document.createElement('input');
|
||||
input.className = 'rename-input';
|
||||
input.value = s.name || '';
|
||||
input.maxLength = 100;
|
||||
nameEl.replaceWith(input);
|
||||
input.focus(); input.select();
|
||||
let done = false;
|
||||
const finish = async (save) => {
|
||||
if (done) return; done = true;
|
||||
const newName = input.value.trim();
|
||||
if (save && newName && newName !== (s.name || '')) {
|
||||
try {
|
||||
const r = await api('PATCH', '/api/sessions/' + encodeURIComponent(s.session_id), { name: newName });
|
||||
if (s.session_id === sid) sessionTitle.textContent = r.name || newName;
|
||||
} catch (e) {
|
||||
addMsg('error', '重命名失败:' + esc(String(e)));
|
||||
}
|
||||
}
|
||||
await refreshSessions();
|
||||
};
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') { e.preventDefault(); finish(true); }
|
||||
else if (e.key === 'Escape') { e.preventDefault(); finish(false); }
|
||||
});
|
||||
input.addEventListener('blur', () => finish(true));
|
||||
}
|
||||
|
||||
// ---------- 会话创建/加载 ----------
|
||||
@@ -375,7 +444,7 @@ async function newSession() {
|
||||
// 保留 draftProject(当前顶栏已选项目),使新会话继承项目绑定(§4.2)
|
||||
try { localStorage.removeItem('genesis_session'); } catch (e) { /* 忽略 */ }
|
||||
chatEl.innerHTML = '';
|
||||
badge.textContent = '未创建会话';
|
||||
sessionTitle.textContent = '未创建会话';
|
||||
updateUploadBar();
|
||||
await refreshRagStats();
|
||||
await refreshSessions();
|
||||
@@ -391,7 +460,7 @@ async function loadSession(id) {
|
||||
// 分层;若不一致,由提示条让用户主动选择。
|
||||
// draftProject = rec.project || null; // 删除:原行为会悄悄改用户偏好
|
||||
applyProjectContext();
|
||||
badge.textContent = '会话: ' + (rec.name || '新会话');
|
||||
sessionTitle.textContent = (rec.name || '新会话');
|
||||
if (progressWs) { try { progressWs.close(); } catch (e) {} }
|
||||
progressWs = GenesisWS.connectProgressWs(id, {
|
||||
onProgress: (e) => GenesisWS.applyProgressEvent(e, (role, text) => addMsg(role, text)),
|
||||
@@ -433,7 +502,7 @@ function renderProjectMismatchHint() {
|
||||
wrap.innerHTML =
|
||||
'<div class="bubble">' +
|
||||
'该会话属于项目 <strong>' + esc(activeProject) + '</strong>,' +
|
||||
'当前顶栏项目为 <strong>' + esc(draftProject || '(未选择)') + '</strong>。' +
|
||||
'当前项目为 <strong>' + esc(draftProject || '(未选择)') + '</strong>。' +
|
||||
'<div class="actions">' +
|
||||
'<a href="#" data-act="switch">切到该项目</a>' +
|
||||
'<a href="#" data-act="keep">保留当前项目</a>' +
|
||||
@@ -471,7 +540,7 @@ async function send() {
|
||||
const d = await api('POST', '/api/sessions', { user_id: 'default', project: draftProject || null });
|
||||
sid = d.session_id;
|
||||
activeProject = draftProject || null;
|
||||
badge.textContent = '会话: ' + (d.name || '新会话');
|
||||
sessionTitle.textContent = (d.name || '新会话');
|
||||
try { localStorage.setItem('genesis_session', sid); } catch (e) { /* 忽略 */ }
|
||||
await refreshSessions();
|
||||
} catch (e) {
|
||||
@@ -523,6 +592,20 @@ document.getElementById('ps-current').addEventListener('click', (e) => { e.stopP
|
||||
document.getElementById('drawer-close').addEventListener('click', closeProjectDrawer);
|
||||
document.getElementById('pf-save').addEventListener('click', saveDrawerProject);
|
||||
document.getElementById('pf-delete').addEventListener('click', deleteDrawerProject);
|
||||
document.getElementById('tb-newproject').addEventListener('click', () => {
|
||||
if (!guardDrawerDirty()) return;
|
||||
openProjectDrawer(null);
|
||||
});
|
||||
document.getElementById('tb-upload').addEventListener('click', () => {
|
||||
const panel = document.getElementById('upload-panel');
|
||||
if (panel) panel.hidden = !panel.hidden;
|
||||
});
|
||||
document.getElementById('sm-rename').addEventListener('click', (ev) => {
|
||||
ev.stopPropagation();
|
||||
const t = sessMenuTarget;
|
||||
closeSessMenu();
|
||||
if (t) startRenameSession(t.el, { session_id: t.sid, name: t.name });
|
||||
});
|
||||
|
||||
// ---------- P0‑A:RAG 开关 + 状态徽标 + 开始影响调查 ----------
|
||||
const ragEnabledEl = document.getElementById('rag-enabled');
|
||||
@@ -555,7 +638,7 @@ async function refreshRagStats() {
|
||||
const n = Number(stats.chunks) || 0;
|
||||
ragStatsEl.textContent = n > 0
|
||||
? 'RAG 已索引 ' + n + ' 片段'
|
||||
: 'RAG 0 片段(请上传既有系统 zip)';
|
||||
: 'RAG 0 片段(请在项目配置中设置代码库目录)';
|
||||
ragStatsEl.classList.toggle('ready', n > 0);
|
||||
ragStatsEl.classList.toggle('zero', n === 0);
|
||||
// 开关可用性:必须先有 RAG 索引才能用
|
||||
@@ -563,7 +646,7 @@ async function refreshRagStats() {
|
||||
if (n === 0) {
|
||||
ragEnabledEl.checked = false;
|
||||
writeRagFlag(false);
|
||||
ragEnabledEl.parentElement.title = '请先在上传区选择"既有系统 zip"并上传';
|
||||
ragEnabledEl.parentElement.title = '请在项目配置中设置既有系统代码库目录';
|
||||
} else {
|
||||
ragEnabledEl.checked = readRagFlag();
|
||||
ragEnabledEl.parentElement.title = ragEnabledEl.checked
|
||||
@@ -607,9 +690,13 @@ impactBtnEl.addEventListener('click', async () => {
|
||||
document.addEventListener('click', (e) => {
|
||||
// 点击切换器外部关闭下拉
|
||||
if (!document.getElementById('proj-switcher').contains(e.target)) toggleProjectSwitcher(false);
|
||||
// 点击「⋯」菜单外部关闭
|
||||
const sm = document.getElementById('sess-menu');
|
||||
if (sm && !sm.hidden && !sm.contains(e.target)) closeSessMenu();
|
||||
});
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
closeSessMenu();
|
||||
if (psMenu.classList.contains('open')) toggleProjectSwitcher(false);
|
||||
else if (drawerOpen) closeProjectDrawer();
|
||||
if (lastTriggerEl) { lastTriggerEl.focus(); lastTriggerEl = null; }
|
||||
@@ -675,7 +762,7 @@ document.getElementById('upload-btn').addEventListener('click', async () => {
|
||||
// B5 / P1‑F:上传成功后同步 activeProject、刷新上传条与侧边栏状态
|
||||
const rec = await api('GET', '/api/sessions/' + encodeURIComponent(sid));
|
||||
activeProject = rec.project || null;
|
||||
badge.textContent = '会话: ' + (rec.name || '新会话');
|
||||
sessionTitle.textContent = (rec.name || '新会话');
|
||||
await refreshSessions();
|
||||
updateUploadBar(); // P1‑F:上传成功后同步上传条(防状态滞后)
|
||||
await refreshRagStats(); // P0‑A:上传 existing_system 后 RAG 索引已建
|
||||
|
||||
@@ -53,12 +53,22 @@
|
||||
return '请新建项目或者选择项目';
|
||||
}
|
||||
return '你好!我是 Genesis 概要设计书生成 Agent。'
|
||||
+ '请先在上方📎上传<strong>要件定义 xlsx</strong>'
|
||||
+ '请先在下方📎上传<strong>要件定义 xlsx</strong>'
|
||||
+ '(模板/规则/代码库已由项目「' + draftProject + '」提供),然后告诉我:'
|
||||
+ '<br>· 「生成概要设计书」(自动解析→影响→生成→QA)'
|
||||
+ '<br>· 「用中文生成」 / 「现在什么状态?」';
|
||||
}
|
||||
|
||||
// 空态主标题(豆包式布局:新建会话居中显示)
|
||||
function emptyHeadline() {
|
||||
return '有什么可以帮到你的?';
|
||||
}
|
||||
|
||||
// 空态建议卡 / 快捷入口提示词(点击填入输入框,不自动发送)
|
||||
function quickPrompts() {
|
||||
return ['生成概要设计书', '影响调查', '用中文生成', '现在什么状态?'];
|
||||
}
|
||||
|
||||
return {
|
||||
autoBindProject,
|
||||
shouldHideUploadSelect,
|
||||
@@ -66,5 +76,7 @@
|
||||
computeDrawerSnapshot,
|
||||
isDrawerDirty,
|
||||
buildWelcome,
|
||||
emptyHeadline,
|
||||
quickPrompts,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -47,3 +47,13 @@ test('buildWelcome: 有项目时提示由项目提供模板/规则/代码库', (
|
||||
assert.match(txt, /项目「A」/);
|
||||
assert.match(txt, /要件定义/);
|
||||
});
|
||||
|
||||
test('emptyHeadline: 返回空态主标题', () => {
|
||||
assert.match(S.emptyHeadline(), /有什么可以帮到你的/);
|
||||
});
|
||||
|
||||
test('quickPrompts: 返回 4 条快捷提示且含影响调查', () => {
|
||||
const p = S.quickPrompts();
|
||||
assert.equal(p.length, 4);
|
||||
assert.ok(p.includes('影响调查'));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user