opencode-session-turns 0.1.3 → 0.1.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.
- package/dist/gui-experimental.cjs +3 -3
- package/package.json +1 -1
- package/src/gui.tsx +10 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const a=require("solid-js/web"),
|
|
2
|
-
`).map(
|
|
1
|
+
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const a=require("solid-js/web"),L=require("@opencode/plugin/gui"),i=require("solid-js");var Lt=a.template("<div class=sr-status>"),It=a.template('<span class="sr-title sr-progress">跳转中'),Mt=a.template("<div class=sr-rail><div class=sr-head><button type=button class=sr-close>×</button></div><div class=sr-list>"),Pt=a.template("<span class=sr-title>会话轮次"),Dt=a.template("<div class=sr-note>"),jt=a.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 G="v12",wt="message-",ht="session-turns:diag",Y="session-turns:open",qt=40,Rt=200,Ot=50,Jt=120,V=45e3,Nt=4e3,zt=300,Ft=800;let xt=0,Z="",X="";const gt=o=>{const s=String(o?.text??"").split(`
|
|
2
|
+
`).map(b=>b.trim()).find(b=>b.length>0);if(s)return s;const d=o?.files?.length??0;return d>0?`[附件 ×${d}]`:"(空消息)"},mt=o=>String(o).padStart(2,"0"),Ht=o=>{const s=new Date(o??0);return`${mt(s.getHours())}:${mt(s.getMinutes())}`},bt=o=>new Promise(s=>setTimeout(s,o)),vt=()=>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}})(),$=o=>document.getElementById(`${wt}${o}`)??[...document.querySelectorAll("[data-message-id]")].find(s=>s.getAttribute("data-message-id")===o),W=o=>{const s=vt(),d=[...document.querySelectorAll("[data-message-id]")],b=d[0]?.getAttribute("data-message-id")??"",R=X?` · 目标 ${$(X)?"在":"不在"} DOM`:"",I=b?` · 行ID ${b.slice(0,20)}`:"";let E="(空)";try{E=o.router.path()||"(空)"}catch{}return`轮次 ${xt} · 容器 ${s?"OK":"缺失"} · DOM ${d.length} 行${R}${I} · 路由 ${E} · hash ${window.location.hash||"(空)"}${Z?` · ${Z}`:""}`},K=(o,s)=>{s.scrollIntoView({block:"start"});const d=o.querySelector("[data-session-title]"),b=d instanceof HTMLElement?d.offsetHeight:0;b&&(o.scrollTop-=b+12)},Ut=o=>{let s="";try{s=o.router.path()||""}catch{}return s.startsWith("/")?s:""},Bt=o=>{const s=document.createElement("a");s.setAttribute("href",o),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()}},Gt=`
|
|
3
3
|
.sr-rail {
|
|
4
4
|
position: fixed;
|
|
5
5
|
top: 50%;
|
|
@@ -215,4 +215,4 @@
|
|
|
215
215
|
white-space: pre-wrap;
|
|
216
216
|
max-width: min(720px, 90vw);
|
|
217
217
|
}
|
|
218
|
-
`;function Vt(o){const
|
|
218
|
+
`;function Vt(o){const s=L.usePlugin(),[d,b]=i.createSignal([]),[R,I]=i.createSignal(""),[E,S]=i.createSignal(!1),[yt,kt]=i.createSignal(""),[Q,tt]=i.createSignal(""),[v,M]=i.createSignal(!1),[et,T]=i.createSignal(0),[rt,P]=i.createSignal(""),y=new Map;let O=0,D=!1,j=!1,J="",A;const _=()=>o.session()?.id,N=()=>{const t=[...y.values()].sort((r,l)=>r.created-l.created),e=i.untrack(d);e.length===t.length&&e.every((r,l)=>r===t[l])||(b(t),xt=t.length)},st=t=>{const e=y.get(t.id);e&&e.created===t.time&&e.text===t.text||y.set(t.id,{id:t.id,created:t.time,text:t.text})},St=async(t,e,r)=>{const l=r?{sessionID:t,type:"user",limit:e,cursor:r}:{sessionID:t,type:"user",limit:e,order:"desc"};return await s.client.message.list(l)},z=async(t,e)=>{const r=++O;D=!0;let l="";try{let n;const c=e==="full"?qt:1,u=e==="full"?Rt:Ot;let p=e!=="full";for(let h=0;h<c;h++){let x;try{x=await St(t,u,n)}catch(m){l=String(m?.message??m);break}if(r!==O)return;p||(y.clear(),p=!0);let f=0;for(const m of x?.data??[])y.has(m.id)||(st({id:m.id,time:m.time?.created??0,text:gt(m)}),f++);N();const g=x?.cursor?.next;if(!g||f===0)break;n=g}}finally{D=!1,J=t,Z=l,kt(y.size===0?l||"暂无用户消息":""),j&&(j=!1,nt())}},nt=()=>{A||(A=setTimeout(()=>{A=void 0;const t=i.untrack(_);t&&z(t,"light")},300))},at=t=>{if(!(!t||t!==i.untrack(_))){if(D){j=!0;return}nt()}},$t=t=>{const e=t?.type;if(e==="message.updated"){const r=t?.data??{},l=r.info;l?.role==="user"&&l?.id&&(st({id:l.id,time:l.time?.created??0,text:gt(l)}),N()),at(r.sessionID)}else if(e==="message.removed"){const r=t?.data??{};r.messageID&&y.delete(r.messageID)&&N(),at(r.sessionID)}else if(e==="location.shutdown"){const r=i.untrack(_);r&&J===r&&z(r,"full")}};i.onMount(()=>{let t=!1,e=0,r;(async()=>{for(;!t;){const c=s.client.event.subscribe()[Symbol.asyncIterator]();try{for(;!t;){const u=await c.next();if(u.done)break;e=0,$t(u.value)}}catch{}try{await c.return?.()}catch{}if(t)break;e++,await bt(Math.min(2e3*e,15e3))}})(),i.onCleanup(()=>{t=!0,clearTimeout(r),A&&clearTimeout(A)})}),i.createEffect(()=>{const t=_();t&&i.untrack(()=>{J!==t&&(O++,D=!1,j=!1,y.clear(),b([]),I(""),H="",z(t,"full"))})});let q;const C=()=>(q?.isConnected||(q=vt()),q),Et=()=>{const t=C();if(!t)return;const e=t.querySelectorAll("[data-message-id]");if(e.length===0)return;const r=t.getBoundingClientRect().top+40;for(const l of e)if(l.getBoundingClientRect().bottom>=r){const n=l.getAttribute("data-message-id");n&&I(n);break}};let F=!1;const ot=()=>{F||(F=!0,requestAnimationFrame(()=>{F=!1,Et()}))};i.onMount(()=>{const t=()=>ot();document.addEventListener("scroll",t,!0);const e=setInterval(ot,1500);i.onCleanup(()=>{document.removeEventListener("scroll",t,!0),clearInterval(e)})});let it=0;const w=(t,e=2600)=>{const r=++it;tt(t),setTimeout(()=>{r===it&&tt("")},e)};i.onMount(()=>{const t=r=>{if(r.key==="Escape"){if(i.untrack(v)){pt(),r.stopPropagation();return}i.untrack(E)&&(S(!1),r.stopPropagation())}},e=r=>{if(!i.untrack(E))return;(r.composedPath?.()??[]).some(c=>c instanceof Element&&c.closest?.(".sr-rail"))||S(!1)};window.addEventListener("keydown",t,!0),document.addEventListener("pointerdown",e,!0),i.onCleanup(()=>{window.removeEventListener("keydown",t,!0),document.removeEventListener("pointerdown",e,!0)})}),i.onMount(()=>{const t=()=>w(`${G} · ${W(s)}`),e=()=>S(r=>!r);window.addEventListener(ht,t),window.addEventListener(Y,e),i.onCleanup(()=>{window.removeEventListener(ht,t),window.removeEventListener(Y,e)}),w(`会话轮次 ${G} 已加载`)});let H="",k=0,lt=0,ct="";const U=t=>{try{const e=t.querySelector('[data-slot="user-message-text"]')??t.querySelector('[data-slot="user-message-body"]')??t;e.classList.remove("sr-flash-ring"),e.offsetWidth,e.classList.add("sr-flash-ring"),setTimeout(()=>e.classList.remove("sr-flash-ring"),1900)}catch{}},_t=()=>{for(const t of d())if($(t.id))return t},dt=(t,e)=>{Bt(`${t}#${wt}${e}`),H=e},ut=()=>{const e=C()?.querySelectorAll("[data-message-id]")??[];if(e.length===0)return"0";const r=e[0].getAttribute("data-message-id")??"",l=e[e.length-1].getAttribute("data-message-id")??"";return`${e.length}:${r}:${l}`},Tt=(t,e,r)=>new Promise(l=>{let n=!1,c,u,p;const h=Date.now(),x=ut(),f=B=>{if(!n){n=!0;try{c?.disconnect()}catch{}clearInterval(u),clearTimeout(p),l(B??null)}},g=B=>{if(e!==k)return f(null);const ft=$(t);if(ft)return f(ft);B&&Date.now()-h>zt&&ut()!==x&&f(null)},m=$(t);if(m)return f(m);c=new MutationObserver(()=>g(!1)),c.observe(document.documentElement,{childList:!0,subtree:!0}),u=setInterval(()=>g(!0),120),p=setTimeout(()=>f($(t)),Math.max(400,r))}),pt=()=>{k++,M(!1),T(0),P(""),w("已取消跳转")},At=async t=>{const e=++k;M(!1),T(0),P(""),X=t.id;const r=Date.now();if(t.id===ct&&r-lt<Ft){const p=$(t.id),h=C();if(p&&h){K(h,p),U(p);return}}lt=r,ct=t.id;const l=C(),n=$(t.id);if(n&&l){K(l,n),U(n),w("已定位");return}const c=Ut(s);if(!c){w(`无法读取当前路由,无法定位 · ${W(s)}`);return}const u=_();M(!0),P(t.id),T(0),S(!0),w("正在定位…",V);try{const p=Date.now()+V;for(let h=0;h<Jt&&Date.now()<p;h++){if(e!==k||_()!==u)return;if(T(h+1),w(`跳转中 · 第 ${h+1} 页`,V),H===t.id){const g=_t()??d()[0];g&&(dt(c,g.id),await bt(120))}if(e!==k)return;dt(c,t.id);const x=Math.min(Nt,p-Date.now()),f=await Tt(t.id,e,x);if(e!==k)return;if(f){const g=C()??l;g&&K(g,f),U(f),w("已定位");return}}e===k&&w(`未能定位 · ${W(s)}`)}finally{e===k&&(M(!1),T(0),P(""))}};i.createEffect(()=>{v()&&rt()&&requestAnimationFrame(()=>{try{document.querySelector('.sr-list .sr-turn[data-busy="true"]')?.scrollIntoView({block:"nearest"})}catch{}})});const Ct=[a.createComponent(i.Show,{get when(){return Q()},get children(){var t=Lt();return a.insert(t,Q),t}}),(()=>{var t=Mt(),e=t.firstChild,r=e.firstChild,l=e.nextSibling;return t.addEventListener("mouseleave",()=>{v()||S(!1)}),t.addEventListener("mouseenter",()=>S(!0)),a.insert(e,a.createComponent(i.Show,{get when(){return v()},get fallback(){return(()=>{var n=Pt();return n.firstChild,a.insert(n,(()=>{var c=a.memo(()=>d().length>0);return()=>c()?` · ${d().length}`:""})(),null),n})()},get children(){var n=It();return n.firstChild,a.insert(n,(()=>{var c=a.memo(()=>et()>0);return()=>c()?` · 第 ${et()} 页`:""})(),null),n}}),r),r.$$click=()=>{v()?pt():S(!1)},a.insert(l,a.createComponent(i.Show,{get when(){return d().length>0},get fallback(){return(()=>{var n=Dt();return a.insert(n,()=>yt()||`… ${G}`),n})()},get children(){return a.createComponent(i.For,{get each(){return d()},children:(n,c)=>(()=>{var u=jt(),p=u.firstChild,h=p.firstChild;h.firstChild;var x=h.nextSibling;return u.$$click=()=>{At(n)},a.insert(h,()=>c()+1,null),a.insert(x,()=>Ht(n.created)),a.insert(p,()=>n.text,null),a.effect(f=>{var g=R()===n.id?"true":"false",m=v()&&rt()===n.id?"true":"false";return g!==f.e&&a.setAttribute(u,"data-active",f.e=g),m!==f.t&&a.setAttribute(u,"data-busy",f.t=m),f},{e:void 0,t:void 0}),u})()})}})),a.effect(n=>{var c=E()?"true":"false",u=v()?"true":"false",p=v()?"取消跳转 (Esc)":"关闭 (Esc)";return c!==n.e&&a.setAttribute(t,"data-open",n.e=c),u!==n.t&&a.setAttribute(t,"data-jumping",n.t=u),p!==n.a&&a.setAttribute(r,"title",n.a=p),n},{e:void 0,t:void 0,a:void 0}),t})()];return a.createComponent(a.Portal,{get mount(){return document.body},children:Ct})}const Wt=L.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(o){o.add(L.Style,Gt),o.add(L.Slot,{at:"window.bottom",render:()=>a.createComponent(i.Show,{get when(){return o.sessions.current()},get children(){return a.createComponent(Vt,{session:()=>o.sessions.current()})}})}),o.add(L.Command,()=>({id:"jump",title:o.t("command.jump"),description:o.t("command.jump.description"),group:o.t("command.jump"),section:"navigation",bind:"mod+alt+r",slash:{name:"turns"},run:()=>{window.dispatchEvent(new Event(Y))}}))}});a.delegateEvents(["click"]);exports.default=Wt;
|
package/package.json
CHANGED
package/src/gui.tsx
CHANGED
|
@@ -770,6 +770,12 @@ function Rail(props) {
|
|
|
770
770
|
|
|
771
771
|
const jump = async (turn) => {
|
|
772
772
|
const token = ++jumpToken
|
|
773
|
+
// A new click always owns the progress UI. This also clears a previous
|
|
774
|
+
// jump's state when the new target takes the mounted fast path, where the
|
|
775
|
+
// superseded loop's finally no longer touches these signals.
|
|
776
|
+
setJumping(false)
|
|
777
|
+
setJumpPage(0)
|
|
778
|
+
setBusyID("")
|
|
773
779
|
lastJumpID = turn.id
|
|
774
780
|
const now = Date.now()
|
|
775
781
|
|
|
@@ -810,6 +816,7 @@ function Rail(props) {
|
|
|
810
816
|
// another seek/page. A round ends as soon as rows appear instead of always
|
|
811
817
|
// sleeping 6s. A newer click bumps jumpToken, which cancels this loop at
|
|
812
818
|
// every await; Esc cancels it too.
|
|
819
|
+
const sessionAtJump = sessionID()
|
|
813
820
|
setJumping(true)
|
|
814
821
|
setBusyID(turn.id)
|
|
815
822
|
setJumpPage(0)
|
|
@@ -819,7 +826,9 @@ function Rail(props) {
|
|
|
819
826
|
try {
|
|
820
827
|
const deadline = Date.now() + JUMP_TOTAL_MS
|
|
821
828
|
for (let round = 0; round < JUMP_ROUNDS && Date.now() < deadline; round++) {
|
|
822
|
-
if
|
|
829
|
+
// Abandon the loop if the user switched sessions mid-jump: toggling the
|
|
830
|
+
// old route hash would navigate back to the previous session.
|
|
831
|
+
if (token !== jumpToken || sessionID() !== sessionAtJump) return
|
|
823
832
|
setJumpPage(round + 1)
|
|
824
833
|
flash(`跳转中 · 第 ${round + 1} 页`, JUMP_TOTAL_MS)
|
|
825
834
|
|