@nextclaw/ui 0.12.6 → 0.12.8

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.
Files changed (115) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/dist/assets/{ChannelsList-D8p4OlM6.js → ChannelsList-KIQIxluX.js} +1 -1
  3. package/dist/assets/{DocBrowser-Cse_F8Nn.js → DocBrowser-BMxf9CIK.js} +1 -1
  4. package/dist/assets/DocBrowser-CyDgAtO9.js +1 -0
  5. package/dist/assets/{DocBrowserContext-Bai1WU2H.js → DocBrowserContext-Ce28gRXt.js} +1 -1
  6. package/dist/assets/{LogoBadge-BdxMPc9v.js → LogoBadge-o92MOA2L.js} +1 -1
  7. package/dist/assets/{MarketplacePage-BbpAkllU.js → MarketplacePage-BySqkYDh.js} +1 -1
  8. package/dist/assets/MarketplacePage-C0olZaek.js +1 -0
  9. package/dist/assets/{McpMarketplacePage-CxPFOgxv.js → McpMarketplacePage-DqKaiXO9.js} +1 -1
  10. package/dist/assets/{ModelConfig-3GLqQ5GY.js → ModelConfig-IrmzoslW.js} +1 -1
  11. package/dist/assets/{ProviderScopedModelInput-BYNouw-i.js → ProviderScopedModelInput-CmTIzgI7.js} +1 -1
  12. package/dist/assets/{ProvidersList-BR1gJ4Dm.js → ProvidersList-8_Kalfwl.js} +1 -1
  13. package/dist/assets/{RemoteAccessPage-DyYVWsyK.js → RemoteAccessPage-CyQlSjPf.js} +1 -1
  14. package/dist/assets/RuntimeConfig-Bk0uYBhf.js +1 -0
  15. package/dist/assets/{SearchConfig-DTeJvp8m.js → SearchConfig-DNBR-UbE.js} +1 -1
  16. package/dist/assets/{SecretsConfig-CCYO6NcV.js → SecretsConfig-Ba1RPJaG.js} +1 -1
  17. package/dist/assets/{SessionsConfig-Du39vDgt.js → SessionsConfig-Doqp5ghH.js} +1 -1
  18. package/dist/assets/{app-query-client-Dr5d-K8d.js → app-query-client-DniXoIN5.js} +1 -1
  19. package/dist/assets/{book-open-Da4OEPqB.js → book-open-DocgeQtR.js} +1 -1
  20. package/dist/assets/chat-page-Bph8M5zo.js +58 -0
  21. package/dist/assets/chat-session-display-CoN3Wmn-.js +1 -0
  22. package/dist/assets/{chunk-JZWAC4HX-CoFVxHXV.js → chunk-JZWAC4HX-BvKvh1R8.js} +1 -1
  23. package/dist/assets/{client-CSk58DcF.js → client-CVqPF5ie.js} +1 -1
  24. package/dist/assets/{config-D8KzikVB.js → config-Bop2oB18.js} +1 -1
  25. package/dist/assets/{createLucideIcon-83gaZMtv.js → createLucideIcon-DVv8taGY.js} +1 -1
  26. package/dist/assets/desktop-update-config-1KBrqLBC.js +1 -0
  27. package/dist/assets/{dist-toEYs-MZ.js → dist-Da5Gm_pO.js} +1 -1
  28. package/dist/assets/{dist-aTmhMDVh.js → dist-DmAlInRu.js} +1 -1
  29. package/dist/assets/{external-link-QQ0TC6X4.js → external-link-DFjw3x1B.js} +1 -1
  30. package/dist/assets/{hash-DaFBEkmi.js → hash-DJtaCejM.js} +1 -1
  31. package/dist/assets/i18n-CwHZ-9vt.js +1 -0
  32. package/dist/assets/{index-CE4N7ItL.css → index-DafCdM4F.css} +1 -1
  33. package/dist/assets/{index-riX7Sg0_.js → index-DdksE6U3.js} +3 -3
  34. package/dist/assets/{infiniteQueryBehavior-BmHX_ayZ.js → infiniteQueryBehavior-DHSEQ3OH.js} +1 -1
  35. package/dist/assets/loader-circle-PsSP0H9n.js +1 -0
  36. package/dist/assets/{logos-Dzlz30M3.js → logos-DEFUIR12.js} +1 -1
  37. package/dist/assets/{page-layout-D2eRufRQ.js → page-layout-Da3i3r6G.js} +1 -1
  38. package/dist/assets/play-DBQbBxTA.js +1 -0
  39. package/dist/assets/plus-DUOVbsyQ.js +1 -0
  40. package/dist/assets/{popover-BSXxm5bj.js → popover-C_mWOFzI.js} +1 -1
  41. package/dist/assets/{refresh-ccw-B3zMtN-_.js → refresh-ccw-D6HkNtfz.js} +1 -1
  42. package/dist/assets/{refresh-cw-DlZkIHnJ.js → refresh-cw-DRcvRrnc.js} +1 -1
  43. package/dist/assets/rotate-cw-BmDKfXtH.js +1 -0
  44. package/dist/assets/{save-Us9fg4Sj.js → save-DHGmi2e9.js} +1 -1
  45. package/dist/assets/search-MChQRYR1.js +1 -0
  46. package/dist/assets/{security-config-BGWYwxNr.js → security-config-CbXfPZzr.js} +1 -1
  47. package/dist/assets/{select-DLYqySQK.js → select-Caud8QvU.js} +1 -1
  48. package/dist/assets/skeleton-B-4vRq_Z.js +1 -0
  49. package/dist/assets/{status-dot-DGayudyB.js → status-dot-DurKKSwA.js} +1 -1
  50. package/dist/assets/{switch-Dz2ScsKx.js → switch-0rmPBRKI.js} +1 -1
  51. package/dist/assets/{tabs-custom-CdKyjiGk.js → tabs-custom-5JLVL6v8.js} +1 -1
  52. package/dist/assets/{trash-2-Db-mZOZs.js → trash-2-C6caKPoz.js} +1 -1
  53. package/dist/assets/{use-infinite-scroll-loader-DBJX5hj0.js → use-infinite-scroll-loader-dwnaa_qi.js} +1 -1
  54. package/dist/assets/{useConfirmDialog-DL0a-oGC.js → useConfirmDialog-mMeWD_yo.js} +1 -1
  55. package/dist/assets/{useMutation-BdZm-9PL.js → useMutation-BmxxvCNf.js} +1 -1
  56. package/dist/assets/x-DuMhMATD.js +1 -0
  57. package/dist/index.html +20 -20
  58. package/package.json +6 -6
  59. package/src/api/runtime-control.ts +34 -0
  60. package/src/api/runtime-control.types.ts +58 -0
  61. package/src/api/types.ts +13 -0
  62. package/src/{App.test.tsx → app.test.tsx} +1 -1
  63. package/src/{App.tsx → app.tsx} +1 -1
  64. package/src/components/chat/ChatConversationPanel.test.tsx +78 -16
  65. package/src/components/chat/ChatSidebar.test.tsx +36 -7
  66. package/src/components/chat/ChatSidebar.tsx +19 -26
  67. package/src/components/chat/chat-child-session-panel.tsx +16 -8
  68. package/src/components/chat/chat-page-runtime.test.ts +1 -1
  69. package/src/components/chat/{ChatPage.tsx → chat-page.tsx} +1 -1
  70. package/src/components/chat/managers/chat-session-list.manager.test.ts +82 -31
  71. package/src/components/chat/managers/chat-session-list.manager.ts +79 -14
  72. package/src/components/chat/managers/chat-ui.manager.ts +2 -0
  73. package/src/components/chat/ncp/README.md +1 -1
  74. package/src/components/chat/ncp/ncp-chat-input.manager.ts +7 -1
  75. package/src/components/chat/ncp/ncp-chat-page-data.test.ts +1 -1
  76. package/src/components/chat/ncp/ncp-session-adapter.test.ts +5 -1
  77. package/src/components/chat/ncp/ncp-session-adapter.ts +12 -0
  78. package/src/components/chat/ncp/session-conversation/use-ncp-child-session-tabs-view.ts +4 -0
  79. package/src/components/chat/ncp/tests/ncp-chat-input.manager.test.ts +99 -0
  80. package/src/components/chat/stores/chat-session-list.store.ts +25 -54
  81. package/src/components/common/ProviderScopedModelInput.tsx +12 -2
  82. package/src/components/config/ModelConfig.test.tsx +108 -2
  83. package/src/components/config/RuntimeConfig.tsx +14 -6
  84. package/src/components/config/desktop-update-config.test.tsx +85 -0
  85. package/src/components/config/desktop-update-config.tsx +44 -3
  86. package/src/components/config/runtime-control-card.test.tsx +255 -0
  87. package/src/components/config/runtime-control-card.tsx +301 -0
  88. package/src/components/config/runtime-presence-card.test.tsx +154 -0
  89. package/src/components/config/runtime-presence-card.tsx +163 -0
  90. package/src/desktop/desktop-update.types.ts +25 -0
  91. package/src/desktop/managers/desktop-presence.manager.ts +91 -0
  92. package/src/desktop/managers/desktop-update.manager.ts +37 -1
  93. package/src/desktop/stores/desktop-presence.store.ts +18 -0
  94. package/src/desktop/stores/desktop-update.store.ts +7 -1
  95. package/src/hooks/use-runtime-control.ts +24 -0
  96. package/src/lib/desktop-update-labels.utils.ts +28 -2
  97. package/src/lib/i18n.runtime-control.ts +120 -0
  98. package/src/lib/i18n.ts +2 -4
  99. package/src/main.tsx +1 -1
  100. package/src/runtime-control/runtime-control.manager.ts +118 -0
  101. package/dist/assets/ChatPage-A45t1Rmf.js +0 -58
  102. package/dist/assets/DocBrowser-B2MpsnU9.js +0 -1
  103. package/dist/assets/MarketplacePage-BNZ3Jx5d.js +0 -1
  104. package/dist/assets/RuntimeConfig-ChdfK4Y_.js +0 -1
  105. package/dist/assets/chat-session-display-CAlPrnlV.js +0 -1
  106. package/dist/assets/desktop-update-config-CfoVwf-w.js +0 -1
  107. package/dist/assets/i18n-C3jb83S6.js +0 -1
  108. package/dist/assets/loader-circle-BjMg63eu.js +0 -1
  109. package/dist/assets/plus-CIXME2pD.js +0 -1
  110. package/dist/assets/search-B_Qr0f6C.js +0 -1
  111. package/dist/assets/skeleton-CYQJazv6.js +0 -1
  112. package/dist/assets/x-B8Tho_xC.js +0 -1
  113. /package/dist/assets/{config-hints-GSUMvmSo.js → config-hints-BZoDjXye.js} +0 -0
  114. /package/dist/assets/{config-layout-CgBMG7OL.js → config-layout-DmlGaay2.js} +0 -0
  115. /package/src/components/chat/ncp/{NcpChatPage.tsx → ncp-chat-page.tsx} +0 -0
