nx-kv 0.8.2 → 0.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -54,6 +54,11 @@ export default function SessionTaskView() {
54
54
  const [suffixEditing, setSuffixEditing] = useState(false);
55
55
  const [suffixDraft, setSuffixDraft] = useState('');
56
56
  const [suffixSaving, setSuffixSaving] = useState(false);
57
+ // 探活状态:'connecting' | 'ok' | 'reconnecting'(数据轮询 + 后续 SSE 接入)
58
+ const [liveStatus, setLiveStatus] = useState('connecting');
59
+ // 「只读弹窗」:点击现状/上一轮总结/任务文本时显示聚焦阅读的模态框
60
+ // modal = { title, body } | null
61
+ const [modal, setModal] = useState(null);
57
62
  const [editingId, setEditingId] = useState(null);
58
63
  const [editText, setEditText] = useState('');
59
64
  const [editSaving, setEditSaving] = useState(false);
@@ -69,11 +74,14 @@ export default function SessionTaskView() {
69
74
 
70
75
  const load = useCallback(async () => {
71
76
  try {
72
- setOverview(await api('/api/localtasks'));
73
- } catch (e) {
74
- toast(e.message);
77
+ const data = await api('/api/localtasks');
78
+ setOverview(data);
79
+ setLiveStatus('ok');
80
+ } catch {
81
+ // 轮询失败 → 进入「重连中」态;指示灯变红,避免每 1.5s 弹错误 toast
82
+ setLiveStatus('reconnecting');
75
83
  }
76
- }, [toast]);
84
+ }, []);
77
85
 
