opencode-session-turns 0.1.13 → 0.1.14
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.
- package/README.md +2 -2
- package/dist/gui-experimental.cjs +45 -17
- package/package.json +1 -1
- package/src/gui.tsx +60 -24
package/README.md
CHANGED
|
@@ -22,8 +22,8 @@ click a turn to jump straight to that message.
|
|
|
22
22
|
- 列出当前会话的每一轮用户提问(从会话消息接口分页拉取,`type=user`)。
|
|
23
23
|
- 鼠标移到窗口右缘的细条上自动展开,移开自动收起;长历史跳转期间面板保持展开。
|
|
24
24
|
- 点击任意一轮,**跳转到对应消息**——包括很久以前、尚未渲染的历史轮次。
|
|
25
|
-
-
|
|
26
|
-
|
|
25
|
+
- 深历史需要逐页加载时,面板头部显示「加载历史中 · N 页 · Xs」,目标轮次带「加载中」
|
|
26
|
+
徽标并琥珀色脉冲;按 `Esc` 或点 × 可随时取消。
|
|
27
27
|
- **历史预热**:宿主自己加载历史每页之间有 2 秒硬冷却,深历史跳转因此要等好几秒。
|
|
28
28
|
轮次条会在你打开面板时(悬停或 `/turns`)后台把历史预热完并还原视口,
|
|
29
29
|
之后所有跳转都是瞬时的;也可以点面板上的「预热」手动触发。
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const
|
|
2
|
-
`).map(p=>p.trim()).find(p=>p.length>0);if(s)return s;const d=
|
|
1
|
+
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const o=require("solid-js/web"),q=require("@opencode/plugin/gui"),l=require("solid-js");var Le=o.template("<div class=sr-status>"),Xe=o.template('<span class="sr-title sr-progress">加载历史中'),Qe=o.template("<div class=sr-foot>"),et=o.template("<div class=sr-rail><div class=sr-head><button type=button class=sr-close>×</button></div><div class=sr-list>"),tt=o.template("<span class=sr-title>会话轮次"),rt=o.template("<div class=sr-note>"),nt=o.template("<span class=sr-badge>加载中"),st=o.template("<button type=button class=sr-turn><span class=sr-text><span class=sr-num>#</span><span class=sr-time></span></span><span class=sr-dash>");const De="v16",le="message-",Pe="session-turns:diag",ce="session-turns:open",ot=40,at=200,it=50,lt=30,ct=45e3,dt=2e4,ut=8e3,pt=12e3,ft=220,gt=800;let Be=0,de="",ue="",pe="";const je=a=>{const s=String(a?.text??"").split(`
|
|
2
|
+
`).map(p=>p.trim()).find(p=>p.length>0);if(s)return s;const d=a?.files?.length??0;return d>0?`[附件 ×${d}]`:"(空消息)"},Re=a=>String(a).padStart(2,"0"),ht=a=>{const s=new Date(a??0);return`${Re(s.getHours())}:${Re(s.getMinutes())}`},se=a=>new Promise(s=>setTimeout(s,a)),fe=()=>document.querySelector('[data-slot="session-timeline-scroll"] > .scroll-view__viewport')??document.querySelector('[data-slot="session-timeline-scroll"] [data-scrollable]')??(()=>{let s=document.querySelector("[data-message-id]")?.parentElement;for(;s;){const d=getComputedStyle(s);if(/(auto|scroll)/.test(d.overflowY)&&s.scrollHeight>s.clientHeight+1)return s;s=s.parentElement}})(),y=a=>{const s=document.getElementById(`${le}${a}`);if(s)return s;const d=`[data-message-id="${a.replace(/"/g,'\\"')}"]`;for(const p of document.querySelectorAll(d))if(p.getAttribute("data-timeline-row")==="UserMessage"||p.querySelector('[data-slot="user-message-text"]'))return p},oe=a=>{const s=fe(),d=[...document.querySelectorAll("[data-message-id]")],p=d[0]?.getAttribute("data-message-id")??"",S=pe?` · 目标 ${y(pe)?"在":"不在"} DOM`:"",$=p?` · 行ID ${p.slice(0,20)}`:"";let E="(空)";try{E=a.router.path()||"(空)"}catch{}return`轮次 ${Be} · 容器 ${s?"OK":"缺失"} · DOM ${d.length} 行${S}${$} · 路由 ${E} · hash ${window.location.hash||"(空)"}${ue?` · ${ue}`:""}${de?` · ${de}`:""}`},mt=a=>{const s=a.getBoundingClientRect().top;let d=s;for(const p of a.querySelectorAll(".sticky, [data-session-title]")){if(!(p instanceof HTMLElement)||getComputedStyle(p).position!=="sticky"&&!p.hasAttribute("data-session-title"))continue;const $=p.getBoundingClientRect();$.height!==0&&($.top>s+4||(d=Math.max(d,$.bottom)))}return d},ae=a=>{try{const s=fe();if(!s||!a.isConnected)return;const d=a.querySelector('[data-slot="user-message-text"]')??a,p=mt(s),S=d.getBoundingClientRect().top-(p+18);Math.abs(S)>2&&(s.scrollTop+=S),ue=`inset ${Math.round(p)} · delta ${Math.round(S)}`}catch{}},ie=a=>{ae(a),setTimeout(()=>ae(a),450),setTimeout(()=>ae(a),1e3)},qe=a=>{let s="";try{s=a.router.path()||""}catch{}return s.startsWith("/")?s:""},bt=a=>{const s=document.createElement("a");s.setAttribute("href",a),s.setAttribute("replace",""),s.setAttribute("noscroll",""),s.style.display="none",document.body.appendChild(s);try{s.dispatchEvent(new MouseEvent("click",{bubbles:!0,cancelable:!0,button:0,view:window}))}finally{s.remove()}},wt=`
|
|
3
3
|
.sr-rail {
|
|
4
4
|
position: fixed;
|
|
5
5
|
top: 50%;
|
|
@@ -60,7 +60,10 @@
|
|
|
60
60
|
flex-shrink: 0;
|
|
61
61
|
}
|
|
62
62
|
.sr-close:hover { opacity: 1; background: var(--surface-base-hover, rgba(127,127,127,.14)); }
|
|
63
|
-
/* the scrollable list; the header stays fixed above it
|
|
63
|
+
/* the scrollable list; the header stays fixed above it.
|
|
64
|
+
The collapsed rail must NOT be a scroll container: centering the content
|
|
65
|
+
inside a scroller overflows in both directions, so the browser clamps the
|
|
66
|
+
scroll offset and the list snaps to the top/bottom when the panel opens. */
|
|
64
67
|
.sr-list {
|
|
65
68
|
flex: 1;
|
|
66
69
|
min-height: 0;
|
|
@@ -68,11 +71,15 @@
|
|
|
68
71
|
flex-direction: column;
|
|
69
72
|
justify-content: center;
|
|
70
73
|
gap: 2px;
|
|
71
|
-
overflow
|
|
72
|
-
overflow-x: hidden;
|
|
74
|
+
overflow: hidden;
|
|
73
75
|
scrollbar-width: none;
|
|
74
76
|
}
|
|
75
|
-
.sr-rail[data-open="true"] .sr-list {
|
|
77
|
+
.sr-rail[data-open="true"] .sr-list {
|
|
78
|
+
justify-content: flex-start;
|
|
79
|
+
gap: 1px;
|
|
80
|
+
overflow-y: auto;
|
|
81
|
+
scrollbar-width: thin;
|
|
82
|
+
}
|
|
76
83
|
.sr-list::-webkit-scrollbar { display: none; }
|
|
77
84
|
.sr-rail[data-open="true"] .sr-list::-webkit-scrollbar { display: block; width: 8px; }
|
|
78
85
|
.sr-list::-webkit-scrollbar-track { background: transparent; }
|
|
@@ -94,9 +101,6 @@
|
|
|
94
101
|
color: inherit;
|
|
95
102
|
font: inherit;
|
|
96
103
|
border-radius: 4px;
|
|
97
|
-
/* long sessions: skip layout/paint for rows outside the panel viewport */
|
|
98
|
-
content-visibility: auto;
|
|
99
|
-
contain-intrinsic-size: 0 26px;
|
|
100
104
|
}
|
|
101
105
|
/* expanded: dash flips to a left-side indicator, content reads left-to-right */
|
|
102
106
|
.sr-rail[data-open="true"] .sr-turn {
|
|
@@ -192,6 +196,17 @@
|
|
|
192
196
|
box-shadow: inset 0 0 0 2px rgba(255,170,60,0), inset 0 0 0 rgba(255,170,60,0);
|
|
193
197
|
}
|
|
194
198
|
}
|
|
199
|
+
.sr-badge {
|
|
200
|
+
font-size: 10px;
|
|
201
|
+
line-height: 1.4;
|
|
202
|
+
padding: 0 5px;
|
|
203
|
+
border-radius: 4px;
|
|
204
|
+
color: rgba(255,170,60,.95);
|
|
205
|
+
background: rgba(255,170,60,.14);
|
|
206
|
+
border: 1px solid rgba(255,170,60,.35);
|
|
207
|
+
flex-shrink: 0;
|
|
208
|
+
white-space: nowrap;
|
|
209
|
+
}
|
|
195
210
|
.sr-note {
|
|
196
211
|
display: none;
|
|
197
212
|
font-size: 11px;
|
|
@@ -199,19 +214,32 @@
|
|
|
199
214
|
padding: 2px;
|
|
200
215
|
}
|
|
201
216
|
.sr-rail[data-open="true"] .sr-note { display: block; }
|
|
217
|
+
.sr-foot {
|
|
218
|
+
flex-shrink: 0;
|
|
219
|
+
font-size: 11px;
|
|
220
|
+
line-height: 1.5;
|
|
221
|
+
color: rgba(255,170,60,.92);
|
|
222
|
+
padding: 6px 2px 0;
|
|
223
|
+
margin-top: 6px;
|
|
224
|
+
border-top: 1px solid rgba(127,127,127,.2);
|
|
225
|
+
white-space: pre-wrap;
|
|
226
|
+
max-height: 64px;
|
|
227
|
+
overflow: hidden;
|
|
228
|
+
}
|
|
202
229
|
.sr-status {
|
|
203
230
|
position: fixed;
|
|
204
|
-
right:
|
|
205
|
-
|
|
231
|
+
right: 42px;
|
|
232
|
+
top: 50%;
|
|
233
|
+
transform: translateY(-50%);
|
|
206
234
|
z-index: 401;
|
|
207
235
|
font-size: 11px;
|
|
208
|
-
|
|
236
|
+
line-height: 1.5;
|
|
237
|
+
padding: 4px 9px;
|
|
209
238
|
border-radius: 6px;
|
|
210
|
-
background: var(--background-strong, rgba(0,0,0,.
|
|
239
|
+
background: var(--background-strong, rgba(0,0,0,.82));
|
|
211
240
|
color: var(--text-base, #fff);
|
|
212
|
-
|
|
213
|
-
pointer-events: none;
|
|
241
|
+
border: 1px solid rgba(127,127,127,.25);
|
|
214
242
|
white-space: pre-wrap;
|
|
215
|
-
max-width:
|
|
243
|
+
max-width: 260px;
|
|
216
244
|
}
|
|
217
|
-
`;function
|
|
245
|
+
`;function xt(a){const s=q.usePlugin(),[d,p]=l.createSignal([]),[S,$]=l.createSignal(""),[E,I]=l.createSignal(!1),[Je,Oe]=l.createSignal(""),[L,ge]=l.createSignal(""),[x,B]=l.createSignal(!1),[he,D]=l.createSignal(0),[me,P]=l.createSignal(0),[yt,J]=l.createSignal(0),[O,N]=l.createSignal(""),_=new Map;let W=0,H=!1,z=!1,V="",j;const v=()=>a.session()?.id,Y=()=>{const e=[..._.values()].sort((r,i)=>r.created-i.created),t=l.untrack(d);t.length===e.length&&t.every((r,i)=>r===e[i])||(p(e),Be=e.length)},be=e=>{const t=_.get(e.id);t&&t.created===e.time&&t.text===e.text||_.set(e.id,{id:e.id,created:e.time,text:e.text})},Ne=async(e,t,r)=>{const i=r?{sessionID:e,type:"user",limit:t,cursor:r}:{sessionID:e,type:"user",limit:t,order:"desc"};return await s.client.message.list(i)},K=async(e,t)=>{const r=++W;H=!0;let i="";try{let n;const c=t==="full"?ot:1,u=t==="full"?at:it;let g=t!=="full";for(let b=0;b<c;b++){let m;try{m=await Ne(e,u,n)}catch(h){i=String(h?.message??h);break}if(r!==W)return;g||(_.clear(),g=!0);let w=0;for(const h of m?.data??[])_.has(h.id)||(be({id:h.id,time:h.time?.created??0,text:je(h)}),w++);Y();const f=m?.cursor?.next;if(!f||w===0)break;n=f}}finally{H=!1,V=e,de=i,Oe(_.size===0?i||"暂无用户消息":""),z&&(z=!1,we())}},we=()=>{j||(j=setTimeout(()=>{j=void 0;const e=l.untrack(v);e&&K(e,"light")},300))},xe=e=>{if(!(!e||e!==l.untrack(v))){if(H){z=!0;return}we()}},He=e=>{const t=e?.type;if(t==="message.updated"){const r=e?.data??{},i=r.info;i?.role==="user"&&i?.id&&(be({id:i.id,time:i.time?.created??0,text:je(i)}),Y()),xe(r.sessionID)}else if(t==="message.removed"){const r=e?.data??{};r.messageID&&_.delete(r.messageID)&&Y(),xe(r.sessionID)}else if(t==="location.shutdown"){const r=l.untrack(v);r&&V===r&&K(r,"full")}};l.onMount(()=>{let e=!1,t=0,r;(async()=>{for(;!e;){const c=s.client.event.subscribe()[Symbol.asyncIterator]();try{for(;!e;){const u=await c.next();if(u.done)break;t=0,He(u.value)}}catch{}try{await c.return?.()}catch{}if(e)break;t++,await se(Math.min(2e3*t,15e3))}})(),l.onCleanup(()=>{e=!0,clearTimeout(r),j&&clearTimeout(j)})}),l.createEffect(()=>{const e=v();e&&l.untrack(()=>{V!==e&&(W++,H=!1,z=!1,_.clear(),p([]),$(""),X="",K(e,"full"))})});let F;const C=()=>(F?.isConnected||(F=fe()),F),ze=()=>{const e=C();if(!e)return;const t=e.querySelectorAll("[data-message-id]");if(t.length===0)return;const r=e.getBoundingClientRect().top+40;for(const i of t)if(i.getBoundingClientRect().bottom>=r){const n=i.getAttribute("data-message-id");n&&$(n);break}};let Z=!1;const ve=()=>{Z||(Z=!0,requestAnimationFrame(()=>{Z=!1,ze()}))};l.onMount(()=>{const e=()=>ve();document.addEventListener("scroll",e,!0);const t=setInterval(ve,1500);l.onCleanup(()=>{document.removeEventListener("scroll",e,!0),clearInterval(t)})});let ye=0;const U=(e,t=2600)=>{const r=++ye;ge(e),setTimeout(()=>{r===ye&&ge("")},t)};l.onMount(()=>{const e=r=>{if(r.key==="Escape"){if(l.untrack(x)){Ee(),r.stopPropagation();return}l.untrack(E)&&(I(!1),r.stopPropagation())}},t=r=>{if(!l.untrack(E))return;(r.composedPath?.()??[]).some(c=>c instanceof Element&&c.closest?.(".sr-rail"))||I(!1)};window.addEventListener("keydown",e,!0),document.addEventListener("pointerdown",t,!0),l.onCleanup(()=>{window.removeEventListener("keydown",e,!0),document.removeEventListener("pointerdown",t,!0)})}),l.onMount(()=>{const e=()=>U(`${De} · ${oe(s)}`),t=()=>{I(r=>!r),Ce()};window.addEventListener(Pe,e),window.addEventListener(ce,t),l.onCleanup(()=>{window.removeEventListener(Pe,e),window.removeEventListener(ce,t)})});let X="",T=0,ke=0,Se="";const Q=e=>{try{const t=e.querySelector('[data-slot="user-message-text"]')??e.querySelector('[data-slot="user-message-body"]')??e;t.classList.remove("sr-flash-ring"),t.offsetWidth,t.classList.add("sr-flash-ring"),setTimeout(()=>t.classList.remove("sr-flash-ring"),1900)}catch{}},Fe=()=>{for(const e of d())if(y(e.id))return e},ee=(e,t)=>{bt(`${e}#${le}${t}`),X=t},$e=()=>{const t=C()?.querySelectorAll("[data-message-id]")??[];if(t.length===0)return"0";const r=t[0].getAttribute("data-message-id")??"",i=t[t.length-1].getAttribute("data-message-id")??"";return`${t.length}:${r}:${i}`},Ue=(e,t,r,i)=>new Promise(n=>{let c=!1,u,g,b;const m=Date.now();let f=$e(),h=m,A=0;const k=G=>{if(!c){c=!0;try{u?.disconnect()}catch{}clearInterval(g),clearTimeout(b),n(G??null)}},M=G=>{if(t!==T)return k(null);const Ae=G?y(e):document.getElementById(`${le}${e}`);if(Ae)return k(Ae);if(!G)return;const Me=$e(),Ie=Date.now();if(Me!==f){f=Me,h=Ie,A++,i?.(A);return}const Ze=A>0?ut:pt;Ie-h>Ze&&k(null)},Te=y(e);if(Te)return k(Te);u=new MutationObserver(()=>M(!1)),u.observe(document.documentElement,{childList:!0,subtree:!0}),g=setInterval(()=>M(!0),120),b=setTimeout(()=>k(y(e)),Math.max(400,r))}),Ee=()=>{T++,B(!1),D(0),P(0),J(0),N(""),U("已取消跳转")},Ge=async e=>{if(te()&&Ve(),x()&&O()===e.id)return;const t=++T;B(!1),D(0),P(0),J(0),N(""),pe=e.id;const r=Date.now();if(e.id===Se&&r-ke<gt){const w=y(e.id),f=C();if(w&&f){ie(w),Q(w);return}}ke=r,Se=e.id;const i=C(),n=y(e.id);if(n&&i){ie(n),Q(n);return}const c=qe(s);if(!c){U(`无法读取当前路由,无法定位 · ${oe(s)}`);return}const u=v();B(!0),N(e.id),D(0),P(0),I(!0);const g=Date.now(),b=setInterval(()=>{P(Math.round((Date.now()-g)/1e3))},500);let m=0;try{const w=Date.now()+ct;for(let f=0;f<lt&&Date.now()<w;f++){if(t!==T||v()!==u)return;if(J(f+1),X===e.id){const M=Fe()??d()[0];M&&(ee(c,M.id),await se(ft))}if(t!==T||v()!==u)return;ee(c,e.id);let h=0;const A=Math.min(dt,w-Date.now()),k=await Ue(e.id,t,A,M=>{h=M,D(m+M)});if(m+=h,t!==T||v()!==u)return;if(k){ie(k),Q(k);return}}t===T&&U(`未能定位 · ${oe(s)}`)}finally{clearInterval(b),t===T&&(B(!1),D(0),P(0),J(0),N(""))}},[te,re]=l.createSignal(!1);let R=0,ne="";const We=()=>{const e=C();if(!e)return;const t=e.getBoundingClientRect();for(const r of e.querySelectorAll("[data-message-id]")){const i=r.getBoundingClientRect();if(i.bottom<=t.top+1)continue;const n=r.getAttribute("data-message-id");if(n)return{el:r,id:n,offset:i.top-t.top}}},_e=e=>{if(!e)return;const t=C();if(!t)return;const r=e.el?.isConnected?e.el:y(e.id);if(!r)return;const i=t.getBoundingClientRect(),n=r.getBoundingClientRect().top-i.top-e.offset;Math.abs(n)>2&&(t.scrollTop+=n)},Ve=()=>{R++,re(!1)},Ce=()=>{const e=v();!e||te()||ne!==e&&(d().length<15||Ye())},Ye=async()=>{if(te())return;const e=qe(s),t=d()[0],r=v();if(!e||!t||!r||ne===r&&y(t.id))return;const i=++R,n=We(),c=C(),u=c?c.scrollTop+c.clientHeight>=c.scrollHeight-32:!0;re(!0);try{ee(e,t.id);const g=Date.now()+12e4,b=setInterval(()=>_e(n),150);try{for(;Date.now()<g&&i===R&&!y(t.id);)await se(250)}finally{clearInterval(b)}if(i!==R)return;if(_e(n),u){const m=C();m&&(m.scrollTop=m.scrollHeight)}ne=r}finally{i===R&&re(!1)}};l.createEffect(()=>{x()&&O()&&requestAnimationFrame(()=>{try{document.querySelector('.sr-list .sr-turn[data-busy="true"]')?.scrollIntoView({block:"nearest"})}catch{}})});const Ke=[o.createComponent(l.Show,{get when(){return L()},get children(){var e=Le();return o.insert(e,L),e}}),(()=>{var e=et(),t=e.firstChild,r=t.firstChild,i=t.nextSibling;return e.addEventListener("mouseleave",()=>{x()||I(!1)}),e.addEventListener("mouseenter",()=>{I(!0),Ce()}),o.insert(t,o.createComponent(l.Show,{get when(){return x()},get fallback(){return(()=>{var n=tt();return n.firstChild,o.insert(n,(()=>{var c=o.memo(()=>d().length>0);return()=>c()?` · ${d().length}`:""})(),null),n})()},get children(){var n=Xe();return n.firstChild,o.insert(n,(()=>{var c=o.memo(()=>he()>0);return()=>c()?` · ${he()} 页`:""})(),null),o.insert(n,(()=>{var c=o.memo(()=>me()>0);return()=>c()?` · ${me()}s`:""})(),null),n}}),r),r.$$click=()=>{x()?Ee():I(!1)},o.insert(i,o.createComponent(l.Show,{get when(){return d().length>0},get fallback(){return(()=>{var n=rt();return o.insert(n,()=>Je()||`… ${De}`),n})()},get children(){return o.createComponent(l.For,{get each(){return d()},children:(n,c)=>(()=>{var u=st(),g=u.firstChild,b=g.firstChild;b.firstChild;var m=b.nextSibling,w=g.nextSibling;return u.$$click=()=>{Ge(n)},o.insert(b,()=>c()+1,null),o.insert(m,()=>ht(n.created)),o.insert(g,()=>n.text,null),o.insert(u,o.createComponent(l.Show,{get when(){return o.memo(()=>!!x())()&&O()===n.id},get children(){return nt()}}),w),o.effect(f=>{var h=S()===n.id?"true":"false",A=x()&&O()===n.id?"true":"false";return h!==f.e&&o.setAttribute(u,"data-active",f.e=h),A!==f.t&&o.setAttribute(u,"data-busy",f.t=A),f},{e:void 0,t:void 0}),u})()})}})),o.insert(e,o.createComponent(l.Show,{get when(){return o.memo(()=>!!E())()&&L()},get children(){var n=Qe();return o.insert(n,L),n}}),null),o.effect(n=>{var c=E()?"true":"false",u=x()?"true":"false",g=x()?"取消跳转 (Esc)":"关闭 (Esc)";return c!==n.e&&o.setAttribute(e,"data-open",n.e=c),u!==n.t&&o.setAttribute(e,"data-jumping",n.t=u),g!==n.a&&o.setAttribute(r,"title",n.a=g),n},{e:void 0,t:void 0,a:void 0}),e})(),o.createComponent(l.Show,{get when(){return o.memo(()=>!E())()&&L()},get children(){var e=Le();return o.insert(e,L),e}})];return o.createComponent(o.Portal,{get mount(){return document.body},children:Ke})}const vt=q.Plugin.define({i18n:{en:{"command.jump":"Session turns: show / jump","command.jump.description":"Reveal the turn rail; click a turn to jump to that message"}},setup(a){a.add(q.Style,wt),a.add(q.Slot,{at:"window.bottom",render:()=>o.createComponent(l.Show,{get when(){return a.sessions.current()},get children(){return o.createComponent(xt,{session:()=>a.sessions.current()})}})}),a.add(q.Command,()=>({id:"jump",title:a.t("command.jump"),description:a.t("command.jump.description"),group:a.t("command.jump"),section:"navigation",bind:"mod+alt+r",slash:{name:"turns"},run:()=>{window.dispatchEvent(new Event(ce))}}))}});o.delegateEvents(["click"]);exports.default=vt;
|
package/package.json
CHANGED
package/src/gui.tsx
CHANGED
|
@@ -253,7 +253,10 @@ const CSS = `
|
|
|
253
253
|
flex-shrink: 0;
|
|
254
254
|
}
|
|
255
255
|
.sr-close:hover { opacity: 1; background: var(--surface-base-hover, rgba(127,127,127,.14)); }
|
|
256
|
-
/* the scrollable list; the header stays fixed above it
|
|
256
|
+
/* the scrollable list; the header stays fixed above it.
|
|
257
|
+
The collapsed rail must NOT be a scroll container: centering the content
|
|
258
|
+
inside a scroller overflows in both directions, so the browser clamps the
|
|
259
|
+
scroll offset and the list snaps to the top/bottom when the panel opens. */
|
|
257
260
|
.sr-list {
|
|
258
261
|
flex: 1;
|
|
259
262
|
min-height: 0;
|
|
@@ -261,11 +264,15 @@ const CSS = `
|
|
|
261
264
|
flex-direction: column;
|
|
262
265
|
justify-content: center;
|
|
263
266
|
gap: 2px;
|
|
264
|
-
overflow
|
|
265
|
-
overflow-x: hidden;
|
|
267
|
+
overflow: hidden;
|
|
266
268
|
scrollbar-width: none;
|
|
267
269
|
}
|
|
268
|
-
.sr-rail[data-open="true"] .sr-list {
|
|
270
|
+
.sr-rail[data-open="true"] .sr-list {
|
|
271
|
+
justify-content: flex-start;
|
|
272
|
+
gap: 1px;
|
|
273
|
+
overflow-y: auto;
|
|
274
|
+
scrollbar-width: thin;
|
|
275
|
+
}
|
|
269
276
|
.sr-list::-webkit-scrollbar { display: none; }
|
|
270
277
|
.sr-rail[data-open="true"] .sr-list::-webkit-scrollbar { display: block; width: 8px; }
|
|
271
278
|
.sr-list::-webkit-scrollbar-track { background: transparent; }
|
|
@@ -287,9 +294,6 @@ const CSS = `
|
|
|
287
294
|
color: inherit;
|
|
288
295
|
font: inherit;
|
|
289
296
|
border-radius: 4px;
|
|
290
|
-
/* long sessions: skip layout/paint for rows outside the panel viewport */
|
|
291
|
-
content-visibility: auto;
|
|
292
|
-
contain-intrinsic-size: 0 26px;
|
|
293
297
|
}
|
|
294
298
|
/* expanded: dash flips to a left-side indicator, content reads left-to-right */
|
|
295
299
|
.sr-rail[data-open="true"] .sr-turn {
|
|
@@ -385,6 +389,17 @@ const CSS = `
|
|
|
385
389
|
box-shadow: inset 0 0 0 2px rgba(255,170,60,0), inset 0 0 0 rgba(255,170,60,0);
|
|
386
390
|
}
|
|
387
391
|
}
|
|
392
|
+
.sr-badge {
|
|
393
|
+
font-size: 10px;
|
|
394
|
+
line-height: 1.4;
|
|
395
|
+
padding: 0 5px;
|
|
396
|
+
border-radius: 4px;
|
|
397
|
+
color: rgba(255,170,60,.95);
|
|
398
|
+
background: rgba(255,170,60,.14);
|
|
399
|
+
border: 1px solid rgba(255,170,60,.35);
|
|
400
|
+
flex-shrink: 0;
|
|
401
|
+
white-space: nowrap;
|
|
402
|
+
}
|
|
388
403
|
.sr-note {
|
|
389
404
|
display: none;
|
|
390
405
|
font-size: 11px;
|
|
@@ -392,20 +407,33 @@ const CSS = `
|
|
|
392
407
|
padding: 2px;
|
|
393
408
|
}
|
|
394
409
|
.sr-rail[data-open="true"] .sr-note { display: block; }
|
|
410
|
+
.sr-foot {
|
|
411
|
+
flex-shrink: 0;
|
|
412
|
+
font-size: 11px;
|
|
413
|
+
line-height: 1.5;
|
|
414
|
+
color: rgba(255,170,60,.92);
|
|
415
|
+
padding: 6px 2px 0;
|
|
416
|
+
margin-top: 6px;
|
|
417
|
+
border-top: 1px solid rgba(127,127,127,.2);
|
|
418
|
+
white-space: pre-wrap;
|
|
419
|
+
max-height: 64px;
|
|
420
|
+
overflow: hidden;
|
|
421
|
+
}
|
|
395
422
|
.sr-status {
|
|
396
423
|
position: fixed;
|
|
397
|
-
right:
|
|
398
|
-
|
|
424
|
+
right: 42px;
|
|
425
|
+
top: 50%;
|
|
426
|
+
transform: translateY(-50%);
|
|
399
427
|
z-index: 401;
|
|
400
428
|
font-size: 11px;
|
|
401
|
-
|
|
429
|
+
line-height: 1.5;
|
|
430
|
+
padding: 4px 9px;
|
|
402
431
|
border-radius: 6px;
|
|
403
|
-
background: var(--background-strong, rgba(0,0,0,.
|
|
432
|
+
background: var(--background-strong, rgba(0,0,0,.82));
|
|
404
433
|
color: var(--text-base, #fff);
|
|
405
|
-
|
|
406
|
-
pointer-events: none;
|
|
434
|
+
border: 1px solid rgba(127,127,127,.25);
|
|
407
435
|
white-space: pre-wrap;
|
|
408
|
-
max-width:
|
|
436
|
+
max-width: 260px;
|
|
409
437
|
}
|
|
410
438
|
`
|
|
411
439
|
|
|
@@ -851,12 +879,9 @@ function Rail(props) {
|
|
|
851
879
|
|
|
852
880
|
// Clicking the same turn again while its jump is running must NOT restart
|
|
853
881
|
// the loop: every restart cancels the host's in-flight page load, which is
|
|
854
|
-
// what makes a deep jump advance only one page per click.
|
|
855
|
-
// progress
|
|
856
|
-
if (jumping() && busyID() === turn.id)
|
|
857
|
-
flash(`正在跳转 · 已加载 ${jumpPages()} 页 · ${jumpElapsed()}s`, JUMP_TOTAL_MS)
|
|
858
|
-
return
|
|
859
|
-
}
|
|
882
|
+
// what makes a deep jump advance only one page per click. The header
|
|
883
|
+
// already shows the progress, so just leave it running.
|
|
884
|
+
if (jumping() && busyID() === turn.id) return
|
|
860
885
|
|
|
861
886
|
const token = ++jumpToken
|
|
862
887
|
// A new click always owns the progress UI. This also clears a previous
|
|
@@ -920,9 +945,8 @@ function Rail(props) {
|
|
|
920
945
|
setJumpElapsed(0)
|
|
921
946
|
// Pin the panel open even if the pointer has left it meanwhile.
|
|
922
947
|
setOpen(true)
|
|
923
|
-
//
|
|
924
|
-
//
|
|
925
|
-
flash(warming() ? "正在预热历史,请稍候…" : "正在加载历史…", 5000)
|
|
948
|
+
// The loading state is shown in the panel header and on the target row -
|
|
949
|
+
// no toast needed, the toast would land outside the user's focus.
|
|
926
950
|
const startedAt = Date.now()
|
|
927
951
|
const elapsedTimer = setInterval(() => {
|
|
928
952
|
setJumpElapsed(Math.round((Date.now() - startedAt) / 1000))
|
|
@@ -1120,7 +1144,7 @@ function Rail(props) {
|
|
|
1120
1144
|
}
|
|
1121
1145
|
>
|
|
1122
1146
|
<span class="sr-title sr-progress">
|
|
1123
|
-
|
|
1147
|
+
加载历史中{jumpPages() > 0 ? ` · ${jumpPages()} 页` : ""}
|
|
1124
1148
|
{jumpElapsed() > 0 ? ` · ${jumpElapsed()}s` : ""}
|
|
1125
1149
|
</span>
|
|
1126
1150
|
</Show>
|
|
@@ -1152,13 +1176,25 @@ function Rail(props) {
|
|
|
1152
1176
|
<span class="sr-time">{formatTime(turn.created)}</span>
|
|
1153
1177
|
{turn.text}
|
|
1154
1178
|
</span>
|
|
1179
|
+
<Show when={jumping() && busyID() === turn.id}>
|
|
1180
|
+
<span class="sr-badge">加载中</span>
|
|
1181
|
+
</Show>
|
|
1155
1182
|
<span class="sr-dash" />
|
|
1156
1183
|
</button>
|
|
1157
1184
|
)}
|
|
1158
1185
|
</For>
|
|
1159
1186
|
</Show>
|
|
1160
1187
|
</div>
|
|
1188
|
+
{/* Messages that need attention (errors, cancel confirmations) live in
|
|
1189
|
+
the panel footer while it is open, and next to the collapsed rail
|
|
1190
|
+
when it is closed - the old bottom-right toast was easy to miss. */}
|
|
1191
|
+
<Show when={open() && status()}>
|
|
1192
|
+
<div class="sr-foot">{status()}</div>
|
|
1193
|
+
</Show>
|
|
1161
1194
|
</div>
|
|
1195
|
+
<Show when={!open() && status()}>
|
|
1196
|
+
<div class="sr-status">{status()}</div>
|
|
1197
|
+
</Show>
|
|
1162
1198
|
</>
|
|
1163
1199
|
)
|
|
1164
1200
|
|