@@ -1 +1 @@
1
- import{_ as e,i as t,m as n,p as r}from"./i18n-C3jb83S6.js";import{a as i,u as a}from"./client-CSk58DcF.js";import{n as o,r as s,t as c}from"./infiniteQueryBehavior-BmHX_ayZ.js";import{a as l,i as u,n as d,r as f,t as p}from"./select-DLYqySQK.js";import{t as m}from"./createLucideIcon-83gaZMtv.js";import{t as h}from"./skeleton-CYQJazv6.js";var g=class extends a{constructor(e,t){super(e,t)}bindMethods(){super.bindMethods(),this.fetchNextPage=this.fetchNextPage.bind(this),this.fetchPreviousPage=this.fetchPreviousPage.bind(this)}setOptions(e){super.setOptions({...e,behavior:s()})}getOptimisticResult(e){return e.behavior=s(),super.getOptimisticResult(e)}fetchNextPage(e){return this.fetch({...e,meta:{fetchMore:{direction:`forward`}}})}fetchPreviousPage(e){return this.fetch({...e,meta:{fetchMore:{direction:`backward`}}})}createResult(e,t){let{state:n}=e,r=super.createResult(e,t),{isFetching:i,isRefetching:a,isError:s,isRefetchError:l}=r,u=n.fetchMeta?.fetchMore?.direction,d=s&&u===`forward`,f=i&&u===`forward`,p=s&&u===`backward`,m=i&&u===`backward`;return{...r,fetchNextPage:this.fetchNextPage,fetchPreviousPage:this.fetchPreviousPage,hasNextPage:c(t,n.data),hasPreviousPage:o(t,n.data),isFetchNextPageError:d,isFetchingNextPage:f,isFetchPreviousPageError:p,isFetchingPreviousPage:m,isRefetchError:l&&!d&&!p,isRefetching:a&&!f&&!m}}};function _(e,t){return i(e,g,t)}var v=m(`PackageSearch`,[[`path`,{d:`M21 10V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l2-1.14`,key:`e7tb2h`}],[`path`,{d:`m7.5 4.27 9 5.15`,key:`1c824w`}],[`polyline`,{points:`3.29 7 12 12 20.71 7`,key:`ousv84`}],[`line`,{x1:`12`,x2:`12`,y1:`22`,y2:`12`,key:`a4e8g8`}],[`circle`,{cx:`18.5`,cy:`15.5`,r:`2.5`,key:`b5zd12`}],[`path`,{d:`M20.27 17.27 22 19`,key:`1l4muz`}]]);function y(e){if(!e||e.pages.length===0)return;let t=e.pages.flatMap(e=>e.items);return{...e.pages[e.pages.length-1],items:t,pages:e.pages,loadedItems:t.length,loadedPages:e.pages.length}}var b=r();function x(e){return(0,b.jsx)(`div`,{className:`mb-4`,children:(0,b.jsxs)(`div`,{className:`flex gap-3 items-center`,children:[(0,b.jsxs)(`div`,{className:`flex-1 min-w-0 relative`,children:[(0,b.jsx)(v,{className:`h-4 w-4 text-gray-400 absolute left-3 top-1/2 -translate-y-1/2`}),(0,b.jsx)(`input`,{value:e.searchText,onChange:t=>e.onSearchTextChange(t.target.value),placeholder:e.searchPlaceholder,className:`w-full h-9 border border-gray-200/80 rounded-xl pl-9 pr-3 text-sm focus:outline-none focus:ring-1 focus:ring-primary/40`})]}),e.scope===`all`&&(0,b.jsxs)(p,{value:e.sort,onValueChange:t=>e.onSortChange(t),children:[(0,b.jsx)(u,{className:`h-9 w-[150px] shrink-0 rounded-lg`,children:(0,b.jsx)(l,{})}),(0,b.jsxs)(d,{children:[(0,b.jsx)(f,{value:`relevance`,children:t(`marketplaceSortRelevance`)}),(0,b.jsx)(f,{value:`updated`,children:t(`marketplaceSortUpdated`)})]})]})]})})}function S(e){return(0,b.jsx)(b.Fragment,{children:Array.from({length:e.count},(e,t)=>(0,b.jsx)(`article`,{className:`rounded-2xl border border-gray-200/40 bg-white px-5 py-4 shadow-sm`,children:(0,b.jsxs)(`div`,{className:`flex items-start gap-3.5 justify-between`,children:[(0,b.jsxs)(`div`,{className:`flex min-w-0 flex-1 gap-3`,children:[(0,b.jsx)(h,{className:`h-10 w-10 shrink-0 rounded-xl`}),(0,b.jsxs)(`div`,{className:`min-w-0 flex-1 space-y-2 pt-0.5`,children:[(0,b.jsx)(h,{className:`h-4 w-32 max-w-[70%]`}),(0,b.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,b.jsx)(h,{className:`h-3 w-12`}),(0,b.jsx)(h,{className:`h-3 w-24`})]}),(0,b.jsx)(h,{className:`h-3 w-full`})]})]}),(0,b.jsx)(h,{className:`h-8 w-20 shrink-0 rounded-xl`})]})},`marketplace-skeleton-${t}`))})}function C(e){return!e.hasMore&&!e.loading?null:(0,b.jsxs)(`div`,{className:`py-4`,children:[e.hasMore&&(0,b.jsx)(`div`,{ref:e.sentinelRef,className:`h-1 w-full`,"aria-hidden":`true`}),e.loading&&(0,b.jsx)(`div`,{"data-testid":`marketplace-loading-more`,className:`pt-3 text-center text-xs text-gray-500`,children:t(`loading`)})]})}function w(e){let t=e.trim().toLowerCase().replace(/_/g,`-`),n=[t,t.split(`-`)[0],`en`];return Array.from(new Set(n.filter(Boolean)))}function T(e){return e.trim().toLowerCase().replace(/_/g,`-`)}function E(e,t,n){if(e){let t=Object.entries(e).map(([e,t])=>({locale:T(e),text:typeof t==`string`?t.trim():``})).filter(e=>e.text.length>0);if(t.length>0){let e=new Map(t.map(e=>[e.locale,e.text]));for(let t of n){let n=T(t),r=e.get(n);if(r)return r}for(let e of n){let n=T(e).split(`-`)[0];if(!n)continue;let r=t.find(e=>e.locale===n||e.locale.startsWith(`${n}-`));if(r)return r.text}return t[0]?.text??``}}return t?.trim()??``}function D(e,t){if(!e)return``;for(let n of t)if(T(n).split(`-`)[0]===`zh`&&e.descriptionZh?.trim())return e.descriptionZh.trim();return e.description?.trim()?e.description.trim():e.descriptionZh?.trim()?e.descriptionZh.trim():``}var O=e(n(),1),k=160;function A(e){let t=(0,O.useRef)(null),n=(0,O.useRef)(null),r=(0,O.useRef)(e.onLoadMore),i=(0,O.useRef)(!1);return(0,O.useEffect)(()=>{r.current=e.onLoadMore},[e.onLoadMore]),(0,O.useEffect)(()=>{e.disabled&&(i.current=!1)},[e.disabled]),(0,O.useEffect)(()=>{let a=t.current,o=n.current,s=e.thresholdPx??k;if(e.disabled||!a||!o)return;let c=()=>{i.current||e.disabled||(i.current=!0,Promise.resolve(r.current()).finally(()=>{i.current=!1}))},l=()=>{o.getBoundingClientRect().top-a.getBoundingClientRect().bottom<=s&&c()};if(typeof IntersectionObserver==`function`){let e=new IntersectionObserver(e=>{e.some(e=>e.isIntersecting)&&c()},{root:a,rootMargin:`0px 0px ${s}px 0px`});return e.observe(o),l(),()=>{e.disconnect()}}return a.addEventListener(`scroll`,l,{passive:!0}),l(),()=>{a.removeEventListener(`scroll`,l)}},[e.disabled,e.thresholdPx,e.watchValue]),{containerRef:t,sentinelRef:n}}export{x as a,y as c,E as i,_ as l,w as n,C as o,D as r,S as s,A as t};
1
+ import{_ as e,i as t,m as n,p as r}from"./i18n-CwHZ-9vt.js";import{a as i,u as a}from"./client-CVqPF5ie.js";import{n as o,r as s,t as c}from"./infiniteQueryBehavior-DHSEQ3OH.js";import{a as l,i as u,n as d,r as f,t as p}from"./select-Caud8QvU.js";import{t as m}from"./createLucideIcon-DVv8taGY.js";import{t as h}from"./skeleton-B-4vRq_Z.js";var g=class extends a{constructor(e,t){super(e,t)}bindMethods(){super.bindMethods(),this.fetchNextPage=this.fetchNextPage.bind(this),this.fetchPreviousPage=this.fetchPreviousPage.bind(this)}setOptions(e){super.setOptions({...e,behavior:s()})}getOptimisticResult(e){return e.behavior=s(),super.getOptimisticResult(e)}fetchNextPage(e){return this.fetch({...e,meta:{fetchMore:{direction:`forward`}}})}fetchPreviousPage(e){return this.fetch({...e,meta:{fetchMore:{direction:`backward`}}})}createResult(e,t){let{state:n}=e,r=super.createResult(e,t),{isFetching:i,isRefetching:a,isError:s,isRefetchError:l}=r,u=n.fetchMeta?.fetchMore?.direction,d=s&&u===`forward`,f=i&&u===`forward`,p=s&&u===`backward`,m=i&&u===`backward`;return{...r,fetchNextPage:this.fetchNextPage,fetchPreviousPage:this.fetchPreviousPage,hasNextPage:c(t,n.data),hasPreviousPage:o(t,n.data),isFetchNextPageError:d,isFetchingNextPage:f,isFetchPreviousPageError:p,isFetchingPreviousPage:m,isRefetchError:l&&!d&&!p,isRefetching:a&&!f&&!m}}};function _(e,t){return i(e,g,t)}var v=m(`PackageSearch`,[[`path`,{d:`M21 10V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l2-1.14`,key:`e7tb2h`}],[`path`,{d:`m7.5 4.27 9 5.15`,key:`1c824w`}],[`polyline`,{points:`3.29 7 12 12 20.71 7`,key:`ousv84`}],[`line`,{x1:`12`,x2:`12`,y1:`22`,y2:`12`,key:`a4e8g8`}],[`circle`,{cx:`18.5`,cy:`15.5`,r:`2.5`,key:`b5zd12`}],[`path`,{d:`M20.27 17.27 22 19`,key:`1l4muz`}]]);function y(e){if(!e||e.pages.length===0)return;let t=e.pages.flatMap(e=>e.items);return{...e.pages[e.pages.length-1],items:t,pages:e.pages,loadedItems:t.length,loadedPages:e.pages.length}}var b=r();function x(e){return(0,b.jsx)(`div`,{className:`mb-4`,children:(0,b.jsxs)(`div`,{className:`flex gap-3 items-center`,children:[(0,b.jsxs)(`div`,{className:`flex-1 min-w-0 relative`,children:[(0,b.jsx)(v,{className:`h-4 w-4 text-gray-400 absolute left-3 top-1/2 -translate-y-1/2`}),(0,b.jsx)(`input`,{value:e.searchText,onChange:t=>e.onSearchTextChange(t.target.value),placeholder:e.searchPlaceholder,className:`w-full h-9 border border-gray-200/80 rounded-xl pl-9 pr-3 text-sm focus:outline-none focus:ring-1 focus:ring-primary/40`})]}),e.scope===`all`&&(0,b.jsxs)(p,{value:e.sort,onValueChange:t=>e.onSortChange(t),children:[(0,b.jsx)(u,{className:`h-9 w-[150px] shrink-0 rounded-lg`,children:(0,b.jsx)(l,{})}),(0,b.jsxs)(d,{children:[(0,b.jsx)(f,{value:`relevance`,children:t(`marketplaceSortRelevance`)}),(0,b.jsx)(f,{value:`updated`,children:t(`marketplaceSortUpdated`)})]})]})]})})}function S(e){return(0,b.jsx)(b.Fragment,{children:Array.from({length:e.count},(e,t)=>(0,b.jsx)(`article`,{className:`rounded-2xl border border-gray-200/40 bg-white px-5 py-4 shadow-sm`,children:(0,b.jsxs)(`div`,{className:`flex items-start gap-3.5 justify-between`,children:[(0,b.jsxs)(`div`,{className:`flex min-w-0 flex-1 gap-3`,children:[(0,b.jsx)(h,{className:`h-10 w-10 shrink-0 rounded-xl`}),(0,b.jsxs)(`div`,{className:`min-w-0 flex-1 space-y-2 pt-0.5`,children:[(0,b.jsx)(h,{className:`h-4 w-32 max-w-[70%]`}),(0,b.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,b.jsx)(h,{className:`h-3 w-12`}),(0,b.jsx)(h,{className:`h-3 w-24`})]}),(0,b.jsx)(h,{className:`h-3 w-full`})]})]}),(0,b.jsx)(h,{className:`h-8 w-20 shrink-0 rounded-xl`})]})},`marketplace-skeleton-${t}`))})}function C(e){return!e.hasMore&&!e.loading?null:(0,b.jsxs)(`div`,{className:`py-4`,children:[e.hasMore&&(0,b.jsx)(`div`,{ref:e.sentinelRef,className:`h-1 w-full`,"aria-hidden":`true`}),e.loading&&(0,b.jsx)(`div`,{"data-testid":`marketplace-loading-more`,className:`pt-3 text-center text-xs text-gray-500`,children:t(`loading`)})]})}function w(e){let t=e.trim().toLowerCase().replace(/_/g,`-`),n=[t,t.split(`-`)[0],`en`];return Array.from(new Set(n.filter(Boolean)))}function T(e){return e.trim().toLowerCase().replace(/_/g,`-`)}function E(e,t,n){if(e){let t=Object.entries(e).map(([e,t])=>({locale:T(e),text:typeof t==`string`?t.trim():``})).filter(e=>e.text.length>0);if(t.length>0){let e=new Map(t.map(e=>[e.locale,e.text]));for(let t of n){let n=T(t),r=e.get(n);if(r)return r}for(let e of n){let n=T(e).split(`-`)[0];if(!n)continue;let r=t.find(e=>e.locale===n||e.locale.startsWith(`${n}-`));if(r)return r.text}return t[0]?.text??``}}return t?.trim()??``}function D(e,t){if(!e)return``;for(let n of t)if(T(n).split(`-`)[0]===`zh`&&e.descriptionZh?.trim())return e.descriptionZh.trim();return e.description?.trim()?e.description.trim():e.descriptionZh?.trim()?e.descriptionZh.trim():``}var O=e(n(),1),k=160;function A(e){let t=(0,O.useRef)(null),n=(0,O.useRef)(null),r=(0,O.useRef)(e.onLoadMore),i=(0,O.useRef)(!1);return(0,O.useEffect)(()=>{r.current=e.onLoadMore},[e.onLoadMore]),(0,O.useEffect)(()=>{e.disabled&&(i.current=!1)},[e.disabled]),(0,O.useEffect)(()=>{let a=t.current,o=n.current,s=e.thresholdPx??k;if(e.disabled||!a||!o)return;let c=()=>{i.current||e.disabled||(i.current=!0,Promise.resolve(r.current()).finally(()=>{i.current=!1}))},l=()=>{o.getBoundingClientRect().top-a.getBoundingClientRect().bottom<=s&&c()};if(typeof IntersectionObserver==`function`){let e=new IntersectionObserver(e=>{e.some(e=>e.isIntersecting)&&c()},{root:a,rootMargin:`0px 0px ${s}px 0px`});return e.observe(o),l(),()=>{e.disconnect()}}return a.addEventListener(`scroll`,l,{passive:!0}),l(),()=>{a.removeEventListener(`scroll`,l)}},[e.disabled,e.thresholdPx,e.watchValue]),{containerRef:t,sentinelRef:n}}export{x as a,y as c,E as i,_ as l,w as n,C as o,D as r,S as s,A as t};
@@ -1 +1 @@
1
- import{_ as e,i as t,m as n,p as r}from"./i18n-C3jb83S6.js";import{r as i}from"./dist-aTmhMDVh.js";import{at as a,it as o,nt as s,ot as c,rt as l,st as u}from"./index-riX7Sg0_.js";var d=e(n(),1),f=r(),p=({open:e,onOpenChange:n,title:r,description:d,confirmLabel:p=t(`confirm`),cancelLabel:m=t(`cancel`),variant:h=`default`,onConfirm:g,onCancel:_})=>(0,f.jsx)(s,{open:e,onOpenChange:n,children:(0,f.jsxs)(l,{className:`[&>:last-child]:hidden`,onCloseAutoFocus:e=>e.preventDefault(),children:[(0,f.jsxs)(c,{children:[(0,f.jsx)(u,{children:r}),d?(0,f.jsx)(o,{children:d}):null]}),(0,f.jsxs)(a,{className:`gap-2 sm:gap-0`,children:[(0,f.jsx)(i,{type:`button`,variant:`outline`,onClick:()=>{_(),n(!1)},children:m}),(0,f.jsx)(i,{type:`button`,variant:h===`destructive`?`destructive`:`default`,onClick:()=>{g(),n(!1)},children:p})]})]})}),m={open:!1,title:``,description:``,confirmLabel:t(`confirm`),cancelLabel:t(`cancel`),variant:`default`,resolve:null};function h(){let[e,n]=(0,d.useState)(m),r=(0,d.useCallback)(e=>new Promise(r=>{n({open:!0,title:e.title,description:e.description??``,confirmLabel:e.confirmLabel??t(`confirm`),cancelLabel:e.cancelLabel??t(`cancel`),variant:e.variant??`default`,resolve:e=>{r(e),n(e=>({...e,open:!1,resolve:null}))}})}),[]),i=(0,d.useCallback)(e=>{n(t=>(!e&&t.resolve&&t.resolve(!1),{...t,open:e,resolve:e?t.resolve:null}))},[]);return{confirm:r,ConfirmDialog:(0,d.useCallback)(()=>(0,f.jsx)(p,{open:e.open,onOpenChange:i,title:e.title,description:e.description||void 0,confirmLabel:e.confirmLabel,cancelLabel:e.cancelLabel,variant:e.variant,onConfirm:()=>e.resolve?.(!0),onCancel:()=>e.resolve?.(!1)}),[e,i])}}export{h as t};
1
+ import{_ as e,i as t,m as n,p as r}from"./i18n-CwHZ-9vt.js";import{r as i}from"./dist-DmAlInRu.js";import{at as a,it as o,nt as s,ot as c,rt as l,st as u}from"./index-DdksE6U3.js";var d=e(n(),1),f=r(),p=({open:e,onOpenChange:n,title:r,description:d,confirmLabel:p=t(`confirm`),cancelLabel:m=t(`cancel`),variant:h=`default`,onConfirm:g,onCancel:_})=>(0,f.jsx)(s,{open:e,onOpenChange:n,children:(0,f.jsxs)(l,{className:`[&>:last-child]:hidden`,onCloseAutoFocus:e=>e.preventDefault(),children:[(0,f.jsxs)(c,{children:[(0,f.jsx)(u,{children:r}),d?(0,f.jsx)(o,{children:d}):null]}),(0,f.jsxs)(a,{className:`gap-2 sm:gap-0`,children:[(0,f.jsx)(i,{type:`button`,variant:`outline`,onClick:()=>{_(),n(!1)},children:m}),(0,f.jsx)(i,{type:`button`,variant:h===`destructive`?`destructive`:`default`,onClick:()=>{g(),n(!1)},children:p})]})]})}),m={open:!1,title:``,description:``,confirmLabel:t(`confirm`),cancelLabel:t(`cancel`),variant:`default`,resolve:null};function h(){let[e,n]=(0,d.useState)(m),r=(0,d.useCallback)(e=>new Promise(r=>{n({open:!0,title:e.title,description:e.description??``,confirmLabel:e.confirmLabel??t(`confirm`),cancelLabel:e.cancelLabel??t(`cancel`),variant:e.variant??`default`,resolve:e=>{r(e),n(e=>({...e,open:!1,resolve:null}))}})}),[]),i=(0,d.useCallback)(e=>{n(t=>(!e&&t.resolve&&t.resolve(!1),{...t,open:e,resolve:e?t.resolve:null}))},[]);return{confirm:r,ConfirmDialog:(0,d.useCallback)(()=>(0,f.jsx)(p,{open:e.open,onOpenChange:i,title:e.title,description:e.description||void 0,confirmLabel:e.confirmLabel,cancelLabel:e.cancelLabel,variant:e.variant,onConfirm:()=>e.resolve?.(!0),onCancel:()=>e.resolve?.(!1)}),[e,i])}}export{h as t};
@@ -1 +1 @@
1
- import{_ as e,m as t}from"./i18n-C3jb83S6.js";import{A as n,D as r,O as i,b as a,l as o,p as s,s as c,w as l}from"./client-CSk58DcF.js";var u=class extends n{#e;#t=void 0;#n;#r;constructor(e,t){super(),this.#e=e,this.setOptions(t),this.bindMethods(),this.#i()}bindMethods(){this.mutate=this.mutate.bind(this),this.reset=this.reset.bind(this)}setOptions(e){let t=this.options;this.options=this.#e.defaultMutationOptions(e),r(this.options,t)||this.#e.getMutationCache().notify({type:`observerOptionsUpdated`,mutation:this.#n,observer:this}),t?.mutationKey&&this.options.mutationKey&&a(t.mutationKey)!==a(this.options.mutationKey)?this.reset():this.#n?.state.status===`pending`&&this.#n.setOptions(this.options)}onUnsubscribe(){this.hasListeners()||this.#n?.removeObserver(this)}onMutationUpdate(e){this.#i(),this.#a(e)}getCurrentResult(){return this.#t}reset(){this.#n?.removeObserver(this),this.#n=void 0,this.#i(),this.#a()}mutate(e,t){return this.#r=t,this.#n?.removeObserver(this),this.#n=this.#e.getMutationCache().build(this.#e,this.options),this.#n.addObserver(this),this.#n.execute(e)}#i(){let e=this.#n?.state??o();this.#t={...e,isPending:e.status===`pending`,isSuccess:e.status===`success`,isError:e.status===`error`,isIdle:e.status===`idle`,mutate:this.mutate,reset:this.reset}}#a(e){s.batch(()=>{if(this.#r&&this.hasListeners()){let t=this.#t.variables,n=this.#t.context,r={client:this.#e,meta:this.options.meta,mutationKey:this.options.mutationKey};if(e?.type===`success`){try{this.#r.onSuccess?.(e.data,t,n,r)}catch(e){Promise.reject(e)}try{this.#r.onSettled?.(e.data,null,t,n,r)}catch(e){Promise.reject(e)}}else if(e?.type===`error`){try{this.#r.onError?.(e.error,t,n,r)}catch(e){Promise.reject(e)}try{this.#r.onSettled?.(void 0,e.error,t,n,r)}catch(e){Promise.reject(e)}}}this.listeners.forEach(e=>{e(this.#t)})})}},d=e(t(),1);function f(e,t){let n=c(t),[r]=d.useState(()=>new u(n,e));d.useEffect(()=>{r.setOptions(e)},[r,e]);let a=d.useSyncExternalStore(d.useCallback(e=>r.subscribe(s.batchCalls(e)),[r]),()=>r.getCurrentResult(),()=>r.getCurrentResult()),o=d.useCallback((e,t)=>{r.mutate(e,t).catch(l)},[r]);if(a.error&&i(r.options.throwOnError,[a.error]))throw a.error;return{...a,mutate:o,mutateAsync:a.mutate}}export{f as t};
1
+ import{_ as e,m as t}from"./i18n-CwHZ-9vt.js";import{A as n,D as r,O as i,b as a,l as o,p as s,s as c,w as l}from"./client-CVqPF5ie.js";var u=class extends n{#e;#t=void 0;#n;#r;constructor(e,t){super(),this.#e=e,this.setOptions(t),this.bindMethods(),this.#i()}bindMethods(){this.mutate=this.mutate.bind(this),this.reset=this.reset.bind(this)}setOptions(e){let t=this.options;this.options=this.#e.defaultMutationOptions(e),r(this.options,t)||this.#e.getMutationCache().notify({type:`observerOptionsUpdated`,mutation:this.#n,observer:this}),t?.mutationKey&&this.options.mutationKey&&a(t.mutationKey)!==a(this.options.mutationKey)?this.reset():this.#n?.state.status===`pending`&&this.#n.setOptions(this.options)}onUnsubscribe(){this.hasListeners()||this.#n?.removeObserver(this)}onMutationUpdate(e){this.#i(),this.#a(e)}getCurrentResult(){return this.#t}reset(){this.#n?.removeObserver(this),this.#n=void 0,this.#i(),this.#a()}mutate(e,t){return this.#r=t,this.#n?.removeObserver(this),this.#n=this.#e.getMutationCache().build(this.#e,this.options),this.#n.addObserver(this),this.#n.execute(e)}#i(){let e=this.#n?.state??o();this.#t={...e,isPending:e.status===`pending`,isSuccess:e.status===`success`,isError:e.status===`error`,isIdle:e.status===`idle`,mutate:this.mutate,reset:this.reset}}#a(e){s.batch(()=>{if(this.#r&&this.hasListeners()){let t=this.#t.variables,n=this.#t.context,r={client:this.#e,meta:this.options.meta,mutationKey:this.options.mutationKey};if(e?.type===`success`){try{this.#r.onSuccess?.(e.data,t,n,r)}catch(e){Promise.reject(e)}try{this.#r.onSettled?.(e.data,null,t,n,r)}catch(e){Promise.reject(e)}}else if(e?.type===`error`){try{this.#r.onError?.(e.error,t,n,r)}catch(e){Promise.reject(e)}try{this.#r.onSettled?.(void 0,e.error,t,n,r)}catch(e){Promise.reject(e)}}}this.listeners.forEach(e=>{e(this.#t)})})}},d=e(t(),1);function f(e,t){let n=c(t),[r]=d.useState(()=>new u(n,e));d.useEffect(()=>{r.setOptions(e)},[r,e]);let a=d.useSyncExternalStore(d.useCallback(e=>r.subscribe(s.batchCalls(e)),[r]),()=>r.getCurrentResult(),()=>r.getCurrentResult()),o=d.useCallback((e,t)=>{r.mutate(e,t).catch(l)},[r]);if(a.error&&i(r.options.throwOnError,[a.error]))throw a.error;return{...a,mutate:o,mutateAsync:a.mutate}}export{f as t};
@@ -0,0 +1 @@
1
+ import{t as e}from"./createLucideIcon-DVv8taGY.js";var t=e(`X`,[[`path`,{d:`M18 6 6 18`,key:`1bl5f8`}],[`path`,{d:`m6 6 12 12`,key:`d8bk6v`}]]);export{t};
package/dist/index.html CHANGED
@@ -6,26 +6,26 @@
6
6
  <link rel="icon" type="image/svg+xml" href="/logo.svg" />
7
7
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8
8
  <title>NextClaw</title>
9
- <script type="module" crossorigin src="/assets/index-riX7Sg0_.js"></script>
10
- <link rel="modulepreload" crossorigin href="/assets/i18n-C3jb83S6.js">
11
- <link rel="modulepreload" crossorigin href="/assets/chunk-JZWAC4HX-CoFVxHXV.js">
12
- <link rel="modulepreload" crossorigin href="/assets/dist-aTmhMDVh.js">
13
- <link rel="modulepreload" crossorigin href="/assets/createLucideIcon-83gaZMtv.js">
14
- <link rel="modulepreload" crossorigin href="/assets/select-DLYqySQK.js">
15
- <link rel="modulepreload" crossorigin href="/assets/dist-toEYs-MZ.js">
16
- <link rel="modulepreload" crossorigin href="/assets/client-CSk58DcF.js">
17
- <link rel="modulepreload" crossorigin href="/assets/infiniteQueryBehavior-BmHX_ayZ.js">
18
- <link rel="modulepreload" crossorigin href="/assets/app-query-client-Dr5d-K8d.js">
19
- <link rel="modulepreload" crossorigin href="/assets/useMutation-BdZm-9PL.js">
20
- <link rel="modulepreload" crossorigin href="/assets/book-open-Da4OEPqB.js">
21
- <link rel="modulepreload" crossorigin href="/assets/external-link-QQ0TC6X4.js">
22
- <link rel="modulepreload" crossorigin href="/assets/plus-CIXME2pD.js">
23
- <link rel="modulepreload" crossorigin href="/assets/search-B_Qr0f6C.js">
24
- <link rel="modulepreload" crossorigin href="/assets/x-B8Tho_xC.js">
25
- <link rel="modulepreload" crossorigin href="/assets/DocBrowserContext-Bai1WU2H.js">
26
- <link rel="modulepreload" crossorigin href="/assets/DocBrowser-Cse_F8Nn.js">
27
- <link rel="modulepreload" crossorigin href="/assets/config-D8KzikVB.js">
28
- <link rel="stylesheet" crossorigin href="/assets/index-CE4N7ItL.css">
9
+ <script type="module" crossorigin src="/assets/index-DdksE6U3.js"></script>
10
+ <link rel="modulepreload" crossorigin href="/assets/i18n-CwHZ-9vt.js">
11
+ <link rel="modulepreload" crossorigin href="/assets/chunk-JZWAC4HX-BvKvh1R8.js">
12
+ <link rel="modulepreload" crossorigin href="/assets/dist-DmAlInRu.js">
13
+ <link rel="modulepreload" crossorigin href="/assets/createLucideIcon-DVv8taGY.js">
14
+ <link rel="modulepreload" crossorigin href="/assets/select-Caud8QvU.js">
15
+ <link rel="modulepreload" crossorigin href="/assets/dist-Da5Gm_pO.js">
16
+ <link rel="modulepreload" crossorigin href="/assets/client-CVqPF5ie.js">
17
+ <link rel="modulepreload" crossorigin href="/assets/infiniteQueryBehavior-DHSEQ3OH.js">
18
+ <link rel="modulepreload" crossorigin href="/assets/app-query-client-DniXoIN5.js">
19
+ <link rel="modulepreload" crossorigin href="/assets/useMutation-BmxxvCNf.js">
20
+ <link rel="modulepreload" crossorigin href="/assets/book-open-DocgeQtR.js">
21
+ <link rel="modulepreload" crossorigin href="/assets/external-link-DFjw3x1B.js">
22
+ <link rel="modulepreload" crossorigin href="/assets/plus-DUOVbsyQ.js">
23
+ <link rel="modulepreload" crossorigin href="/assets/search-MChQRYR1.js">
24
+ <link rel="modulepreload" crossorigin href="/assets/x-DuMhMATD.js">
25
+ <link rel="modulepreload" crossorigin href="/assets/DocBrowserContext-Ce28gRXt.js">
26
+ <link rel="modulepreload" crossorigin href="/assets/DocBrowser-BMxf9CIK.js">
27
+ <link rel="modulepreload" crossorigin href="/assets/config-Bop2oB18.js">
28
+ <link rel="stylesheet" crossorigin href="/assets/index-DafCdM4F.css">
29
29
  </head>
30
30
 
31
31
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/ui",
3
- "version": "0.12.6",
3
+ "version": "0.12.8",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -28,11 +28,11 @@
28
28
  "tailwind-merge": "^2.5.4",
29
29
  "zod": "^3.23.8",
30
30
  "zustand": "^5.0.2",
31
- "@nextclaw/agent-chat": "0.1.9",
32
- "@nextclaw/agent-chat-ui": "0.3.3",
33
- "@nextclaw/ncp-http-agent-client": "0.3.12",
34
- "@nextclaw/ncp-react": "0.4.19",
35
- "@nextclaw/ncp": "0.5.0"
31
+ "@nextclaw/ncp-http-agent-client": "0.3.13",
32
+ "@nextclaw/ncp-react": "0.4.21",
33
+ "@nextclaw/ncp": "0.5.1",
34
+ "@nextclaw/agent-chat-ui": "0.3.5",
35
+ "@nextclaw/agent-chat": "0.1.10"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@testing-library/react": "^16.3.0",
@@ -0,0 +1,34 @@
1
+ import { api } from './client';
2
+ import type { RuntimeControlActionResult, RuntimeControlView } from './runtime-control.types';
3
+
4
+ export async function fetchRuntimeControl(): Promise<RuntimeControlView> {
5
+ const response = await api.get<RuntimeControlView>('/api/runtime/control');
6
+ if (!response.ok) {
7
+ throw new Error(response.error.message);
8
+ }
9
+ return response.data;
10
+ }
11
+
12
+ export async function startRuntimeService(): Promise<RuntimeControlActionResult> {
13
+ const response = await api.post<RuntimeControlActionResult>('/api/runtime/control/start-service', {});
14
+ if (!response.ok) {
15
+ throw new Error(response.error.message);
16
+ }
17
+ return response.data;
18
+ }
19
+
20
+ export async function restartRuntimeService(): Promise<RuntimeControlActionResult> {
21
+ const response = await api.post<RuntimeControlActionResult>('/api/runtime/control/restart-service', {});
22
+ if (!response.ok) {
23
+ throw new Error(response.error.message);
24
+ }
25
+ return response.data;
26
+ }
27
+
28
+ export async function stopRuntimeService(): Promise<RuntimeControlActionResult> {
29
+ const response = await api.post<RuntimeControlActionResult>('/api/runtime/control/stop-service', {});
30
+ if (!response.ok) {
31
+ throw new Error(response.error.message);
32
+ }
33
+ return response.data;
34
+ }
@@ -0,0 +1,58 @@
1
+ export type RuntimeControlEnvironment =
2
+ | 'desktop-embedded'
3
+ | 'managed-local-service'
4
+ | 'self-hosted-web'
5
+ | 'shared-web';
6
+
7
+ export type RuntimeLifecycleState =
8
+ | 'healthy'
9
+ | 'starting-service'
10
+ | 'restarting-service'
11
+ | 'stopping-service'
12
+ | 'restarting-app'
13
+ | 'recovering'
14
+ | 'unavailable'
15
+ | 'failed';
16
+
17
+ export type RuntimeActionImpact = 'none' | 'brief-ui-disconnect' | 'full-app-relaunch';
18
+
19
+ export type RuntimeActionCapability = {
20
+ available: boolean;
21
+ requiresConfirmation: boolean;
22
+ impact: RuntimeActionImpact;
23
+ reasonIfUnavailable?: string;
24
+ };
25
+
26
+ export type RuntimeServiceState =
27
+ | 'running'
28
+ | 'stopped'
29
+ | 'starting'
30
+ | 'stopping'
31
+ | 'restarting'
32
+ | 'unknown';
33
+
34
+ export type RuntimeControlView = {
35
+ environment: RuntimeControlEnvironment;
36
+ lifecycle: RuntimeLifecycleState;
37
+ serviceState: RuntimeServiceState;
38
+ canStartService: RuntimeActionCapability;
39
+ canRestartService: RuntimeActionCapability;
40
+ canStopService: RuntimeActionCapability;
41
+ canRestartApp: RuntimeActionCapability;
42
+ ownerLabel?: string;
43
+ managementHint?: string;
44
+ message?: string;
45
+ };
46
+
47
+ export type RuntimeControlAction =
48
+ | 'start-service'
49
+ | 'restart-service'
50
+ | 'stop-service'
51
+ | 'restart-app';
52
+
53
+ export type RuntimeControlActionResult = {
54
+ accepted: boolean;
55
+ action: RuntimeControlAction;
56
+ lifecycle: RuntimeLifecycleState;
57
+ message: string;
58
+ };
package/src/api/types.ts CHANGED
@@ -174,6 +174,16 @@ export type {
174
174
  RemoteSettingsUpdateRequest,
175
175
  RemoteSettingsView
176
176
  } from './remote.types';
177
+ export type {
178
+ RuntimeActionCapability,
179
+ RuntimeActionImpact,
180
+ RuntimeControlAction,
181
+ RuntimeControlEnvironment,
182
+ RuntimeControlView,
183
+ RuntimeLifecycleState,
184
+ RuntimeServiceState,
185
+ RuntimeControlActionResult
186
+ } from './runtime-control.types';
177
187
 
178
188
  export type AgentProfileView = {
179
189
  id: string;
@@ -240,6 +250,8 @@ export type SessionEntryView = {
240
250
  key: string;
241
251
  createdAt: string;
242
252
  updatedAt: string;
253
+ lastMessageAt?: string;
254
+ readAt?: string;
243
255
  agentId?: string;
244
256
  label?: string;
245
257
  channel?: string;
@@ -331,6 +343,7 @@ export type SessionPatchUpdate = {
331
343
  preferredThinking?: ThinkingLevel | null;
332
344
  sessionType?: string | null;
333
345
  projectRoot?: string | null;
346
+ uiReadAt?: string | null;
334
347
  clearHistory?: boolean;
335
348
  };
336
349
 
@@ -3,7 +3,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
3
3
  import { MemoryRouter } from 'react-router-dom';
4
4
  import { I18nProvider } from '@/components/providers/I18nProvider';
5
5
  import { ThemeProvider } from '@/components/providers/ThemeProvider';
6
- import AppContent from '@/App';
6
+ import AppContent from '@/app';
7
7
 
8
8
  const mocks = vi.hoisted(() => ({
9
9
  refetch: vi.fn(),
@@ -11,7 +11,7 @@ import { Toaster } from 'sonner';
11
11
  import { Routes, Route, Navigate } from 'react-router-dom';
12
12
 
13
13
  const ModelConfigPage = lazy(async () => ({ default: (await import('@/components/config/ModelConfig')).ModelConfig }));
14
- const ChatPage = lazy(async () => ({ default: (await import('@/components/chat/ChatPage')).ChatPage }));
14
+ const ChatPage = lazy(async () => ({ default: (await import('@/components/chat/chat-page')).ChatPage }));
15
15
  const SearchConfigPage = lazy(async () => ({ default: (await import('@/components/config/SearchConfig')).SearchConfig }));
16
16
  const ProvidersListPage = lazy(async () => ({ default: (await import('@/components/config/ProvidersList')).ProvidersList }));
17
17
  const ChannelsListPage = lazy(async () => ({ default: (await import('@/components/config/ChannelsList')).ChannelsList }));
@@ -24,6 +24,8 @@ const mocks = vi.hoisted(() => ({
24
24
  title: "北京天气",
25
25
  agentId: "weather",
26
26
  updatedAt: "2026-04-10T09:00:00.000Z",
27
+ lastMessageAt: "2026-04-10T09:00:00.000Z",
28
+ readAt: "2026-04-10T09:00:00.000Z",
27
29
  sessionTypeLabel: "Codex",
28
30
  preferredModel: "openai/gpt-5.3-codex",
29
31
  projectName: "project-alpha",
@@ -84,17 +86,14 @@ vi.mock("@/components/chat/presenter/chat-presenter-context", () => ({
84
86
  setSelectedAgentId: mocks.setSelectedAgentId,
85
87
  markSessionRead: (
86
88
  sessionKey: string | null | undefined,
87
- updatedAt: string | null | undefined,
89
+ readAt: string | null | undefined,
88
90
  ) =>
89
91
  sessionKey
90
92
  ? useChatSessionListStore.getState().markSessionRead(
91
93
  sessionKey,
92
- updatedAt,
94
+ readAt,
93
95
  )
94
96
  : undefined,
95
- hydrateReadWatermarks: (
96
- entries: readonly { sessionKey: string; updatedAt: string | null | undefined }[],
97
- ) => useChatSessionListStore.getState().hydrateReadWatermarks(entries),
98
97
  },
99
98
  chatInputManager: {
100
99
  setPendingSessionType: mocks.setPendingSessionType,
@@ -192,8 +191,7 @@ describe("ChatConversationPanel", () => {
192
191
  },
193
192
  });
194
193
  useChatSessionListStore.setState({
195
- readUpdatedAtBySessionKey: {},
196
- hasHydratedReadWatermarks: false,
194
+ optimisticReadAtBySessionKey: {},
197
195
  snapshot: {
198
196
  ...useChatSessionListStore.getState().snapshot,
199
197
  },
@@ -299,6 +297,8 @@ describe("ChatChildSessionPanel", () => {
299
297
  title: "北京天气",
300
298
  agentId: "weather",
301
299
  updatedAt: "2026-04-10T09:00:00.000Z",
300
+ lastMessageAt: "2026-04-10T09:00:00.000Z",
301
+ readAt: "2026-04-10T09:00:00.000Z",
302
302
  sessionTypeLabel: "Codex",
303
303
  preferredModel: "openai/gpt-5.3-codex",
304
304
  projectName: "project-alpha",
@@ -345,6 +345,8 @@ describe("ChatChildSessionPanel", () => {
345
345
  title: "北京天气",
346
346
  agentId: "weather",
347
347
  updatedAt: "2026-04-10T09:00:00.000Z",
348
+ lastMessageAt: "2026-04-10T09:00:00.000Z",
349
+ readAt: "2026-04-10T09:00:00.000Z",
348
350
  sessionTypeLabel: "Codex",
349
351
  preferredModel: "openai/gpt-5.3-codex",
350
352
  projectName: "project-alpha",
@@ -356,6 +358,8 @@ describe("ChatChildSessionPanel", () => {
356
358
  title: "上海天气",
357
359
  agentId: "weather",
358
360
  updatedAt: "2026-04-10T09:05:00.000Z",
361
+ lastMessageAt: "2026-04-10T09:05:00.000Z",
362
+ readAt: "2026-04-10T09:05:00.000Z",
359
363
  sessionTypeLabel: "Claude Code",
360
364
  preferredModel: "anthropic/claude-sonnet-4",
361
365
  projectName: "project-beta",
@@ -408,6 +412,8 @@ describe("ChatChildSessionPanel", () => {
408
412
  title: "北京天气",
409
413
  agentId: "weather",
410
414
  updatedAt: "2026-04-10T09:00:00.000Z",
415
+ lastMessageAt: "2026-04-10T09:00:00.000Z",
416
+ readAt: "2026-04-10T09:00:00.000Z",
411
417
  sessionTypeLabel: "Codex",
412
418
  preferredModel: "openai/gpt-5.3-codex",
413
419
  projectName: "project-alpha",
@@ -419,6 +425,8 @@ describe("ChatChildSessionPanel", () => {
419
425
  title: "上海天气",
420
426
  agentId: "weather",
421
427
  updatedAt: "2026-04-10T09:05:00.000Z",
428
+ lastMessageAt: "2026-04-10T09:05:00.000Z",
429
+ readAt: "2026-04-10T09:05:00.000Z",
422
430
  runStatus: "running",
423
431
  sessionTypeLabel: "Claude Code",
424
432
  preferredModel: "anthropic/claude-sonnet-4",
@@ -450,9 +458,7 @@ describe("ChatChildSessionPanel", () => {
450
458
  />,
451
459
  );
452
460
 
453
- expect(
454
- screen.queryByLabelText("Session has unread updates"),
455
- ).toBeNull();
461
+ expect(screen.queryByLabelText("Session has unread updates")).toBeNull();
456
462
 
457
463
  mocks.resolvedChildTabs = [
458
464
  {
@@ -461,6 +467,8 @@ describe("ChatChildSessionPanel", () => {
461
467
  title: "北京天气",
462
468
  agentId: "weather",
463
469
  updatedAt: "2026-04-10T09:00:00.000Z",
470
+ lastMessageAt: "2026-04-10T09:00:00.000Z",
471
+ readAt: "2026-04-10T09:00:00.000Z",
464
472
  sessionTypeLabel: "Codex",
465
473
  preferredModel: "openai/gpt-5.3-codex",
466
474
  projectName: "project-alpha",
@@ -472,6 +480,8 @@ describe("ChatChildSessionPanel", () => {
472
480
  title: "上海天气",
473
481
  agentId: "weather",
474
482
  updatedAt: "2026-04-10T09:05:00.000Z",
483
+ lastMessageAt: "2026-04-10T09:06:00.000Z",
484
+ readAt: "2026-04-10T09:05:00.000Z",
475
485
  sessionTypeLabel: "Claude Code",
476
486
  preferredModel: "anthropic/claude-sonnet-4",
477
487
  projectName: "project-beta",
@@ -502,9 +512,7 @@ describe("ChatChildSessionPanel", () => {
502
512
  />,
503
513
  );
504
514
 
505
- expect(
506
- screen.getByLabelText("Session has unread updates"),
507
- ).toBeTruthy();
515
+ expect(screen.getByLabelText("Session has unread updates")).toBeTruthy();
508
516
 
509
517
  rerender(
510
518
  <ChatChildSessionPanel
@@ -529,8 +537,62 @@ describe("ChatChildSessionPanel", () => {
529
537
  />,
530
538
  );
531
539
 
532
- expect(
533
- screen.queryByLabelText("Session has unread updates"),
534
- ).toBeNull();
540
+ expect(screen.queryByLabelText("Session has unread updates")).toBeNull();
541
+ });
542
+
543
+ it("does not show an unread dot for child tabs without a persisted ui read baseline", () => {
544
+ mocks.resolvedChildTabs = [
545
+ {
546
+ sessionKey: "child-session-1",
547
+ parentSessionKey: "parent-session-1",
548
+ title: "北京天气",
549
+ agentId: "weather",
550
+ updatedAt: "2026-04-10T09:00:00.000Z",
551
+ lastMessageAt: "2026-04-10T09:00:00.000Z",
552
+ readAt: null,
553
+ sessionTypeLabel: "Codex",
554
+ preferredModel: "openai/gpt-5.3-codex",
555
+ projectName: "project-alpha",
556
+ projectRoot: "/Users/demo/project-alpha",
557
+ },
558
+ {
559
+ sessionKey: "child-session-2",
560
+ parentSessionKey: "parent-session-1",
561
+ title: "上海天气",
562
+ agentId: "weather",
563
+ updatedAt: "2026-04-10T09:05:00.000Z",
564
+ lastMessageAt: "2026-04-10T09:06:00.000Z",
565
+ readAt: null,
566
+ sessionTypeLabel: "Claude Code",
567
+ preferredModel: "anthropic/claude-sonnet-4",
568
+ projectName: "project-beta",
569
+ projectRoot: "/Users/demo/project-beta",
570
+ },
571
+ ];
572
+
573
+ render(
574
+ <ChatChildSessionPanel
575
+ tabs={[
576
+ {
577
+ sessionKey: "child-session-1",
578
+ parentSessionKey: "parent-session-1",
579
+ label: "北京天气",
580
+ agentId: "weather",
581
+ },
582
+ {
583
+ sessionKey: "child-session-2",
584
+ parentSessionKey: "parent-session-1",
585
+ label: "上海天气",
586
+ agentId: "weather",
587
+ },
588
+ ]}
589
+ activeSessionKey="child-session-1"
590
+ onSelectSession={vi.fn()}
591
+ onClose={vi.fn()}
592
+ onBackToParent={vi.fn()}
593
+ />,
594
+ );
595
+
596
+ expect(screen.queryByLabelText("Session has unread updates")).toBeNull();
535
597
  });
536
598
  });
@@ -32,11 +32,10 @@ vi.mock('@/components/chat/presenter/chat-presenter-context', () => ({
32
32
  setQuery: mocks.setQuery,
33
33
  setListMode: mocks.setListMode,
34
34
  selectSession: mocks.selectSession,
35
- markSessionRead: (sessionKey: string | null | undefined, updatedAt: string | null | undefined) =>
36
- sessionKey ? useChatSessionListStore.getState().markSessionRead(sessionKey, updatedAt) : undefined,
37
- hydrateReadWatermarks: (
38
- entries: readonly { sessionKey: string; updatedAt: string | null | undefined }[],
39
- ) => useChatSessionListStore.getState().hydrateReadWatermarks(entries)
35
+ markSessionRead: (
36
+ sessionKey: string | null | undefined,
37
+ readAt: string | null | undefined,
38
+ ) => (sessionKey ? useChatSessionListStore.getState().markSessionRead(sessionKey, readAt) : undefined),
40
39
  }
41
40
  })
42
41
  }));
@@ -132,8 +131,7 @@ function resetSidebarTestState() {
132
131
  }
133
132
  });
134
133
  useChatSessionListStore.setState({
135
- readUpdatedAtBySessionKey: {},
136
- hasHydratedReadWatermarks: false,
134
+ optimisticReadAtBySessionKey: {},
137
135
  snapshot: {
138
136
  ...useChatSessionListStore.getState().snapshot,
139
137
  query: '',
@@ -514,6 +512,8 @@ describe('ChatSidebar session item interactions', () => {
514
512
  key: 'session:ncp-1',
515
513
  createdAt: '2026-03-19T09:00:00.000Z',
516
514
  updatedAt: '2026-03-19T09:05:00.000Z',
515
+ lastMessageAt: '2026-03-19T09:05:00.000Z',
516
+ readAt: '2026-03-19T09:05:00.000Z',
517
517
  label: 'Current Task',
518
518
  sessionType: 'native',
519
519
  sessionTypeMutable: false,
@@ -523,6 +523,8 @@ describe('ChatSidebar session item interactions', () => {
523
523
  key: 'session:ncp-2',
524
524
  createdAt: '2026-03-19T10:00:00.000Z',
525
525
  updatedAt: '2026-03-19T10:05:00.000Z',
526
+ lastMessageAt: '2026-03-19T10:05:00.000Z',
527
+ readAt: '2026-03-19T10:05:00.000Z',
526
528
  label: 'Background Task',
527
529
  sessionType: 'native',
528
530
  sessionTypeMutable: false,
@@ -550,6 +552,8 @@ describe('ChatSidebar session item interactions', () => {
550
552
  key: 'session:ncp-2',
551
553
  createdAt: '2026-03-19T10:00:00.000Z',
552
554
  updatedAt: '2026-03-19T10:06:00.000Z',
555
+ lastMessageAt: '2026-03-19T10:06:00.000Z',
556
+ readAt: '2026-03-19T10:05:00.000Z',
553
557
  label: 'Background Task',
554
558
  sessionType: 'native',
555
559
  sessionTypeMutable: false,
@@ -571,6 +575,8 @@ describe('ChatSidebar session item interactions', () => {
571
575
  key: 'session:ncp-2',
572
576
  createdAt: '2026-03-19T10:00:00.000Z',
573
577
  updatedAt: '2026-03-19T10:06:00.000Z',
578
+ lastMessageAt: '2026-03-19T10:06:00.000Z',
579
+ readAt: '2026-03-19T10:05:00.000Z',
574
580
  label: 'Background Task',
575
581
  sessionType: 'native',
576
582
  sessionTypeMutable: false,
@@ -601,4 +607,27 @@ describe('ChatSidebar session item interactions', () => {
601
607
 
602
608
  expect(screen.queryByLabelText('Session has unread updates')).toBeNull();
603
609
  });
610
+
611
+ it('does not show an unread dot for sessions without a persisted ui read baseline', () => {
612
+ mocks.sessionItems = [
613
+ createSessionItem({
614
+ key: 'session:ncp-legacy',
615
+ createdAt: '2026-03-19T09:00:00.000Z',
616
+ updatedAt: '2026-03-19T09:05:00.000Z',
617
+ lastMessageAt: '2026-03-19T09:05:00.000Z',
618
+ label: 'Legacy Session',
619
+ sessionType: 'native',
620
+ sessionTypeMutable: false,
621
+ messageCount: 1
622
+ })
623
+ ];
624
+
625
+ render(
626
+ <MemoryRouter>
627
+ <ChatSidebar />
628
+ </MemoryRouter>
629
+ );
630
+
631
+ expect(screen.queryByLabelText('Session has unread updates')).toBeNull();
632
+ });
604
633
  });
@@ -149,28 +149,13 @@ const navItems = [
149
149
  function useChatSessionUnreadState(
150
150
  items: readonly NcpSessionListItemView[],
151
151
  selectedSessionKey: string | null,
152
- markSessionRead: (sessionKey: string | null | undefined, updatedAt: string | null | undefined) => void,
153
- hydrateReadWatermarks: (
154
- entries: readonly { sessionKey: string; updatedAt: string | null | undefined }[],
152
+ markSessionRead: (
153
+ sessionKey: string | null | undefined,
154
+ readAt: string | null | undefined,
155
+ currentReadAt?: string | null,
155
156
  ) => void,
156
157
  ): Record<string, string> {
157
- const readUpdatedAtBySessionKey = useChatSessionListStore((state) => state.readUpdatedAtBySessionKey);
158
- const hasHydratedReadWatermarks = useChatSessionListStore((state) => state.hasHydratedReadWatermarks);
159
-
160
- useEffect(() => {
161
- const syncHydratedReadWatermarks = () => {
162
- if (hasHydratedReadWatermarks || items.length === 0) {
163
- return;
164
- }
165
- hydrateReadWatermarks(
166
- items.map(({ session }) => ({
167
- sessionKey: session.key,
168
- updatedAt: session.updatedAt
169
- }))
170
- );
171
- };
172
- syncHydratedReadWatermarks();
173
- }, [hasHydratedReadWatermarks, hydrateReadWatermarks, items]);
158
+ const optimisticReadAtBySessionKey = useChatSessionListStore((state) => state.optimisticReadAtBySessionKey);
174
159
 
175
160
  useEffect(() => {
176
161
  const syncSelectedSessionReadState = () => {
@@ -182,12 +167,16 @@ function useChatSessionUnreadState(
182
167
  return;
183
168
  }
184
169
  const { session: selectedSession } = selectedItem;
185
- markSessionRead(selectedSession.key, selectedSession.updatedAt);
170
+ markSessionRead(
171
+ selectedSession.key,
172
+ selectedSession.lastMessageAt,
173
+ selectedSession.readAt,
174
+ );
186
175
  };
187
176
  syncSelectedSessionReadState();
188
177
  }, [items, markSessionRead, selectedSessionKey]);
189
178
 
190
- return readUpdatedAtBySessionKey;
179
+ return optimisticReadAtBySessionKey;
191
180
  }
192
181
 
193
182
  export function ChatSidebar() {
@@ -220,11 +209,10 @@ export function ChatSidebar() {
220
209
  [defaultSessionType, inputSnapshot.sessionTypeOptions]
221
210
  );
222
211
  const isProjectFirstView = listSnapshot.listMode === 'project-first';
223
- const readUpdatedAtBySessionKey = useChatSessionUnreadState(
212
+ const optimisticReadAtBySessionKey = useChatSessionUnreadState(
224
213
  items,
225
214
  listSnapshot.selectedSessionKey,
226
215
  presenter.chatSessionListManager.markSessionRead,
227
- presenter.chatSessionListManager.hydrateReadWatermarks,
228
216
  );
229
217
  const handleLanguageSwitch = (nextLang: I18nLanguage) => {
230
218
  if (language === nextLang) return;
@@ -261,10 +249,15 @@ export function ChatSidebar() {
261
249
  };
262
250
  const renderSessionItem = ({ session, runStatus }: NcpSessionListItemView) => {
263
251
  const active = listSnapshot.selectedSessionKey === session.key;
252
+ const optimisticReadAt = optimisticReadAtBySessionKey[session.key];
253
+ const effectiveReadAt =
254
+ optimisticReadAt && session.readAt
255
+ ? (optimisticReadAt.localeCompare(session.readAt) > 0 ? optimisticReadAt : session.readAt)
256
+ : optimisticReadAt ?? session.readAt;
264
257
  const showUnreadDot = shouldShowUnreadSessionIndicator({
265
258
  active,
266
- updatedAt: session.updatedAt,
267
- readUpdatedAt: readUpdatedAtBySessionKey[session.key],
259
+ lastMessageAt: session.lastMessageAt,
260
+ readAt: effectiveReadAt,
268
261
  runStatus,
269
262
  });
270
263
  const context = resolveSessionContextView(session, inputSnapshot.sessionTypeOptions);