78
86
  useEffect(() => {
79
87
  load();
@@ -85,6 +93,16 @@ export default function SessionTaskView() {
85
93
  return () => clearInterval(t);
86
94
  }, [load]);
87
95
 
96
+ // 只读弹窗:按 Esc 关闭
97
+ useEffect(() => {
98
+ if (!modal) return;
99
+ const onKey = (e) => {
100
+ if (e.key === 'Escape') setModal(null);
101
+ };
102
+ window.addEventListener('keydown', onKey);
103
+ return () => window.removeEventListener('keydown', onKey);
104
+ }, [modal]);
105
+
88
106
  const topics = overview ? overview.topics : [];
89
107
 
90
108
  // 同步页面上的超时配置(来源:后端 settings)
@@ -495,7 +513,35 @@ export default function SessionTaskView() {
495
513
  style={{ margin: 0 }}
496
514
  />
497
515
  <h3>主题总览(按工作目录)</h3>
498
- <span className="muted">{overview ? `${topics.length} 个 · 等待中 ${overview.waiting}` : ''}</span>
516
+ <span
517
+ className="muted"
518
+ style={{
519
+ fontSize: 11,
520
+ display: 'inline-flex',
521
+ alignItems: 'center',
522
+ gap: 4,
523
+ color: liveStatus === 'ok' ? '#2a9d5c' : liveStatus === 'reconnecting' ? '#c0392b' : '#888',
524
+ }}
525
+ title={
526
+ liveStatus === 'ok'
527
+ ? '实时连接正常(数据轮询可达后端)'
528
+ : liveStatus === 'reconnecting'
529
+ ? '连接断开,正在重连…'
530
+ : '正在连接…'
531
+ }
532
+ >
533
+ <span
534
+ style={{
535
+ display: 'inline-block',
536
+ width: 8,
537
+ height: 8,
538
+ borderRadius: '50%',
539
+ background: liveStatus === 'ok' ? '#2a9d5c' : liveStatus === 'reconnecting' ? '#c0392b' : '#aaa',
540
+ }}
541
+ />
542
+ {liveStatus === 'ok' ? '实时连接正常' : liveStatus === 'reconnecting' ? '连接断开,正在重连…' : '正在连接…'}
543
+ </span>
544
+ <span className="muted">{overview ? ` · ${topics.length} 个 · 等待中 ${overview.waiting}` : ''}</span>
499
545
  <span style={{ marginLeft: 'auto', display: 'flex', gap: 8 }}>
500
546
  <button
501
547
  className="btn small ghost"
@@ -557,7 +603,12 @@ export default function SessionTaskView() {
557
603
  </div>
558
604
  </div>
559
605
  {t.summary && (
560
- <div className="muted" style={{ fontSize: 11, whiteSpace: 'pre-wrap', marginBottom: 6 }}>
606
+ <div
607
+ className="muted"
608
+ style={{ fontSize: 11, whiteSpace: 'pre-wrap', marginBottom: 6, cursor: 'zoom-in' }}
609
+ title="点击查看完整现状"
610
+ onClick={(e) => { e.stopPropagation(); setModal({ title: `主题「${t.name}」现状`, body: t.summary }); }}
611
+ >
561
612
  现状:{t.summary}
562
613
  </div>
563
614
  )}
@@ -712,12 +763,22 @@ export default function SessionTaskView() {
712
763
  </>
713
764
  ) : (
714
765
  <>
715
- {t.text}
766
+ <span
767
+ style={{ cursor: t.text.length > 50 ? 'zoom-in' : 'default' }}
768
+ onClick={(e) => {
769
+ e.stopPropagation();
770
+ if (t.text.length > 50) setModal({ title: `任务 #${t.id} 全文`, body: t.text });
771
+ }}
772
+ title={t.text.length > 50 ? '点击查看完整任务文本' : undefined}
773
+ >
774
+ {t.text}
775
+ </span>
716
776
  {t.roundSummary && (
717
777
  <div
718
778
  className="muted"
719
- style={{ fontSize: 11, marginTop: 4, paddingTop: 4, borderTop: '1px dashed var(--line,#e6e8eb)', whiteSpace: 'pre-wrap' }}
720
- title="该任务所回应的 agent 上一轮完成总结"
779
+ style={{ fontSize: 11, marginTop: 4, paddingTop: 4, borderTop: '1px dashed var(--line,#e6e8eb)', whiteSpace: 'pre-wrap', cursor: 'zoom-in' }}
780
+ title="点击查看完整上一轮总结"
781
+ onClick={(e) => { e.stopPropagation(); setModal({ title: '上一轮完成总结', body: t.roundSummary }); }}
721
782
  >
722
783
  上一轮完成:{t.roundSummary}
723
784
  </div>
@@ -777,6 +838,38 @@ export default function SessionTaskView() {
777
838
  )}
778
839
 
779
840
  <CliHints module="localtask" />
841
+
842
+ {modal && (
843
+ <div
844
+ onClick={() => setModal(null)}
845
+ style={{
846
+ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.4)',
847
+ display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000,
848
+ }}
849
+ >
850
+ <div
851
+ onClick={(e) => e.stopPropagation()}
852
+ style={{
853
+ background: '#fff', borderRadius: 8, padding: 20, maxWidth: 'min(720px, 90vw)',
854
+ maxHeight: '80vh', overflow: 'auto', boxShadow: '0 12px 40px rgba(0,0,0,0.25)',
855
+ }}
856
+ >
857
+ <div style={{ display: 'flex', alignItems: 'center', marginBottom: 12 }}>
858
+ <strong style={{ fontSize: 14 }}>{modal.title}</strong>
859
+ <button
860
+ className="btn small ghost"
861
+ onClick={() => setModal(null)}
862
+ style={{ marginLeft: 'auto' }}
863
+ >
864
+ 关闭
865
+ </button>
866
+ </div>
867
+ <div style={{ fontSize: 13, lineHeight: 1.6, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
868
+ {modal.body}
869
+ </div>
870
+ </div>
871
+ </div>
872
+ )}
780
873
  </>
781
874
  );
782
875
  }
@@ -92,6 +92,11 @@ export async function handleApi(req, res, url) {
92
92
  } else {
93
93
  Object.assign(raw, await readBody(req).catch(() => ({})));
94
94
  }
95
+ // stream 模式:把 req/res/url 透传给 run,让 action 自己接管响应(SSE 等)
96
+ if (action.stream) {
97
+ await action.run({ ...applySpec(action, raw), __raw: { req, res, url } }, { transport: 'http' });
98
+ return;
99
+ }
95
100
  const data = await action.run(applySpec(action, raw), { transport: 'http' });
96
101
  sendJson(res, 200, { ok: true, data });
97
102
  } catch (err) {
@@ -1 +1 @@
1
- import{o as e,s as t,t as n}from"./index-Iz1qH6TC.js";var r=t();function i({module:t}){let{boot:i}=e(),a=(i?.commands||[]).filter(e=>e.module===t);return a.length?(0,r.jsxs)(`div`,{className:`cli-hint`,children:[(0,r.jsx)(`span`,{className:`cli-hint-label`,children:`CLI 等价(同构命令,加 --json 得机器可读输出):`}),a.map(e=>(0,r.jsx)(n,{className:`cli-cmd`,text:e.command,title:`点击复制:${e.usage}`,children:e.command},e.id))]}):null}export{i as t};
1
+ import{o as e,s as t,t as n}from"./index-2OK9SG2y.js";var r=t();function i({module:t}){let{boot:i}=e(),a=(i?.commands||[]).filter(e=>e.module===t);return a.length?(0,r.jsxs)(`div`,{className:`cli-hint`,children:[(0,r.jsx)(`span`,{className:`cli-hint-label`,children:`CLI 等价(同构命令,加 --json 得机器可读输出):`}),a.map(e=>(0,r.jsx)(n,{className:`cli-cmd`,text:e.command,title:`点击复制:${e.usage}`,children:e.command},e.id))]}):null}export{i as t};