feat(desktop): run-state becomes display-only; send key turns into a stop button while running

The top-left run-state is now a pure status indicator (green=ready, red=running)
and no longer handles clicks. Stopping the current turn moves to the send key:
while the agent is running the send button turns into a red stop square — clicking
it cancels the current turn (ChatGPT/Claude style). The Enter key still submits, so
"type while running + Enter" keeps lyx's interrupt-and-resend behavior (#5) intact.

- setBusy toggles .is-stop on the send button + aria-label (stop/send)
- send click: cancelPrompt() when busy, submitInput() otherwise
- remove run-toggle click handler + .stopped state; .run-state.busy goes red
- .send.is-stop: red bg + white rounded square (hide arrow); i18n act.send
- cache bust styles v19->20, app v25->26
This commit is contained in:
dd3xp committed 2026-05-23 23:38:34 +08:00
1 parent 8783914077
commit 57cf4d6632
3 files changed
+30 -22

No files matched your search

+15 -13
View File
@@ -304,7 +304,7 @@ const I18N = {
'st.starting': '启动中…', 'st.stopping': '停止中…',
'st.online': '在线', 'st.offline': '离线', 'st.error': '错误', 'st.running': '运行', 'st.abnormal': '异常',
'act.configure': '配置', 'act.logs': '日志', 'act.restart': '重启', 'act.stop': '停止', 'act.start': '启动',
'act.copy': '复制', 'act.copied': '已复制', 'act.copyTex': 'TeX',
'act.copy': '复制', 'act.copied': '已复制', 'act.copyTex': 'TeX', 'act.send': '发送',
'proc.imbotWechat': 'imbot · 微信', 'proc.imbotDing': 'imbot · 钉钉', 'proc.scheduler': '定时任务调度',
'cm.scheduling': '调度中', 'cm.running': '执行中', 'cm.idleSt': '空闲',
'cm.master': '已派 3 子任务', 'cm.w1': '子任务:抓取数据', 'cm.w2': '子任务:复核结果', 'cm.sub': '等待派单',
@@ -409,7 +409,7 @@ const I18N = {
'st.starting': 'Starting…', 'st.stopping': 'Stopping…',
'st.online': 'Online', 'st.offline': 'Offline', 'st.error': 'Error', 'st.running': 'Running', 'st.abnormal': 'Error',
'act.configure': 'Configure', 'act.logs': 'Logs', 'act.restart': 'Restart', 'act.stop': 'Stop', 'act.start': 'Start',
'act.copy': 'Copy', 'act.copied': 'Copied', 'act.copyTex': 'TeX',
'act.copy': 'Copy', 'act.copied': 'Copied', 'act.copyTex': 'TeX', 'act.send': 'Send',
'proc.imbotWechat': 'imbot · WeChat', 'proc.imbotDing': 'imbot · DingTalk', 'proc.scheduler': 'Scheduler',
'cm.scheduling': 'Scheduling', 'cm.running': 'Running', 'cm.idleSt': 'Idle',
'cm.master': 'Dispatched 3 subtasks', 'cm.w1': 'Subtask: fetch data', 'cm.w2': 'Subtask: review results', 'cm.sub': 'Waiting for tasks',
@@ -1041,22 +1041,19 @@ function statusLabel() {
if (s && rt(s).busy) return t('status.running');
return state.bridgeReady ? t('status.ready') : t('status.disconnected');
}
function refreshStatusLabel() { if (!runToggle.classList.contains('stopped')) runLabel.textContent = statusLabel(); }
function refreshStatusLabel() { runLabel.textContent = statusLabel(); }
function setBusy(sess, busy) {
const r = rt(sess); r.busy = busy;
if (!isActive(sess)) return;
runToggle.classList.remove('stopped');
runToggle.classList.toggle('busy', busy);
runLabel.textContent = busy ? t('status.running') : (state.bridgeReady ? t('status.ready') : t('status.disconnected'));
}
runToggle.addEventListener('click', async () => {
const sess = activeSess();
if (sess && rt(sess).busy) {
await cancelPrompt();
runLabel.textContent = t('status.stopped');
runToggle.classList.add('stopped');
if (sendBtn) {
sendBtn.classList.toggle('is-stop', busy);
sendBtn.setAttribute('aria-label', busy ? t('act.stop') : t('act.send'));
sendBtn.title = busy ? t('act.stop') : '';
}
});
}
// run-toggle 现为纯状态展示组件:运行中转红,不再响应点击(停止改由发送键的录制键承担)
/* ═══════════════ 会话 ═══════════════ */
function isUntitled(x) { return !x || /^(new chat|新对话|新会话)$/i.test(String(x).trim()); }
@@ -1406,7 +1403,12 @@ async function submitInput() {
setComposerLocked(false);
}
}
sendBtn.addEventListener('click', (e) => { e.preventDefault(); submitInput(); });
sendBtn.addEventListener('click', (e) => {
e.preventDefault();
const sess = activeSess();
if (sess && rt(sess).busy) { cancelPrompt(); return; } // 运行中:发送键是录制键 → 纯停止
submitInput();
});
inputEl.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); submitInput(); } });
inputEl.addEventListener('input', () => {
inputEl.style.height = 'auto';
+2 -2
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<title>GenericAgent</title>
<link rel="stylesheet" href="styles.css?v=19">
<link rel="stylesheet" href="styles.css?v=20">
<!-- KaTeX: LaTeX 公式渲染 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css" crossorigin="anonymous">
<!-- highlight.js: 代码高亮 -->
@@ -451,7 +451,7 @@
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.js" crossorigin="anonymous"></script>
<!-- highlight.js -->
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js" crossorigin="anonymous"></script>
<script src="app.js?v=25"></script>
<script src="app.js?v=26"></script>
<script src="annot.js"></script><!-- DEV 脚手架,可删 -->
</body>
</html>
+13 -7
View File
@@ -497,19 +497,17 @@ html[data-appearance="dark"] .swatch[data-theme="8"].sel::after{
padding:5px 12px; font-size:12px; color:var(--txt-2); cursor:pointer;
}
.pill:hover{ border-color:var(--line-strong); }
/* 纯状态展示组件(不可点):就绪=绿,运行中=红 */
.run-state{
display:flex; align-items:center; gap:9px; height:30px; color:var(--ok);
font-weight:600; font-size:12px; border:none; background:transparent;
padding:0; font-family:inherit; cursor:pointer; line-height:1;
padding:0; font-family:inherit; cursor:default; line-height:1;
}
.run-state .dot{
width:9px; height:9px; border-radius:50%; background:var(--ok);
flex:0 0 auto; transition:box-shadow .15s;
width:9px; height:9px; border-radius:50%; background:var(--ok); flex:0 0 auto;
}
.run-state:hover .dot{ box-shadow:0 0 0 7px var(--ok-ring); }
.run-state.stopped{ color:var(--danger); }
.run-state.stopped .dot{ background:var(--danger-dot); border-radius:2px; }
.run-state.stopped:hover .dot{ box-shadow:0 0 0 7px var(--danger-ring); }
.run-state.busy{ color:var(--danger); }
.run-state.busy .dot{ background:var(--danger-dot); }
.link{ color:var(--blue); cursor:pointer; }
.muted{ color:var(--muted); }
.ic-btn{
@@ -808,6 +806,14 @@ html[data-boot-has-sessions] .chat-start{ display:none !important; }
animation:ga-spin .65s linear infinite;
}
.send svg{ width:15px; height:15px; }
/* 运行中:发送键变红色停止键(点击=暂停当前回合)。回车仍走发送(打断重发)。 */
.send.is-stop{ background:var(--danger); cursor:pointer; }
.send.is-stop:hover{ background:var(--danger-dot); }
.send.is-stop svg{ display:none; }
.send.is-stop::after{
content:''; width:12px; height:12px; border-radius:3px;
background:var(--on-accent);
}
/* ============ ④ 右侧会话面板 ============ */
.rp-toggle, .sb-toggle{