@rexvane/piweb 0.3.10 → 0.3.11

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 (91) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +3 -3
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/prerender-manifest.json +3 -3
  5. package/.next/react-loadable-manifest.json +2 -2
  6. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  7. package/.next/server/app/_global-error.html +1 -1
  8. package/.next/server/app/_global-error.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  12. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  13. package/.next/server/app/_not-found.html +1 -1
  14. package/.next/server/app/_not-found.rsc +2 -2
  15. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  16. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  17. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  18. package/.next/server/app/api/agent/[id]/events/route.js +2 -2
  19. package/.next/server/app/api/agent/[id]/events/route.js.nft.json +1 -1
  20. package/.next/server/app/api/agent/[id]/route.js +1 -1
  21. package/.next/server/app/api/agent/[id]/route.js.nft.json +1 -1
  22. package/.next/server/app/api/agent/[id]/tree/route.js +1 -1
  23. package/.next/server/app/api/agent/[id]/tree/route.js.nft.json +1 -1
  24. package/.next/server/app/api/agent/new/route.js +1 -1
  25. package/.next/server/app/api/agent/new/route.js.nft.json +1 -1
  26. package/.next/server/app/api/growth/route.js +1 -1
  27. package/.next/server/app/api/growth/route.js.nft.json +1 -1
  28. package/.next/server/app/api/models/route.js +1 -1
  29. package/.next/server/app/api/models/route.js.nft.json +1 -1
  30. package/.next/server/app/api/plugins/route.js +1 -1
  31. package/.next/server/app/api/plugins/route.js.nft.json +1 -1
  32. package/.next/server/app/api/security/route.js +1 -1
  33. package/.next/server/app/api/security/route.js.nft.json +1 -1
  34. package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
  35. package/.next/server/app/api/sessions/[id]/export/route.js.nft.json +1 -1
  36. package/.next/server/app/api/sessions/[id]/route.js +1 -1
  37. package/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
  38. package/.next/server/app/api/sessions/route.js +1 -1
  39. package/.next/server/app/api/sessions/route.js.nft.json +1 -1
  40. package/.next/server/app/api/skills/route.js +2 -2
  41. package/.next/server/app/api/skills/route.js.nft.json +1 -1
  42. package/.next/server/app/api/update/route.js +2 -2
  43. package/.next/server/app/api/update/route.js.nft.json +1 -1
  44. package/.next/server/app/api/version/route.js +1 -1
  45. package/.next/server/app/api/workspaces/route.js +1 -1
  46. package/.next/server/app/api/workspaces/route.js.nft.json +1 -1
  47. package/.next/server/app/index.html +1 -1
  48. package/.next/server/app/index.rsc +4 -4
  49. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
  50. package/.next/server/app/index.segments/_full.segment.rsc +4 -4
  51. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  52. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  53. package/.next/server/app/login.html +1 -1
  54. package/.next/server/app/login.rsc +3 -3
  55. package/.next/server/app/login.segments/_full.segment.rsc +3 -3
  56. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  57. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +3 -3
  58. package/.next/server/app/page.js +1 -1
  59. package/.next/server/app/page_client-reference-manifest.js +1 -1
  60. package/.next/server/app-paths-manifest.json +3 -3
  61. package/.next/server/chunks/229.js +1 -0
  62. package/.next/server/chunks/496.js +1 -1
  63. package/.next/server/chunks/686.js +2 -2
  64. package/.next/server/middleware-build-manifest.js +1 -1
  65. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  66. package/.next/server/pages/404.html +1 -1
  67. package/.next/server/pages/500.html +1 -1
  68. package/.next/server/server-reference-manifest.json +1 -1
  69. package/.next/static/chunks/127.c31e688e156605e3.js +1 -0
  70. package/.next/static/chunks/686-9f15e4e370d26815.js +1 -0
  71. package/.next/static/chunks/app/page-708cd3ee4cfe8af5.js +1 -0
  72. package/.next/static/chunks/webpack-9d84b382d4dec27a.js +1 -0
  73. package/.next/static/css/b2d35abc828d4713.css +1 -0
  74. package/package.json +1 -1
  75. package/src/components/ChatWindow.tsx +43 -1
  76. package/src/components/SettingsPanel.tsx +127 -2
  77. package/src/hooks/usePiWeb.ts +13 -1
  78. package/src/i18n.tsx +44 -0
  79. package/src/lib/agent-manager.ts +12 -0
  80. package/src/lib/event-batcher.ts +62 -0
  81. package/src/lib/models-service.ts +430 -19
  82. package/src/lib/provider-usage.ts +91 -0
  83. package/src/lib/session-retention.ts +119 -0
  84. package/.next/server/chunks/780.js +0 -1
  85. package/.next/static/chunks/18.8b025ee3303c3dc7.js +0 -1
  86. package/.next/static/chunks/686-97c833615d1565c5.js +0 -1
  87. package/.next/static/chunks/app/page-19e37fe240ae3e20.js +0 -1
  88. package/.next/static/chunks/webpack-eebc3d300017fa47.js +0 -1
  89. package/.next/static/css/080ec3dba8907e83.css +0 -1
  90. /package/.next/static/{CLy0QZWDpEExcItT1yudl → 222KLUIvefiGf7M0Ov-Qz}/_buildManifest.js +0 -0
  91. /package/.next/static/{CLy0QZWDpEExcItT1yudl → 222KLUIvefiGf7M0Ov-Qz}/_ssgManifest.js +0 -0
@@ -0,0 +1 @@
1
+ /*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,::backdrop,:after,:before{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial}}}@layer theme{:host,:root{--font-sans:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue","Noto Sans",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-zinc-200:oklch(92% .004 286.32);--color-zinc-300:oklch(87.1% .006 286.286);--color-zinc-400:oklch(70.5% .015 286.067);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-md:28rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--font-weight-medium:500;--font-weight-bold:700;--leading-relaxed:1.625;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--radius-2xl:1rem;--radius-3xl:1.5rem;--animate-spin:spin 1s linear infinite;--blur-sm:8px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,::backdrop,:after,:before{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}:host,html{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue","Noto Sans",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}menu,ol,ul{list-style:none}audio,canvas,embed,iframe,img,object,svg,video{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,optgroup,select,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit,::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.\!relative{position:relative!important}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.sticky{position:sticky}.inset-0{inset:0}.inset-x-0{inset-inline:0}.-top-1{top:calc(var(--spacing) * -1)}.-top-1\.5{top:calc(var(--spacing) * -1.5)}.top-0{top:0}.top-0\.5{top:calc(var(--spacing) * .5)}.top-1\/2{top:50%}.top-3{top:calc(var(--spacing) * 3)}.top-5{top:calc(var(--spacing) * 5)}.top-6{top:calc(var(--spacing) * 6)}.top-8{top:calc(var(--spacing) * 8)}.top-9{top:calc(var(--spacing) * 9)}.top-full{top:100%}.-right-1{right:calc(var(--spacing) * -1)}.-right-1\.5{right:calc(var(--spacing) * -1.5)}.right-0{right:0}.right-1{right:var(--spacing)}.right-2{right:calc(var(--spacing) * 2)}.right-4{right:calc(var(--spacing) * 4)}.right-5{right:calc(var(--spacing) * 5)}.bottom-2{bottom:calc(var(--spacing) * 2)}.bottom-9{bottom:calc(var(--spacing) * 9)}.bottom-10{bottom:calc(var(--spacing) * 10)}.bottom-24{bottom:calc(var(--spacing) * 24)}.bottom-full{bottom:100%}.left-0{left:0}.left-1\/2{left:50%}.left-3{left:calc(var(--spacing) * 3)}.z-20{z-index:20}.z-50{z-index:50}.z-\[70\]{z-index:70}.z-\[80\]{z-index:80}.z-\[85\]{z-index:85}.z-\[95\]{z-index:95}.z-\[100\]{z-index:100}.z-\[105\]{z-index:105}.z-\[110\]{z-index:110}.z-\[120\]{z-index:120}.z-\[130\]{z-index:130}.z-\[9999\]{z-index:9999}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.mx-1{margin-inline:var(--spacing)}.mx-3{margin-inline:calc(var(--spacing) * 3)}.mx-auto{margin-inline:auto}.my-1{margin-block:var(--spacing)}.my-1\.5{margin-block:calc(var(--spacing) * 1.5)}.my-2{margin-block:calc(var(--spacing) * 2)}.-mt-3{margin-top:calc(var(--spacing) * -3)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-5{margin-top:calc(var(--spacing) * 5)}.mt-6{margin-top:calc(var(--spacing) * 6)}.mt-7{margin-top:calc(var(--spacing) * 7)}.mb-1{margin-bottom:var(--spacing)}.mb-1\.5{margin-bottom:calc(var(--spacing) * 1.5)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-5{margin-bottom:calc(var(--spacing) * 5)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.h-1{height:var(--spacing)}.h-1\.5{height:calc(var(--spacing) * 1.5)}.h-2{height:calc(var(--spacing) * 2)}.h-3{height:calc(var(--spacing) * 3)}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-8{height:calc(var(--spacing) * 8)}.h-11{height:calc(var(--spacing) * 11)}.h-14{height:calc(var(--spacing) * 14)}.h-\[42px\]{height:42px}.h-full{height:100%}.h-screen{height:100vh}.max-h-72{max-height:calc(var(--spacing) * 72)}.max-h-96{max-height:calc(var(--spacing) * 96)}.max-h-\[70vh\]{max-height:70vh}.max-h-\[340px\]{max-height:340px}.min-h-0{min-height:0}.min-h-10{min-height:calc(var(--spacing) * 10)}.min-h-screen{min-height:100vh}.w-1\.5{width:calc(var(--spacing) * 1.5)}.w-2{width:calc(var(--spacing) * 2)}.w-3{width:calc(var(--spacing) * 3)}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-4{width:calc(var(--spacing) * 4)}.w-5{width:calc(var(--spacing) * 5)}.w-8{width:calc(var(--spacing) * 8)}.w-9{width:calc(var(--spacing) * 9)}.w-14{width:calc(var(--spacing) * 14)}.w-36{width:calc(var(--spacing) * 36)}.w-44{width:calc(var(--spacing) * 44)}.w-52{width:calc(var(--spacing) * 52)}.w-56{width:calc(var(--spacing) * 56)}.w-64{width:calc(var(--spacing) * 64)}.w-\[260px\]{width:260px}.w-\[312px\]{width:312px}.w-\[calc\(100\%-8px\)\]{width:calc(100% - 8px)}.w-\[min\(92vw\,360px\)\]{width:min(92vw,360px)}.w-\[min\(92vw\,380px\)\]{width:min(92vw,380px)}.w-\[min\(92vw\,480px\)\]{width:min(92vw,480px)}.w-full{width:100%}.w-max{width:max-content}.w-screen{width:100vw}.max-w-\[45\%\]{max-width:45%}.max-w-\[85\%\]{max-width:85%}.max-w-\[90vw\]{max-width:90vw}.max-w-\[180px\]{max-width:180px}.max-w-\[220px\]{max-width:220px}.max-w-\[420px\]{max-width:420px}.max-w-\[calc\(100vw-24px\)\]{max-width:calc(100vw - 24px)}.max-w-full{max-width:100%}.max-w-md{max-width:var(--container-md)}.min-w-0{min-width:0}.min-w-4{min-width:calc(var(--spacing) * 4)}.min-w-\[86px\]{min-width:86px}.flex-1{flex:1}.flex-none{flex:none}.grow{flex-grow:1}.-translate-x-1\/2{--tw-translate-x:calc(calc(1 / 2 * 100%) * -1)}.-translate-x-1\/2,.-translate-y-1\/2{translate:var(--tw-translate-x) var(--tw-translate-y)}.-translate-y-1\/2{--tw-translate-y:calc(calc(1 / 2 * 100%) * -1)}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.animate-spin{animation:var(--animate-spin)}.cursor-col-resize{cursor:col-resize}.cursor-pointer{cursor:pointer}.resize-none{resize:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.items-stretch{align-items:stretch}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-2\.5{gap:calc(var(--spacing) * 2.5)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-3\.5{gap:calc(var(--spacing) * 3.5)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-5{gap:calc(var(--spacing) * 5)}.gap-6{gap:calc(var(--spacing) * 6)}.gap-\[6px\]{gap:6px}.gap-x-2{column-gap:calc(var(--spacing) * 2)}.gap-x-3{column-gap:calc(var(--spacing) * 3)}.gap-y-1{row-gap:var(--spacing)}.self-start{align-self:flex-start}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-3xl{border-radius:var(--radius-3xl)}.rounded-\[10px\]{border-radius:10px}.rounded-\[24px\]{border-radius:24px}.rounded-full{border-radius:3.40282e+38px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-\[var\(--dsw-border-l1\)\]{border-color:var(--dsw-border-l1)}.border-\[var\(--dsw-label-tertiary\)\]{border-color:var(--dsw-label-tertiary)}.border-current{border-color:currentColor}.border-transparent{border-color:#0000}.border-white\/10{border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.border-white\/10{border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.border-white\/15{border-color:#ffffff26}@supports (color:color-mix(in lab,red,red)){.border-white\/15{border-color:color-mix(in oklab,var(--color-white) 15%,transparent)}}.border-t-transparent{border-top-color:#0000}.bg-\[\#2c2c2c\]{background-color:#2c2c2c}.bg-\[\#212121\]{background-color:#212121}.bg-black\/60{background-color:#0009}@supports (color:color-mix(in lab,red,red)){.bg-black\/60{background-color:color-mix(in oklab,var(--color-black) 60%,transparent)}}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.bg-white\/\[0\.05\]{background-color:#ffffff0d}@supports (color:color-mix(in lab,red,red)){.bg-white\/\[0\.05\]{background-color:color-mix(in oklab,var(--color-white) 5%,transparent)}}.object-contain{object-fit:contain}.object-cover{object-fit:cover}.p-1{padding:var(--spacing)}.p-1\.5{padding:calc(var(--spacing) * 1.5)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-6{padding:calc(var(--spacing) * 6)}.p-7{padding:calc(var(--spacing) * 7)}.px-1{padding-inline:var(--spacing)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-3\.5{padding-inline:calc(var(--spacing) * 3.5)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.px-12{padding-inline:calc(var(--spacing) * 12)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-3\.5{padding-block:calc(var(--spacing) * 3.5)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-5{padding-block:calc(var(--spacing) * 5)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-10{padding-block:calc(var(--spacing) * 10)}.pt-1{padding-top:var(--spacing)}.pt-2{padding-top:calc(var(--spacing) * 2)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pt-4{padding-top:calc(var(--spacing) * 4)}.pr-1{padding-right:var(--spacing)}.pr-2{padding-right:calc(var(--spacing) * 2)}.pr-3{padding-right:calc(var(--spacing) * 3)}.pr-4{padding-right:calc(var(--spacing) * 4)}.pr-8{padding-right:calc(var(--spacing) * 8)}.pb-0{padding-bottom:0}.pb-1{padding-bottom:var(--spacing)}.pb-1\.5{padding-bottom:calc(var(--spacing) * 1.5)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pb-5{padding-bottom:calc(var(--spacing) * 5)}.pl-1{padding-left:var(--spacing)}.pl-3{padding-left:calc(var(--spacing) * 3)}.pl-4{padding-left:calc(var(--spacing) * 4)}.pl-5{padding-left:calc(var(--spacing) * 5)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[14\.5px\]{font-size:14.5px}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.text-ellipsis{text-overflow:ellipsis}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-\[\#f87171\]{color:#f87171}.text-\[var\(--dsw-label-primary\)\]{color:var(--dsw-label-primary)}.text-black{color:var(--color-black)}.text-white{color:var(--color-white)}.text-zinc-200{color:var(--color-zinc-200)}.text-zinc-300{color:var(--color-zinc-300)}.text-zinc-400{color:var(--color-zinc-400)}.lowercase{text-transform:lowercase}.uppercase{text-transform:uppercase}.line-through{text-decoration-line:line-through}.underline{text-decoration-line:underline}.opacity-0{opacity:0}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040)}.shadow-2xl,.shadow-xl{box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a)}.ring{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.backdrop-blur-sm{--tw-backdrop-blur:blur(var(--blur-sm));-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-150{--tw-duration:.15s;transition-duration:.15s}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.group-focus-within\:flex:is(:where(.group):focus-within *){display:flex}.group-focus-within\:hidden:is(:where(.group):focus-within *){display:none}@media (hover:hover){.group-hover\:block:is(:where(.group):hover *){display:block}.group-hover\:flex:is(:where(.group):hover *){display:flex}.group-hover\:hidden:is(:where(.group):hover *){display:none}.group-hover\/tool\:opacity-100:is(:where(.group\/tool):hover *){opacity:1}}.first\:mt-0:first-child{margin-top:0}@media (hover:hover){.hover\:border-\[\#f87171\]\/40:hover{border-color:oklab(71.0627% .153814 .0628211/.4)}.hover\:border-white\/25:hover{border-color:#ffffff40}@supports (color:color-mix(in lab,red,red)){.hover\:border-white\/25:hover{border-color:color-mix(in oklab,var(--color-white) 25%,transparent)}}.hover\:bg-\[\#383838\]:hover{background-color:#383838}.hover\:bg-\[var\(--dsw-hover\)\]:hover{background-color:var(--dsw-hover)}.hover\:bg-white\/10:hover{background-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.hover\:bg-white\/10:hover{background-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.hover\:bg-zinc-200:hover{background-color:var(--color-zinc-200)}.hover\:text-\[\#ef4444\]:hover{color:#ef4444}.hover\:text-white:hover{color:var(--color-white)}.hover\:underline:hover{text-decoration-line:underline}}.focus\:border-white\/40:focus{border-color:#fff6}@supports (color:color-mix(in lab,red,red)){.focus\:border-white\/40:focus{border-color:color-mix(in oklab,var(--color-white) 40%,transparent)}}.focus\:ring-1:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.focus\:ring-white\/30:focus{--tw-ring-color:#ffffff4d}@supports (color:color-mix(in lab,red,red)){.focus\:ring-white\/30:focus{--tw-ring-color:color-mix(in oklab,var(--color-white) 30%,transparent)}}.focus-visible\:opacity-100:focus-visible{opacity:1}.disabled\:cursor-wait:disabled{cursor:wait}.disabled\:opacity-50:disabled{opacity:.5}@media (min-width:40rem){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}}:root{--dsw-deepseek-400:#679efe;--dsw-deepseek-450:#5686fe;--dsw-deepseek-500:#4176e6;--dsw-amber-500:#f59e0b;--dsw-green-500:#22c55e;--dsw-red-600:#ec1313;--dsw-bg-base:#fff;--dsw-bg-elevated:#fff;--dsw-sidebar-fill:#f9fafb;--dsw-input-major:#fff;--dsw-bubble:#eff4ff;--dsw-hover:#0000000a;--dsw-active:#0000000f;--dsw-label-primary:#000;--dsw-label-secondary:#404042;--dsw-label-tertiary:#6e6e73;--dsw-label-caption:#96969b;--dsw-border-l1:#0000000a;--dsw-border-l2:#0000001a;--dsw-border-l3:#0000001f;--dsw-border-l4:#00000029;--dsw-accent:var(--dsw-deepseek-500);--dsw-accent-soft:#4176e61a;--dsw-warn:var(--dsw-amber-500);--dsw-success:var(--dsw-green-500);--dsw-danger:var(--dsw-red-600);--dsw-selector:#0000000d;--dsw-layer-2:#fff;--dsw-layer-3:#fff;--dsw-btn-elevated:#fff;--dsw-btn-floating-hover:#f0f1f3;--dsw-module-platform:#eeeff1;--dsw-border-strong:#adb2b8;--dsw-glass-popover:#fff;--dsw-glass-input:#fff;--dsw-glass-modal:#fff;--dsw-mask:#0000003d;--dsw-mask-blur:blur(6px);--dsw-elevation-stroke:var(--dsw-border-l2);--dsw-elevation-soft:0 0 0 .5px var(--dsw-elevation-stroke),0 4px 16px #00000008,0 0 24px #00000008;--dsw-elevation-panel:0 0 0 .5px var(--dsw-elevation-stroke),0 8px 32px #00000014;--dsw-elevation-prominent:0 0 0 .5px var(--dsw-elevation-stroke),0 16px 48px #00000029;--ds-ease-in-out:cubic-bezier(.4,0,.2,1);--ds-duration-fast:.1s;--ds-duration-normal:.2s;--ds-duration-slow:.3s;--dsh-content-font-size:14px;--dsh-content-font-delta:0px;--dsh-content-font-size-secondary:13px;--piweb-chat-font-size:var(--dsh-content-font-size);--piweb-chat-font-l:calc(var(--piweb-chat-font-size) * .96);--piweb-chat-font-m:calc(var(--piweb-chat-font-size) * .93);--piweb-chat-font-s:calc(var(--piweb-chat-font-size) * .89);--piweb-chat-font-t:calc(var(--piweb-chat-font-size) * .86);--piweb-chat-font-xs:calc(var(--piweb-chat-font-size) * .82);--font-ui:"Maple Mono CN",ui-monospace,SFMono-Regular,Menlo,Consolas,"PingFang SC","Microsoft YaHei",monospace;--font-mono:"Maple Mono CN",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;--dsh-chat-content-width:clamp(544px,51.2%,736px);--dsh-composer-card-max-width:calc(var(--dsh-chat-content-width) + 32px)}@media (prefers-color-scheme:dark){:root:not([data-ds-light-theme]),body:not([data-ds-light-theme]){--dsw-bg-base:#151517;--dsw-bg-elevated:#1e1e20;--dsw-sidebar-fill:#1b1b1c;--dsw-input-major:#2c2c2e;--dsw-bubble:#26282e;--dsw-hover:#ffffff0d;--dsw-active:#ffffff14;--dsw-label-primary:#f9fafb;--dsw-label-secondary:#cfd3d6;--dsw-label-tertiary:#adb2b8;--dsw-label-caption:#81858c;--dsw-border-l1:#ffffff0f;--dsw-border-l2:#ffffff1f;--dsw-border-l3:#ffffff29;--dsw-border-l4:#fff3;--dsw-accent:var(--dsw-deepseek-400);--dsw-accent-soft:#679efe24;--dsw-danger:#f25a5a;--dsw-selector:#ffffff14;--dsw-layer-2:#2c2c2e;--dsw-layer-3:#353638;--dsw-btn-elevated:#43454a;--dsw-btn-floating-hover:#353638;--dsw-module-platform:#353638;--dsw-glass-popover:#353638;--dsw-glass-input:#2c2c2e;--dsw-glass-modal:#2c2c2e;--dsw-mask:#00000080;--dsw-elevation-stroke:var(--dsw-border-l2);--dsw-elevation-soft:0 0 0 .5px var(--dsw-elevation-stroke),0 4px 16px #0003,0 0 24px #0003;--dsw-elevation-panel:0 0 0 .5px var(--dsw-elevation-stroke),0 8px 32px #0006;--dsw-elevation-prominent:0 0 0 .5px var(--dsw-elevation-stroke),0 16px 48px #0000008c}}body[data-ds-dark-theme],html[data-ds-dark-theme]{--dsw-bg-base:#151517;--dsw-bg-elevated:#1e1e20;--dsw-sidebar-fill:#1b1b1c;--dsw-input-major:#2c2c2e;--dsw-bubble:#26282e;--dsw-hover:#ffffff0d;--dsw-active:#ffffff14;--dsw-label-primary:#f9fafb;--dsw-label-secondary:#cfd3d6;--dsw-label-tertiary:#adb2b8;--dsw-label-caption:#81858c;--dsw-border-l1:#ffffff0f;--dsw-border-l2:#ffffff1f;--dsw-border-l3:#ffffff29;--dsw-border-l4:#fff3;--dsw-accent:var(--dsw-deepseek-400);--dsw-accent-soft:#679efe24;--dsw-danger:#f25a5a;--dsw-selector:#ffffff14;--dsw-layer-2:#2c2c2e;--dsw-layer-3:#353638;--dsw-btn-elevated:#43454a;--dsw-btn-floating-hover:#353638;--dsw-module-platform:#353638;--dsw-glass-popover:#353638;--dsw-glass-input:#2c2c2e;--dsw-glass-modal:#2c2c2e;--dsw-mask:#00000080;--dsw-elevation-stroke:var(--dsw-border-l2);--dsw-elevation-soft:0 0 0 .5px var(--dsw-elevation-stroke),0 4px 16px #0003,0 0 24px #0003;--dsw-elevation-panel:0 0 0 .5px var(--dsw-elevation-stroke),0 8px 32px #0006;--dsw-elevation-prominent:0 0 0 .5px var(--dsw-elevation-stroke),0 16px 48px #0000008c}body,html{height:100%}body{background:var(--dsw-bg-base);color:var(--dsw-label-primary);font-family:var(--font-ui);font-size:var(--dsh-content-font-size);line-height:calc(24px + var(--dsh-content-font-delta));-webkit-font-smoothing:antialiased;overflow:hidden}button{font-size:inherit;cursor:pointer;padding:0}button,input,select,textarea{font-family:inherit;color:inherit;background:0 0;border:none}input,select,textarea{outline:none}select{color-scheme:light}select option{background:var(--dsw-layer-2);color:var(--dsw-label-primary)}body[data-ds-dark-theme] select{color-scheme:dark}input::placeholder,textarea::placeholder{color:var(--dsw-label-caption)}*{box-sizing:border-box;corner-shape:superellipse(1.5)}@media (prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}@media (max-width:840px){:root{--dsh-chat-content-width:100%;--dsh-composer-card-max-width:100%}.pi-main>.hairline-b{padding-left:56px;padding-right:12px}.settings-dialog{flex-direction:column;border-radius:22px!important;width:calc(100vw - 16px)!important;max-width:none!important;height:calc(100vh - 16px)!important}.settings-nav{border-bottom:.5px solid var(--dsw-border-l2);overflow-x:auto;flex-direction:row!important;width:100%!important;padding:10px!important}.settings-nav>button{flex:none;height:36px}.settings-title{display:none}}::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-thumb{background:var(--dsw-active);background-clip:content-box;border:2.5px solid #0000;border-radius:8px}::-webkit-scrollbar-thumb:hover{background:var(--dsw-border-l3);background-clip:content-box;border:2px solid #0000}::-webkit-scrollbar-track{background:0 0}.icon-btn{width:28px;height:28px;color:var(--dsw-label-secondary);transition:background var(--ds-duration-fast) var(--ds-ease-in-out),color var(--ds-duration-fast) var(--ds-ease-in-out);border-radius:8px;justify-content:center;align-items:center;display:inline-flex}.icon-btn:hover{background:var(--dsw-hover)}.chip,.icon-btn:hover{color:var(--dsw-label-primary)}.chip{height:28px;white-space:nowrap;transition:background var(--ds-duration-fast) var(--ds-ease-in-out),color var(--ds-duration-fast) var(--ds-ease-in-out);border-radius:24px;align-items:center;gap:5px;padding:0 10px;font-size:13px;font-weight:500;line-height:20px;display:inline-flex}.chip:hover{background:var(--dsw-hover);color:var(--dsw-label-primary)}.chip .chevron{color:var(--dsw-label-caption);transition:transform .12s var(--ds-ease-in-out)}.chip[data-open=true] .chevron{transform:rotate(180deg)}.hero-chip{height:28px;color:var(--dsw-label-primary);white-space:nowrap;cursor:pointer;transition:background var(--ds-duration-fast) var(--ds-ease-in-out);background:0 0;border-radius:14px;align-items:center;gap:6px;padding:0 8px;font-size:13px;font-weight:500;line-height:20px;display:inline-flex}.hero-chip:hover,.hero-chip[data-open=true]{background:var(--dsw-hover)}.hero-chip .hero-chip-icon{color:var(--dsw-label-primary);flex:none}.hero-chip .chevron{color:var(--dsw-label-caption);transition:transform .12s var(--ds-ease-in-out);flex:none}.hero-chip[data-open=true] .chevron{transform:rotate(180deg)}.btn-outline{border:.5px solid var(--dsw-border-l3);height:36px;color:var(--dsw-label-primary);transition:background var(--ds-duration-fast) var(--ds-ease-in-out);background:0 0;border-radius:18px;justify-content:center;align-items:center;gap:6px;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}.btn-outline:hover{background:var(--dsw-hover)}.btn-primary-white{background:var(--dsw-label-primary);height:36px;color:var(--dsw-bg-base);transition:opacity var(--ds-duration-fast) var(--ds-ease-in-out);border-radius:18px;justify-content:center;align-items:center;gap:6px;padding:0 14px;font-size:14px;font-weight:500;line-height:22px;display:inline-flex}.btn-primary-white:disabled{opacity:.5;cursor:default}.btn-new-session{border:.5px solid var(--dsw-border-l3);background:var(--dsw-btn-elevated);height:38px;color:var(--dsw-label-primary);transition:background var(--ds-duration-fast) var(--ds-ease-in-out);border-radius:12px;justify-content:center;align-items:center;gap:6px;padding:8px 16px;font-size:14px;font-weight:500;line-height:22px;display:inline-flex}.btn-new-session:hover{background:var(--dsw-btn-floating-hover)}.select-chip{border:.5px solid var(--dsw-border-l2);height:32px;color:var(--dsw-label-primary);transition:background var(--ds-duration-fast) var(--ds-ease-in-out);border-radius:16px;align-items:center;gap:6px;padding:0 12px;font-size:13px;display:inline-flex}.select-chip:hover{background:var(--dsw-hover)}.tab-underline{color:var(--dsw-label-tertiary);transition:color var(--ds-duration-fast) var(--ds-ease-in-out);padding:6px 2px;font-size:13.5px;position:relative}.tab-underline:hover{color:var(--dsw-label-primary)}.tab-underline[data-active=true]{color:var(--dsw-accent);font-weight:600}.tab-underline:after{content:"";background:0 0;border-radius:2px;height:2px;position:absolute;bottom:-1px;left:0;right:0}.tab-underline[data-active=true]:after{background:var(--dsw-accent)}.settings-tab-underline{color:var(--dsw-label-tertiary);transition:color var(--ds-duration-fast) var(--ds-ease-in-out);padding:6px 2px 10px;font-size:13.5px;position:relative}.settings-tab-underline:hover{color:var(--dsw-label-primary)}.settings-tab-underline[data-active=true]{color:var(--dsw-label-primary);font-weight:500}.settings-tab-underline:after{content:"";background:0 0;border-radius:2px;height:2px;position:absolute;bottom:-1px;left:0;right:0}.settings-tab-underline[data-active=true]:after{background:var(--dsw-label-primary)}.btn-primary-circle{background:var(--dsw-accent);color:#fff;width:34px;height:34px;transition:opacity var(--ds-duration-fast) var(--ds-ease-in-out),transform var(--ds-duration-fast) var(--ds-ease-in-out);border-radius:999px;flex:none;justify-content:center;align-items:center;margin-top:-3px;margin-bottom:-3px;display:inline-flex;transform:translateY(-2px)}.btn-primary-circle:disabled{opacity:.4;cursor:default}.btn-ghost-circle{width:30px;height:30px;color:var(--dsw-label-secondary);border:.5px solid var(--dsw-border-l2);background:0 0;border-radius:999px;flex:none;justify-content:center;align-items:center;margin-top:-3px;margin-bottom:-3px;display:inline-flex;transform:translateY(-2px)}.btn-ghost-circle:hover{color:var(--dsw-danger);border-color:var(--dsw-danger)}.pw-queue{flex-direction:column;gap:3px;margin:0 14px 8px;font-size:12.5px;display:flex}.pw-queue-row{min-width:0;color:var(--dsw-label-secondary);align-items:center;gap:8px;display:flex}.pw-queue-tag{text-transform:capitalize;color:var(--dsw-label-tertiary);border:.5px solid var(--dsw-border-l2);border-radius:4px;flex:none;padding:0 6px;font-size:10.5px;line-height:16px}.pw-queue-text{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.pw-queue-foot{color:var(--dsw-label-caption);gap:12px;margin-top:1px;padding-left:2px;font-size:11.5px;display:flex}.pw-queue-foot button:hover{color:var(--dsw-label-secondary)}.pw-busy-hint{color:var(--dsw-label-caption);margin:6px 14px 0;font-size:11.5px;line-height:16px}.hairline-t{border-top:.5px solid var(--dsw-border-l2)}.hairline-b{border-bottom:.5px solid var(--dsw-border-l2)}.hairline-r{border-right:.5px solid var(--dsw-border-l2)}.rail-btn{width:36px;height:36px;color:var(--dsw-label-secondary);transition:background var(--ds-duration-fast) var(--ds-ease-in-out),color var(--ds-duration-fast) var(--ds-ease-in-out);border-radius:12px;justify-content:center;align-items:center;display:inline-flex}.rail-btn:hover{background:var(--dsw-hover);color:var(--dsw-label-primary)}.state-dot{border-radius:999px;flex:none;width:8px;height:8px;position:relative}.state-dot.running{background:var(--dsw-deepseek-450);animation:dot-breathe 1s ease-in-out infinite}.state-dot.pending{background:var(--dsw-warn);box-shadow:0 0 0 3px #f59e0b1a}.state-dot.done{background:var(--dsw-success)}@keyframes dot-breathe{0%,to{opacity:1}50%{opacity:.45}}.popover{background:var(--dsw-glass-popover);box-shadow:var(--dsw-elevation-prominent);border:.5px solid var(--dsw-border-l1);border-radius:16px;padding:6px;overflow:hidden}.modal-mask{background:var(--dsw-mask);-webkit-backdrop-filter:var(--dsw-mask-blur)}.glass-modal{background:var(--dsw-glass-modal)}.md{overflow-wrap:break-word;min-width:0;font-size:var(--piweb-chat-font-size);line-height:1.65}.md>:first-child{margin-top:0}.md>:last-child{margin-bottom:0}.md p{margin:.5em 0}.md h1,.md h2,.md h3,.md h4{margin:1em 0 .4em;font-weight:600;line-height:1.35}.md h1{font-size:1.35em}.md h2{font-size:1.22em}.md h3{font-size:1.1em}.md ol,.md ul{margin:.5em 0;padding-left:1.5em}.md li{margin:.2em 0}.md a{color:var(--dsw-accent);text-decoration:none}.md a:hover{text-decoration:underline}.md blockquote{border-left:3px solid var(--dsw-border-l3);color:var(--dsw-label-secondary);margin:.6em 0;padding:.1em 0 .1em .9em}.md code{font-family:var(--font-mono);border-radius:5px;padding:.1em .35em;font-size:.9em}.md code,.md pre{background:var(--dsw-selector)}.md pre{border:.5px solid var(--dsw-border-l1);border-radius:12px;margin:.7em 0;padding:12px 14px;overflow-x:auto}.md pre code{background:0 0;padding:0;font-size:13px;line-height:1.55}.md table{border-collapse:collapse;width:100%;margin:.7em 0;font-size:.95em}.md td,.md th{border:.5px solid var(--dsw-border-l2);text-align:left;padding:5px 10px}.md th{background:var(--dsw-hover)}.md hr{border:none;border-top:.5px solid var(--dsw-border-l2);margin:1em 0}:root{--hl-comment:#828892;--hl-keyword:#a046c8;--hl-string:#1e825a;--hl-number:#be6414;--hl-func:#3764dc;--hl-type:#148296;--hl-attr:#8c5a1e;--hl-tag:#c8325a;--hl-regex:#c83c28;--hl-meta:#786e28;--hl-punct:var(--dsw-label-tertiary);--hl-variable:#1e64aa}body[data-ds-dark-theme],html[data-ds-dark-theme]{--hl-comment:#78808c;--hl-keyword:#c896ff;--hl-string:#96d2a5;--hl-number:#f0af6e;--hl-func:#8cb9ff;--hl-type:#64c8d2;--hl-attr:#dcc382;--hl-tag:#f08296;--hl-regex:#eb826e;--hl-meta:#c8be78;--hl-punct:var(--dsw-label-tertiary);--hl-variable:#96c8fa}.hljs{color:var(--dsw-label-secondary)}.hljs-comment,.hljs-quote{color:var(--hl-comment);font-style:italic}.hljs-doctag,.hljs-keyword,.hljs-literal,.hljs-section,.hljs-selector-tag,.hljs-strong{color:var(--hl-keyword)}.hljs-strong{font-weight:600}.hljs-addition,.hljs-bullet,.hljs-link,.hljs-string,.hljs-symbol{color:var(--hl-string)}.hljs-number,.hljs-selector-class,.hljs-selector-id,.hljs-selector-pseudo,.hljs-template-variable{color:var(--hl-number)}.hljs-code,.hljs-name,.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:var(--hl-func)}.hljs-built_in,.hljs-builtin-name,.hljs-class .hljs-title,.hljs-type{color:var(--hl-type)}.hljs-attr,.hljs-attribute,.hljs-property{color:var(--hl-attr)}.hljs-deletion,.hljs-selector-attr,.hljs-tag{color:var(--hl-tag)}.hljs-char.escape_,.hljs-regexp,.hljs-subst{color:var(--hl-regex)}.hljs-formula,.hljs-meta,.hljs-meta .hljs-keyword,.hljs-meta .hljs-string{color:var(--hl-meta)}.hljs-operator,.hljs-params,.hljs-variable,.hljs-variable.language_{color:var(--hl-variable)}.hljs-punctuation{color:var(--hl-punct)}.hljs-emphasis{font-style:italic}.hljs-addition,.hljs-deletion{background:0 0}.msg-user-bubble{background:var(--dsw-input-major);max-width:85%;font-size:var(--piweb-chat-font-size);border-radius:18px;padding:10px 16px;line-height:1.65;display:inline-block}.msg-actions{opacity:.45;transition:opacity var(--ds-duration-fast) var(--ds-ease-in-out);align-items:center;gap:6px;margin-top:6px;display:flex}.msg-actions:hover{opacity:1}.msg-clock{font-size:var(--dsh-content-font-size-secondary,13px);line-height:calc(24px + var(--dsh-content-font-delta,0px));color:var(--dsw-label-tertiary)}.msg-clock-start{padding-right:12px}:root{--pw-danger-soft:#ec13131f;--pw-shimmer-hi:#96beff}body[data-ds-dark-theme],html[data-ds-dark-theme]{--pw-danger-soft:#f25a5a29;--pw-shimmer-hi:#e1ecff}.pw-turn{padding:6px 0 2px}.pw-run,.pw-turn{flex-direction:column;gap:1px;display:flex}.pw-step{min-height:26px;animation:pw-in .16s var(--ds-ease-in-out,ease-out) both;align-items:flex-start;gap:10px;display:flex}@keyframes pw-in{0%{opacity:0;transform:translateY(2px)}to{opacity:1;transform:none}}.pw-icon{width:16px;height:26px;color:var(--dsw-label-caption);flex:none;justify-content:center;align-items:center;display:inline-flex}.pw-step[data-tone=live] .pw-icon{color:var(--dsw-accent);animation:dot-breathe 1.4s ease-in-out infinite}.pw-step[data-tone=fail] .pw-icon{color:var(--dsw-danger)}.pw-step[data-tone=warn] .pw-icon{color:var(--dsw-warn)}.pw-step[data-kind=retry] .pw-icon{animation:pw-rotate 1.2s linear infinite}@keyframes pw-rotate{to{transform:rotate(1turn)}}.pw-pi{font-family:var(--font-mono);color:var(--dsw-accent);font-size:15px;font-weight:600;line-height:1;transform:translateY(-1px)}.pw-body{flex:1;min-width:0}.pw-line{min-height:26px;line-height:26px;font-size:var(--piweb-chat-font-m);color:var(--dsw-label-tertiary);align-items:center;gap:6px;display:flex}button.pw-line{text-align:left;cursor:pointer;width:100%;font:inherit;font-size:var(--piweb-chat-font-m);background:0 0;border:0;padding:0}button.pw-line:hover{color:var(--dsw-label-secondary)}.pw-verb{flex:none}.pw-verb[data-failed=true]{color:var(--dsw-danger)}.pw-arg{font-family:var(--font-mono);font-size:var(--piweb-chat-font-s);color:var(--dsw-label-secondary);flex:0 auto}.pw-arg,.pw-gist{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.pw-gist{flex:1}.pw-meta{font-size:var(--piweb-chat-font-t);white-space:nowrap}.pw-line .pw-chev,.pw-meta{color:var(--dsw-label-caption);flex:none}.pw-line .pw-chev{transition:transform .12s var(--ds-ease-in-out,ease)}.pw-line[aria-expanded=true] .pw-chev{transform:rotate(90deg)}.pw-summary strong{color:var(--dsw-label-secondary);font-weight:600}.pw-run[data-open]>.pw-step:not(:first-child){margin-left:4px}.pw-shimmer{background:linear-gradient(90deg,var(--dsw-accent) 0,var(--pw-shimmer-hi) 35%,var(--dsw-accent) 70%);color:#0000;background-size:220% 100%;-webkit-background-clip:text;background-clip:text;flex:none;animation:pw-shimmer 2.2s linear infinite}@keyframes pw-shimmer{0%{background-position:120% 0}to{background-position:-100% 0}}.pw-tail{font-size:var(--piweb-chat-font-s);color:var(--dsw-label-caption);word-break:break-word;-webkit-line-clamp:2;-webkit-box-orient:vertical;margin:-4px 0 5px;line-height:1.5;display:-webkit-box;overflow:hidden}.pw-result{font-family:var(--font-mono);font-size:var(--piweb-chat-font-t);color:var(--dsw-label-tertiary);align-items:flex-start;gap:6px;margin:-4px 0 5px;line-height:1.55;display:flex}.pw-result[role=button]{cursor:pointer}.pw-rail{text-align:center;width:14px;color:var(--dsw-label-caption);-webkit-user-select:none;user-select:none;flex:none}.pw-lines{white-space:pre-wrap;word-break:break-word;flex:1;min-width:0}.pw-lines>div{text-overflow:ellipsis;white-space:pre;overflow:hidden}.pw-lines .pw-head{color:var(--dsw-label-tertiary)}.pw-lines .pw-more{color:var(--dsw-label-caption)}.pw-result[role=button]:hover .pw-more{text-decoration:underline}.pw-result[data-failed=true],.pw-result[data-failed=true] .pw-head,.pw-result[data-failed=true] .pw-rail{color:var(--dsw-danger)}.pw-diff{max-height:420px;margin:-2px 0 6px 20px;padding:6px 8px}.pw-diff,.pw-output{background:var(--dsw-markdown-code-block,var(--dsw-hover));border-radius:10px;overflow:auto}.pw-output{max-height:360px;font-family:var(--font-mono);font-size:var(--piweb-chat-font-t);color:var(--dsw-label-secondary);white-space:pre-wrap;word-break:break-word;margin:0 0 6px 20px;padding:8px 12px;line-height:1.55}.pw-output.pw-think-full{color:var(--dsw-label-tertiary);font-family:inherit}.pw-output .cmdline{color:var(--dsw-label-tertiary);border-bottom:.5px solid var(--dsw-border-l2);white-space:pre-wrap;word-break:break-all;margin-bottom:6px;padding-bottom:6px}.pw-output .cmdline:before{content:"$ ";color:var(--dsw-label-caption)}button.pw-more{font:inherit;font-family:var(--font-mono);font-size:var(--piweb-chat-font-t);color:var(--dsw-label-caption);cursor:pointer;text-align:left;background:0 0;border:0;margin-top:4px;padding:0;display:block}button.pw-more:hover{color:var(--dsw-label-secondary);text-decoration:underline}.pw-narr{font-size:var(--piweb-chat-font-l);color:var(--dsw-label-primary);margin:7px 0 3px;line-height:1.65}.pw-turn>.pw-narr:first-child{margin-top:1px}.pw-narr[data-error=true]{color:var(--dsw-danger)}.pw-narr .md p{margin:0}.pw-narr .md p+p{margin-top:.5em}.pw-narr .md pre{margin:.5em 0}.pw-warn-text{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--dsw-warn);flex:1;overflow:hidden}.pw-step[data-kind=abort] .pw-line,.pw-step[data-kind=context] .pw-line{color:var(--dsw-label-caption);font-size:var(--piweb-chat-font-s)}body[data-pebrel-theme],html[data-pebrel-theme]{--dsw-bg-base:#fff;--dsw-bg-elevated:#fff;--dsw-input-major:#fff;--dsw-bubble:#64748b0d;--dsw-hover:#64748b21;--dsw-active:#64748b33;--dsw-label-primary:#334155;--dsw-label-secondary:#64748b;--dsw-label-tertiary:#94a3b8;--dsw-label-caption:#a3adbb;--dsw-border-l1:#33415514;--dsw-border-l2:#3341551f;--dsw-border-l3:#33415529;--dsw-border-l4:#3341553d;--dsw-warn:#d97706;--dsw-success:#548c9e;--dsw-danger:#b23a48;--dsw-selector:#64748b14;--dsw-layer-2:#fff;--dsw-layer-3:#f5f5f6;--dsw-btn-elevated:#fff;--dsw-btn-floating-hover:#64748b21;--dsw-module-platform:#f5f5f6;--dsw-border-strong:#64748b;--dsw-glass-popover:#fff;--dsw-glass-input:#fff;--dsw-glass-modal:#fff;--dsw-mask:#0f172a38;--dsw-elevation-stroke:var(--dsw-border-l2)}body[data-pebrel-theme=pebrel],body[data-pebrel-theme=silver-steel],html[data-pebrel-theme=pebrel],html[data-pebrel-theme=silver-steel]{--dsw-sidebar-fill:#f3f4f6;--dsw-accent:#495057;--dsw-accent-soft:#4950571f}body[data-pebrel-theme=limestone-coal],html[data-pebrel-theme=limestone-coal]{--dsw-sidebar-fill:#f0efeb;--dsw-layer-3:#f0efeb;--dsw-module-platform:#f0efeb;--dsw-accent:#58554c;--dsw-accent-soft:#58554c1f}body[data-pebrel-theme=linen-moss],html[data-pebrel-theme=linen-moss]{--dsw-sidebar-fill:#f2f2ec;--dsw-layer-3:#f2f2ec;--dsw-module-platform:#f2f2ec;--dsw-accent:#5f635f;--dsw-accent-soft:#5f635f1f}body[data-pebrel-theme=nord-paper],html[data-pebrel-theme=nord-paper]{--dsw-bg-base:#fcfbf9;--dsw-bg-elevated:#f5f4f0;--dsw-sidebar-fill:#f5f4f0;--dsw-input-major:#fcfbf9;--dsw-bubble:#8c8a8014;--dsw-hover:#ebeae5;--dsw-active:#2b5a382e;--dsw-label-primary:#1a1a1a;--dsw-label-secondary:#474646;--dsw-label-tertiary:#8c8a80;--dsw-label-caption:#a8a49a;--dsw-border-l1:#eceae4;--dsw-border-l2:#e0dfd5;--dsw-border-l3:#d5d3c9;--dsw-border-l4:#c1beb5;--dsw-accent:#2b5a38;--dsw-accent-soft:#2b5a382e;--dsw-warn:#a85a20;--dsw-success:#2b5a38;--dsw-danger:#a33a3a;--dsw-selector:#ebeae5;--dsw-layer-2:#fcfbf9;--dsw-layer-3:#f5f4f0;--dsw-btn-elevated:#fcfbf9;--dsw-btn-floating-hover:#ebeae5;--dsw-module-platform:#f0efe9;--dsw-border-strong:#8c8a80;--dsw-glass-popover:#fcfbf9;--dsw-glass-input:#fcfbf9;--dsw-glass-modal:#f5f4f0;--dsw-mask:#1a1a1a30}body[data-pebrel-theme][data-ds-dark-theme],html[data-pebrel-theme][data-ds-dark-theme]{--dsw-label-primary:#e2e8f0;--dsw-label-secondary:#b9c3cf;--dsw-label-tertiary:#94a3b8;--dsw-label-caption:#64748b;--dsw-bubble:#ffffff0d;--dsw-hover:#cbd5e124;--dsw-active:#cbd5e136;--dsw-border-l1:#cbd5e114;--dsw-border-l2:#cbd5e11f;--dsw-border-l3:#cbd5e129;--dsw-border-l4:#cbd5e13d;--dsw-warn:#f59e0b;--dsw-success:#7db2c2;--dsw-danger:#c44a58;--dsw-selector:#cbd5e114;--dsw-border-strong:#94a3b8;--dsw-mask:#02061780;--dsw-elevation-stroke:var(--dsw-border-l2)}body[data-pebrel-theme=silver-steel][data-ds-dark-theme],html[data-pebrel-theme=silver-steel][data-ds-dark-theme]{--dsw-bg-base:#16181e;--dsw-bg-elevated:#1e2129;--dsw-sidebar-fill:#16181e;--dsw-input-major:#1a1c24;--dsw-layer-2:#1e2129;--dsw-layer-3:#343a48;--dsw-btn-elevated:#1e2129;--dsw-btn-floating-hover:#cbd5e124;--dsw-module-platform:#1e2129;--dsw-glass-popover:#1e2129;--dsw-glass-input:#1a1c24;--dsw-glass-modal:#1e2129;--dsw-accent:#94a3b8;--dsw-accent-soft:#94a3b826}body[data-pebrel-theme=limestone-coal][data-ds-dark-theme],html[data-pebrel-theme=limestone-coal][data-ds-dark-theme]{--dsw-bg-base:#161616;--dsw-bg-elevated:#1e1e1e;--dsw-sidebar-fill:#161616;--dsw-input-major:#171717;--dsw-layer-2:#1e1e1e;--dsw-layer-3:#383836;--dsw-btn-elevated:#1e1e1e;--dsw-btn-floating-hover:#cbd5e124;--dsw-module-platform:#1e1e1e;--dsw-glass-popover:#1e1e1e;--dsw-glass-input:#171717;--dsw-glass-modal:#1e1e1e;--dsw-accent:#d4d4d4;--dsw-accent-soft:#d4d4d424;--dsw-warn:#ceb27e}body[data-pebrel-theme=linen-moss][data-ds-dark-theme],html[data-pebrel-theme=linen-moss][data-ds-dark-theme]{--dsw-bg-base:#191c19;--dsw-bg-elevated:#212521;--dsw-sidebar-fill:#191c19;--dsw-input-major:#1e211e;--dsw-layer-2:#212521;--dsw-layer-3:#363d36;--dsw-btn-elevated:#212521;--dsw-btn-floating-hover:#cbd5e124;--dsw-module-platform:#212521;--dsw-glass-popover:#212521;--dsw-glass-input:#1e211e;--dsw-glass-modal:#212521;--dsw-accent:#a3b3a3;--dsw-accent-soft:#a3b3a326}body[data-pebrel-theme=nord-paper][data-ds-dark-theme],html[data-pebrel-theme=nord-paper][data-ds-dark-theme]{--dsw-bg-base:#2e3440;--dsw-bg-elevated:#3b4252;--dsw-sidebar-fill:#2e3440;--dsw-input-major:#3b4252;--dsw-bubble:#3b4252;--dsw-hover:#ffffff0f;--dsw-active:#88c0d02e;--dsw-label-primary:#e5e9f0;--dsw-label-secondary:#c0c7d3;--dsw-label-tertiary:#7b8294;--dsw-label-caption:#6f7688;--dsw-border-l1:#ffffff0d;--dsw-border-l2:#ffffff0f;--dsw-border-l3:#434c5e;--dsw-border-l4:#4c566a;--dsw-accent:#88c0d0;--dsw-accent-soft:#88c0d02e;--dsw-warn:#ebcb8b;--dsw-success:#a3be8c;--dsw-danger:#bf616a;--dsw-selector:#ffffff0f;--dsw-layer-2:#3b4252;--dsw-layer-3:#434c5e;--dsw-btn-elevated:#3b4252;--dsw-btn-floating-hover:#434c5e;--dsw-module-platform:#3b4252;--dsw-border-strong:#7b8294;--dsw-glass-popover:#3b4252;--dsw-glass-input:#3b4252;--dsw-glass-modal:#3b4252;--dsw-mask:#0206175c}body[data-pebrel-theme=pebrel][data-ds-dark-theme],html[data-pebrel-theme=pebrel][data-ds-dark-theme]{--dsw-bg-base:#222630;--dsw-bg-elevated:#2b303b;--dsw-sidebar-fill:#222630;--dsw-input-major:#0f111a;--dsw-layer-2:#2b303b;--dsw-layer-3:#414858;--dsw-btn-elevated:#2b303b;--dsw-btn-floating-hover:#cbd5e124;--dsw-module-platform:#2b303b;--dsw-glass-popover:#2b303b;--dsw-glass-input:#0f111a;--dsw-glass-modal:#2b303b;--dsw-accent:#52a8ff;--dsw-accent-soft:#52a8ff26}@keyframes piweb-spin-kf{to{transform:rotate(1turn)}}.piweb-spin{animation:piweb-spin-kf .8s linear infinite}@keyframes piweb-pulse-kf{0%,to{opacity:1}50%{opacity:.3}}.piweb-pulse{animation:piweb-pulse-kf 1.1s ease-in-out infinite}@keyframes stream-cursor-kf{0%,49%{opacity:1}50%,to{opacity:0}}.stream-cursor{vertical-align:-.15em;background:var(--dsw-accent);width:2px;height:1em;margin-left:2px;animation:stream-cursor-kf 1s step-end infinite;display:inline-block}.pw-seg{background:var(--dsw-hover);border-radius:8px;flex:none;gap:1px;padding:2px;display:inline-flex}.pw-seg>button{height:20px;color:var(--dsw-label-tertiary);white-space:nowrap;transition:background var(--ds-duration-fast) var(--ds-ease-in-out),color var(--ds-duration-fast) var(--ds-ease-in-out);border-radius:6px;padding:0 8px;font-size:11.5px;line-height:20px}.pw-seg>button[data-active=true]{background:var(--dsw-bg-elevated);color:var(--dsw-label-primary);box-shadow:0 0 0 .5px var(--dsw-border-l2),0 1px 2px #0000000f}.pw-chip{border:.5px solid var(--dsw-border-l3);height:20px;color:var(--dsw-label-tertiary);white-space:nowrap;transition:background var(--ds-duration-fast) var(--ds-ease-in-out),color var(--ds-duration-fast) var(--ds-ease-in-out),border-color var(--ds-duration-fast) var(--ds-ease-in-out);border-radius:999px;flex:none;padding:0 8px;font-size:11px;line-height:19px}.pw-chip:hover{background:var(--dsw-hover);color:var(--dsw-label-primary)}.pw-chip[data-active=true]{background:var(--dsw-accent-soft);color:var(--dsw-accent);border-color:#0000}.pw-chip:disabled{opacity:.4;pointer-events:none}.pw-project{background:var(--dsw-sidebar-fill);border-right:.5px solid var(--dsw-border-l2)}.pw-project input,.pw-viewer input{color:var(--dsw-label-primary);outline:none}.pw-tree{-webkit-user-select:none;user-select:none;font-size:12.5px;line-height:22px}.pw-tree-row{cursor:pointer;white-space:nowrap;height:22px;color:var(--dsw-label-primary);transition:background var(--ds-duration-fast) var(--ds-ease-in-out);border-radius:6px;align-items:center;gap:4px;padding-right:8px;display:flex;position:absolute;left:0;right:0}.pw-tree-row:hover{background:var(--dsw-hover)}.pw-tree-row[data-focused],.pw-tree-row[data-selected]{background:var(--dsw-active)}.pw-tree:focus-visible .pw-tree-row[data-focused]{box-shadow:inset 0 0 0 1px var(--dsw-accent)}.pw-tree-chevron,.pw-tree-row[data-lazy]>.pw-tree-name{color:var(--dsw-label-caption)}.pw-tree-chevron{width:12px;flex:none;justify-content:center;align-items:center;display:inline-flex}.pw-tree-icon{object-fit:contain;flex:none;width:16px;height:16px}.pw-tree-row>.pw-tree-icon{filter:saturate(.78);transition:filter var(--ds-duration-fast) var(--ds-ease-in-out),opacity var(--ds-duration-fast) var(--ds-ease-in-out)}.pw-tree-row>.pw-tree-icon[data-kind=dir]{opacity:.88;transform:translateY(-.25px)scale(.87)}.pw-tree-row>.pw-tree-icon[data-kind=file]{opacity:.93;transform:scale(.91)}.pw-tree-row:hover>.pw-tree-icon{filter:saturate(.95);opacity:1}.pw-viewer .pw-tree-icon{filter:saturate(.82);opacity:.92;transform:scale(.9)}.pw-tree-name{text-overflow:ellipsis;flex:1;min-width:0;overflow:hidden}.pw-tree-from{color:var(--dsw-label-caption);margin-left:6px;font-size:11px}.pw-tree-stat{font-variant-numeric:tabular-nums;color:var(--dsw-label-caption);flex:none;gap:4px;font-size:10.5px;display:inline-flex}.pw-tree-badge{text-align:center;font-variant-numeric:tabular-nums;border:.5px solid;border-radius:4px;flex:none;min-width:14px;height:14px;padding:0 3px;font-size:10px;font-weight:600;line-height:13px}.pw-tree-actions{flex:none;align-items:center;gap:1px;display:none}.pw-tree-row:hover .pw-tree-actions{display:inline-flex}.pw-tree-row:hover .pw-tree-stat{display:none}.pw-tree-pulse{background:var(--dsw-accent);border-radius:999px;flex:none;width:6px;height:6px;animation:dot-breathe 1s ease-in-out infinite}.pw-tree-row[data-pending]>.pw-tree-name{color:var(--dsw-accent)}@keyframes pw-tree-in{0%{opacity:0;transform:translate(-6px)}to{opacity:1;transform:none}}@keyframes pw-tree-glow-add{0%{background:#22c55e47}to{background:0 0}}@keyframes pw-tree-glow-mod{0%{background:#f59e0b42}to{background:0 0}}@keyframes pw-tree-glow-del{0%{background:#ec13133d}to{background:0 0}}.pw-tree-row[data-fresh]{animation:pw-tree-in .25s var(--ds-ease-in-out) both,pw-tree-glow-mod 2.2s ease-out both}.pw-tree-row[data-fresh=A]{animation-name:pw-tree-in,pw-tree-glow-add}.pw-tree-row[data-fresh=D]{animation-name:pw-tree-in,pw-tree-glow-del}.pw-timeline{flex:none}.pw-viewer{width:80vw;height:80vh;box-shadow:var(--dsw-elevation-prominent);animation:pw-in .16s var(--ds-ease-in-out) both;transition:width var(--ds-duration-normal) var(--ds-ease-in-out),height var(--ds-duration-normal) var(--ds-ease-in-out),border-radius var(--ds-duration-normal) var(--ds-ease-in-out);border-radius:20px}.pw-viewer[data-fullscreen]{border-radius:0;width:100vw;height:100vh}.pw-viewer-tabs{flex:none;height:40px;padding:0 8px 0 10px}.pw-viewer-tabs,.pw-viewer-tabstrip{align-items:center;gap:2px;display:flex}.pw-viewer-tabstrip{scrollbar-width:none;min-width:0;overflow-x:auto}.pw-viewer-tabstrip::-webkit-scrollbar{display:none}.pw-viewer-tab{max-width:200px;height:28px;color:var(--dsw-label-tertiary);transition:background var(--ds-duration-fast) var(--ds-ease-in-out),color var(--ds-duration-fast) var(--ds-ease-in-out);border-radius:8px;flex:none;align-items:center;gap:6px;padding:0 8px;font-size:12.5px;display:inline-flex}.pw-viewer-tab:hover{background:var(--dsw-hover);color:var(--dsw-label-primary)}.pw-viewer-tab[data-active=true]{background:var(--dsw-active);color:var(--dsw-label-primary)}.pw-viewer-tab-main{min-width:0;color:inherit;align-items:center;gap:6px;display:inline-flex}.pw-viewer-tab-close{width:16px;height:16px;color:var(--dsw-label-caption);opacity:0;transition:opacity var(--ds-duration-fast) var(--ds-ease-in-out);border-radius:4px;justify-content:center;align-items:center;margin-right:-4px;display:inline-flex}.pw-viewer-tab-close:focus-visible,.pw-viewer-tab:hover .pw-viewer-tab-close,.pw-viewer-tab[data-active=true] .pw-viewer-tab-close{opacity:1}.pw-viewer-tab-close:hover{background:var(--dsw-hover);color:var(--dsw-label-primary)}.pw-viewer-toolbar{flex:none;gap:6px;height:36px;padding:0 12px}.pw-crumb,.pw-viewer-toolbar{align-items:center;display:flex}.pw-crumb{color:var(--dsw-label-tertiary);white-space:nowrap;flex-wrap:nowrap;font-size:12.5px;overflow:hidden}.pw-crumb button:hover{color:var(--dsw-accent)}.pw-crumb-sep{color:var(--dsw-label-caption);margin:0 4px}.pw-viewer-notice{color:var(--dsw-label-secondary);background:var(--dsw-hover);border-bottom:.5px solid var(--dsw-border-l1);flex:none;padding:4px 14px;font-size:11.5px}.pw-viewer-notice[data-kind=deleted]{color:var(--dsw-danger);background:#ec131314}.pw-viewer-notice[data-kind=warn]{color:var(--dsw-warn)}.pw-viewer .md pre{overflow-x:auto}.pw-quick{z-index:5;background:#00000014;justify-content:center;align-items:flex-start;padding-top:56px;display:flex;position:absolute;inset:0}.pw-quick-box{border-radius:14px;width:min(560px,90%);padding:4px}.pw-quick-list{max-height:50vh;padding:2px;overflow-y:auto}.pw-quick-item{width:100%;height:30px;color:var(--dsw-label-primary);text-align:left;border-radius:8px;align-items:center;gap:8px;padding:0 8px;font-size:12.5px;display:flex}.pw-quick-item[data-active=true]{background:var(--dsw-active)}.pw-fdiff{font-family:var(--font-mono);font-size:12px;line-height:20px}.pw-fdiff-row{white-space:pre;height:20px;color:var(--dsw-label-secondary);align-items:stretch;display:flex;position:absolute;left:0;right:0}.pw-fdiff-row[data-kind=add]{box-shadow:inset 2px 0 0 var(--dsw-success);background:#22c55e24}.pw-fdiff-row[data-kind=del]{box-shadow:inset 2px 0 0 var(--dsw-danger);background:#ec13131f}.pw-fdiff-gutter{color:var(--dsw-label-caption);-webkit-user-select:none;user-select:none;font-variant-numeric:tabular-nums;background:inherit;flex:none;gap:1ch;padding:0 8px 0 10px;display:inline-flex;position:sticky;left:0}.pw-fdiff-gutter>span{display:inline-block}.pw-fdiff-sign{-webkit-user-select:none;user-select:none;width:1.5ch;color:var(--dsw-label-caption);flex:none}.pw-fdiff-row[data-kind=add] .pw-fdiff-sign{color:var(--dsw-success)}.pw-fdiff-row[data-kind=del] .pw-fdiff-sign{color:var(--dsw-danger)}.pw-fdiff-text{background:0 0;flex:1;padding-right:24px}.pw-fdiff-fold{height:20px;color:var(--dsw-label-caption);background:var(--dsw-hover);text-align:center;letter-spacing:.02em;font-size:11px;position:absolute;left:0;right:0}.pw-fdiff-fold:hover{color:var(--dsw-accent)}.pw-fdiff-ticks{pointer-events:none;width:8px;position:absolute;top:0;bottom:0;right:2px}.pw-fdiff-tick{pointer-events:auto;cursor:pointer;background:var(--dsw-success);opacity:.6;border-radius:1px;width:8px;height:3px;position:absolute;right:0}.pw-fdiff-tick[data-kind=del]{background:var(--dsw-danger)}.pw-fdiff-tick[data-current]{opacity:1;box-shadow:0 0 0 1px var(--dsw-label-tertiary)}@media (max-width:840px){.pw-viewer{border-radius:0;width:100vw;height:100vh}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(1turn)}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rexvane/piweb",
3
- "version": "0.3.10",
3
+ "version": "0.3.11",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Modern web UI for the pi coding agent, driven by the official pi SDK without modifying pi",
@@ -1014,7 +1014,7 @@ function isFinalAnswer(m: WebMessage): boolean {
1014
1014
  return hasText && !hasTool && sr !== "toolUse" && sr !== "pending" && sr !== "error" && sr !== "aborted";
1015
1015
  }
1016
1016
 
1017
- function TurnBlock({
1017
+ function TurnBlockView({
1018
1018
  turn,
1019
1019
  messages,
1020
1020
  tools,
@@ -1195,6 +1195,48 @@ export function SessionStatsBar({
1195
1195
  );
1196
1196
  }
1197
1197
 
1198
+ type TurnBlockProps = Parameters<typeof TurnBlockView>[0];
1199
+
1200
+ /**
1201
+ * 回合级跳过:流式期间只有正在变的那条消息身份会变(foldPiWebEvent 只替换被改动的消息),
1202
+ * 历史回合用到的数据全部保持身份,于是 200 条消息的会话里历史回合可以整体跳过重渲染。
1203
+ */
1204
+ function turnBlockPropsEqual(prev: TurnBlockProps, next: TurnBlockProps): boolean {
1205
+ if (
1206
+ prev.cwd !== next.cwd ||
1207
+ prev.isStreaming !== next.isStreaming ||
1208
+ prev.isLast !== next.isLast ||
1209
+ prev.lastAssistantIndex !== next.lastAssistantIndex ||
1210
+ prev.retryNotice !== next.retryNotice ||
1211
+ prev.streamStartedAt !== next.streamStartedAt ||
1212
+ prev.runningTool !== next.runningTool ||
1213
+ prev.outputTokens !== next.outputTokens ||
1214
+ prev.workingMessage !== next.workingMessage ||
1215
+ prev.contextFiles !== next.contextFiles ||
1216
+ prev.onFork !== next.onFork ||
1217
+ prev.onEditMessage !== next.onEditMessage ||
1218
+ prev.onInspectTool !== next.onInspectTool ||
1219
+ prev.onOpenFile !== next.onOpenFile
1220
+ ) return false;
1221
+ // buildTurns 每次重建回合对象:比较内容而不是引用
1222
+ if (prev.turn.key !== next.turn.key || prev.turn.userIndex !== next.turn.userIndex) return false;
1223
+ if (prev.turn.assistantIndexes.length !== next.turn.assistantIndexes.length) return false;
1224
+ if (!prev.turn.assistantIndexes.every((value, index) => value === next.turn.assistantIndexes[index])) return false;
1225
+ const indexes = [prev.turn.userIndex, ...prev.turn.assistantIndexes].filter((index) => index >= 0);
1226
+ if (!indexes.every((index) => prev.messages[index] === next.messages[index])) return false;
1227
+ // 只比较本回合引用到的工具条目:tools 外层容器每次工具事件都会换身份
1228
+ const toolIds: string[] = [];
1229
+ for (const index of indexes) {
1230
+ for (const part of prev.messages[index]?.content ?? []) {
1231
+ const call = part as { type?: string; id?: string };
1232
+ if (call.type === "toolCall" && call.id) toolIds.push(call.id);
1233
+ }
1234
+ }
1235
+ return toolIds.every((id) => prev.tools[id] === next.tools[id]);
1236
+ }
1237
+
1238
+ const TurnBlock = memo(TurnBlockView, turnBlockPropsEqual);
1239
+
1198
1240
  export function ChatWindow({
1199
1241
  messages,
1200
1242
  tools,
@@ -35,7 +35,8 @@ import { useI18n } from "@/i18n";
35
35
  import { applyPebrelTheme, loadPebrelTheme, loadThemeMode, type PebrelTheme, type ThemeMode } from "@/lib/theme";
36
36
  import { modelDraftFromConfig, serializeProviderDraft, validateModelDrafts, type ModelDraft } from "@/lib/model-draft";
37
37
  import { customApiOptions } from "@/lib/provider-display";
38
- import type { ProviderUsage, ProviderView } from "@/lib/models-service";
38
+ import { quotaWindowKind, supportsUsageProbe, type BalanceAmount, type BalanceDetailKey, type ProviderUsage, type QuotaWindow, type XaiUsage } from "@/lib/provider-usage";
39
+ import type { ProviderView } from "@/lib/models-service";
39
40
  import type { ToolPreset } from "@/lib/types";
40
41
 
41
42
  type Section = "general" | "models" | "tools" | "skills" | "plugins";
@@ -886,7 +887,7 @@ function ModelsSection() {
886
887
  }, [oauthProvider, load]);
887
888
 
888
889
  // xAI 订阅配额(x-ratelimit-* 响应头;后端 5 分钟缓存,force 跳过)
889
- const [xaiUsage, setXaiUsage] = useState<ProviderUsage | null>(null);
890
+ const [xaiUsage, setXaiUsage] = useState<XaiUsage | null>(null);
890
891
  const [usageBusy, setUsageBusy] = useState(false);
891
892
  const [usageError, setUsageError] = useState(false);
892
893
  const fetchUsage = useCallback(async (force: boolean) => {
@@ -904,6 +905,106 @@ function ModelsSection() {
904
905
  }
905
906
  }, []);
906
907
 
908
+ // 窗口制/余额制配额:各提供商走各自的只读端点(后端 5 分钟缓存,force 跳过)
909
+ const [providerUsages, setProviderUsages] = useState<Record<string, ProviderUsage | null>>({});
910
+ const [usageBusyId, setUsageBusyId] = useState<string | null>(null);
911
+ const [usageFailedIds, setUsageFailedIds] = useState<Record<string, boolean>>({});
912
+ const fetchProviderUsage = useCallback(async (providerId: string, force: boolean) => {
913
+ setUsageBusyId(providerId);
914
+ setUsageFailedIds((prev) => ({ ...prev, [providerId]: false }));
915
+ try {
916
+ const r = await fetch("/api/models", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action: "providerUsage", providerId, force }) });
917
+ const j = await r.json();
918
+ if (!r.ok || !j.success) throw new Error(j.error || "usage probe failed");
919
+ setProviderUsages((prev) => ({ ...prev, [providerId]: j.data.usage ?? null }));
920
+ } catch {
921
+ setUsageFailedIds((prev) => ({ ...prev, [providerId]: true }));
922
+ } finally {
923
+ setUsageBusyId((current) => (current === providerId ? null : current));
924
+ }
925
+ }, []);
926
+
927
+ // 这些端点都是只读探测、不消耗额度,登录后自动取一次;xAI 那条会发一次可能计费的请求,仍然只手动触发
928
+ const requestedUsage = useRef(new Set<string>());
929
+ useEffect(() => {
930
+ for (const p of providers) {
931
+ if (!p.authReady || !supportsUsageProbe(p.id) || p.id === "xai" || requestedUsage.current.has(p.id)) continue;
932
+ requestedUsage.current.add(p.id);
933
+ void fetchProviderUsage(p.id, false);
934
+ }
935
+ }, [providers, fetchProviderUsage]);
936
+
937
+ // 窗口时长 → 标签:7 天叫「周限」、30 天叫「月限」,更短的按小时/天如实标注
938
+ const quotaWindowLabel = (seconds: number): string => {
939
+ const kind = quotaWindowKind(seconds);
940
+ if (kind === "weekly") return t.usageWindowWeekly;
941
+ if (kind === "monthly") return t.usageWindowMonthly;
942
+ return (kind === "hours" ? t.usageWindowHours : t.usageWindowDays).replace("{n}", String(Math.round(seconds / (kind === "hours" ? 3600 : 86400))));
943
+ };
944
+ // 服务端不给周期时(Copilot 的额度类别、OpenCode 的 rolling)按名字标注
945
+ const quotaWindowName = (name: string): string => {
946
+ if (name === "premium") return t.usageWindowPremium;
947
+ if (name === "chat") return t.usageWindowChat;
948
+ if (name === "completions") return t.usageWindowCompletions;
949
+ if (name === "rolling") return t.usageWindowRolling;
950
+ return titleCase(name);
951
+ };
952
+ const quotaVariantLabel = (variant: string): string => {
953
+ if (variant === "opus") return t.usageVariantOpus;
954
+ if (variant === "code") return t.usageVariantCode;
955
+ return titleCase(variant);
956
+ };
957
+ const quotaWindowHeading = (w: QuotaWindow): string => {
958
+ if (w.limitWindowSeconds !== undefined) return `${quotaWindowLabel(w.limitWindowSeconds)}${w.variant ? ` (${quotaVariantLabel(w.variant)})` : ""}`;
959
+ return w.name ? quotaWindowName(w.name) : "";
960
+ };
961
+
962
+ // 余额明细项的标签由界面翻译,服务端只给语义 key
963
+ const balanceDetailLabel = (key: BalanceDetailKey): string => {
964
+ if (key === "granted") return t.usageBalanceGranted;
965
+ if (key === "toppedUp") return t.usageBalanceToppedUp;
966
+ if (key === "voucher") return t.usageBalanceVoucher;
967
+ if (key === "cash") return t.usageBalanceCash;
968
+ if (key === "used") return t.usageBalanceUsed;
969
+ return t.usageBalanceLimit;
970
+ };
971
+ const fmtAmount = (value: BalanceAmount): string => `${value.currency === "USD" ? "$" : value.currency === "CNY" ? "¥" : ""}${value.amount}${value.currency && value.currency !== "USD" && value.currency !== "CNY" ? ` ${value.currency}` : ""}`;
972
+
973
+ // 一个提供商的配额行:窗口制显示每个窗口的剩余与重置,余额制显示主余额与明细
974
+ const renderProviderUsage = (providerId: string) => {
975
+ const usage = providerUsages[providerId];
976
+ const busy = usageBusyId === providerId;
977
+ const failed = usageFailedIds[providerId];
978
+ return (
979
+ <>
980
+ <div className="flex items-center gap-2">
981
+ {!usage && <span>{failed ? t.usageProbeFailed : busy ? t.queryingQuota : providerId === "xai" ? t.usageXaiDisclosure : t.usageReadOnlyDisclosure}</span>}
982
+ {usage?.kind === "quota" && usage.planType && <span>{t.usagePlan} {titleCase(usage.planType)}</span>}
983
+ {usage?.kind === "balance" && usage.primary && (
984
+ <span>{usage.primaryKey === "used" ? t.usageBalanceUsed : t.usageQuotaRemaining} <span style={{ color: "var(--dsw-label-secondary)" }}>{fmtAmount(usage.primary)}</span></span>
985
+ )}
986
+ <button type="button" className="pw-chip" disabled={busy} title={providerId === "xai" ? t.usageXaiDisclosure : t.usageReadOnlyDisclosure} onClick={() => void fetchProviderUsage(providerId, true)}>
987
+ {busy ? t.usageProbing : usage ? t.usageRefresh : t.usageProbe}
988
+ </button>
989
+ </div>
990
+ {usage?.kind === "balance" && usage.details.length > 0 && (
991
+ <span>{usage.details.map((detail) => `${balanceDetailLabel(detail.key)} ${fmtAmount(detail)}`).join(" · ")}</span>
992
+ )}
993
+ {usage?.kind === "quota" && usage.windows.length === 0 && <span>{t.usageQuotaRemaining} —</span>}
994
+ {usage?.kind === "quota" && usage.windows.map((w, index) => (
995
+ <div key={`${w.limitWindowSeconds ?? w.name ?? "window"}-${w.variant ?? ""}-${index}`} className="flex items-center gap-2">
996
+ <span style={{ width: 84, flex: "none" }}>{quotaWindowHeading(w)}</span>
997
+ <span style={{ width: 92, flex: "none" }}>{t.usageQuotaRemaining} {w.remainingPercent}%</span>
998
+ <span>{t.usageReset} {fmtQuotaReset(w.resetAt)}</span>
999
+ <div className="h-1 overflow-hidden rounded-full" style={{ width: 120, flex: "none", background: "var(--dsw-border-l2)" }}>
1000
+ <div className="h-full rounded-full" style={{ width: `${w.remainingPercent}%`, background: quotaToneColor(w.remainingPercent) }} />
1001
+ </div>
1002
+ </div>
1003
+ ))}
1004
+ </>
1005
+ );
1006
+ };
1007
+
907
1008
  const notify = (ok: boolean, msg?: string) => {
908
1009
  setToast({ ok, msg: msg ?? (ok ? t.toastSaved : t.toastError) });
909
1010
  setTimeout(() => setToast(null), 2400);
@@ -1201,6 +1302,12 @@ function ModelsSection() {
1201
1302
  </div>
1202
1303
  )}
1203
1304
 
1305
+ {supportsUsageProbe(p.id) && p.id !== "xai" && p.authReady && (
1306
+ <div className="mt-1.5 flex flex-col gap-1.5 px-4" style={{ fontSize: 11.5, color: "var(--dsw-label-tertiary)" }}>
1307
+ {renderProviderUsage(p.id)}
1308
+ </div>
1309
+ )}
1310
+
1204
1311
  {editingThis && (
1205
1312
  <div className="mt-2 rounded-2xl p-4" style={{ border: "0.5px solid var(--dsw-border-l2)" }}>
1206
1313
  {isCustom ? (
@@ -1501,6 +1608,24 @@ function fmtQuotaTokens(value: number): string {
1501
1608
  return value.toLocaleString();
1502
1609
  }
1503
1610
 
1611
+ /** 套餐值来自服务端声明(如 "pro"/"plus"),首字母大写后直接跟在标签后显示。 */
1612
+ function titleCase(value: string): string {
1613
+ return value.length === 0 ? value : value[0].toUpperCase() + value.slice(1);
1614
+ }
1615
+
1616
+ function fmtQuotaReset(value: string | undefined): string {
1617
+ if (!value) return "—";
1618
+ const date = new Date(value);
1619
+ return Number.isNaN(date.getTime()) ? "—" : date.toLocaleString();
1620
+ }
1621
+
1622
+ /** 剩余越少越警示:<30% 红、≤60% 黄、其余常规。 */
1623
+ function quotaToneColor(percent: number): string {
1624
+ if (percent < 30) return "var(--dsw-danger)";
1625
+ if (percent <= 60) return "var(--dsw-warning)";
1626
+ return "var(--dsw-success)";
1627
+ }
1628
+
1504
1629
  function Field({ label, children }: { label: string; children: React.ReactNode }) {
1505
1630
  return (
1506
1631
  <div>
@@ -4,6 +4,7 @@
4
4
  * 中央状态:会话列表轮询、当前会话 SSE 订阅、事件折叠(快照→增量)、命令发送。
5
5
  */
6
6
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
7
+ import { createEventBatcher, type EventBatcher } from "@/lib/event-batcher";
7
8
  import type {
8
9
  GrowthStep,
9
10
  SessionSummary,
@@ -334,6 +335,8 @@ export function usePiWeb() {
334
335
  const [currentId, setCurrentId] = useState<string | null>(null);
335
336
  const [currentPath, setCurrentPath] = useState<string | null>(null);
336
337
  const [state, setState] = useState<PiWebState>(emptyState);
338
+ /** 当前 SSE 连接的增量批量器:按帧合并事件,避免每个 token 一次整树渲染 */
339
+ const batcherRef = useRef<EventBatcher<WebEvent> | null>(null);
337
340
  const [models, setModels] = useState<{ providers: any[]; models: any[] } | null>(null);
338
341
  const [addedWorkspaces, setAddedWorkspaces] = useState<string[]>([]);
339
342
  const [removedWorkspaces, setRemovedWorkspaces] = useState<string[]>([]);
@@ -637,6 +640,11 @@ export function usePiWeb() {
637
640
  if (esRef.current === null) connect();
638
641
  }, delay);
639
642
  };
643
+ // 流式事件按帧合并:token 速率再高也只每 16ms 渲染一次(见 event-batcher)
644
+ const batcher = createEventBatcher<WebEvent>({
645
+ flush: (events) => setState((s) => events.reduce((acc, evt) => foldPiWebEvent(acc, evt), s)),
646
+ });
647
+ batcherRef.current = batcher;
640
648
  es.onmessage = (e) => {
641
649
  let parsed: any;
642
650
  try {
@@ -646,6 +654,8 @@ export function usePiWeb() {
646
654
  return;
647
655
  }
648
656
  if (parsed.type === "snapshot" && parsed.snapshot) {
657
+ // 快照是权威状态:之前缓冲的增量已经过时,直接丢弃
658
+ batcher.cancel();
649
659
  retryDelayMs = 2000;
650
660
  const snap = parsed.snapshot as WebSnapshot;
651
661
  setSnapshotEpoch((epoch) => epoch + 1);
@@ -667,13 +677,15 @@ export function usePiWeb() {
667
677
  growth: { ...prev.growth, error: snap.growthError ?? null },
668
678
  }));
669
679
  } else {
670
- setState((s) => foldPiWebEvent(s, parsed as WebEvent));
680
+ batcher.push(parsed as WebEvent);
671
681
  }
672
682
  };
673
683
  };
674
684
  let retryTimer: ReturnType<typeof setTimeout> | undefined;
675
685
  connect();
676
686
  return () => {
687
+ batcherRef.current?.cancel();
688
+ batcherRef.current = null;
677
689
  if (retryTimer) clearTimeout(retryTimer);
678
690
  esRef.current?.close();
679
691
  esRef.current = null;
package/src/i18n.tsx CHANGED
@@ -210,6 +210,28 @@ const zh = {
210
210
  usageProbing: "检测中…",
211
211
  usageProbeDisclosure: "检测会向 xAI 发送一次可能计费的最小请求。",
212
212
  usageProbeFailed: "配额检测失败,未获得用量信息。",
213
+ usagePlan: "套餐",
214
+ usageQuotaRemaining: "额度剩余",
215
+ queryingQuota: "正在查询额度…",
216
+ usageReadOnlyDisclosure: "读取提供商的只读用量端点,不消耗额度。",
217
+ usageXaiDisclosure: "会向 xAI 发出一次 1 token 的请求以读取限流头,会计入用量。",
218
+ usageReset: "刷新时间",
219
+ usageWindowWeekly: "周限",
220
+ usageWindowMonthly: "月限",
221
+ usageWindowHours: "{n} 小时",
222
+ usageWindowDays: "{n} 天",
223
+ usageWindowRolling: "滚动窗口",
224
+ usageWindowChat: "对话",
225
+ usageWindowCompletions: "补全",
226
+ usageWindowPremium: "高级请求",
227
+ usageVariantOpus: "Opus",
228
+ usageVariantCode: "代码",
229
+ usageBalanceGranted: "赠金",
230
+ usageBalanceToppedUp: "充值",
231
+ usageBalanceVoucher: "代金券",
232
+ usageBalanceCash: "现金",
233
+ usageBalanceUsed: "已用",
234
+ usageBalanceLimit: "上限",
213
235
  oauthWait: "等待授权…",
214
236
  oauthCancel: "取消登录",
215
237
  noSessions: "暂无会话",
@@ -745,6 +767,28 @@ const en: Dict = {
745
767
  usageProbing: "Checking…",
746
768
  usageProbeDisclosure: "Checking sends one minimal request to xAI that may be billed.",
747
769
  usageProbeFailed: "Quota check failed; no usage data was returned.",
770
+ usagePlan: "Plan",
771
+ usageQuotaRemaining: "Remaining quota",
772
+ queryingQuota: "Querying quota…",
773
+ usageReadOnlyDisclosure: "Reads the provider's read-only usage endpoint; consumes no quota.",
774
+ usageXaiDisclosure: "Sends one 1-token request to xAI to read the rate-limit headers; counts against your usage.",
775
+ usageReset: "Resets",
776
+ usageWindowWeekly: "Weekly",
777
+ usageWindowMonthly: "Monthly",
778
+ usageWindowHours: "{n}h",
779
+ usageWindowDays: "{n}d",
780
+ usageWindowRolling: "Rolling",
781
+ usageWindowChat: "Chat",
782
+ usageWindowCompletions: "Completions",
783
+ usageWindowPremium: "Premium requests",
784
+ usageVariantOpus: "Opus",
785
+ usageVariantCode: "Code",
786
+ usageBalanceGranted: "Granted",
787
+ usageBalanceToppedUp: "Topped up",
788
+ usageBalanceVoucher: "Voucher",
789
+ usageBalanceCash: "Cash",
790
+ usageBalanceUsed: "Used",
791
+ usageBalanceLimit: "Limit",
748
792
  oauthWait: "Waiting for authorization…",
749
793
  oauthCancel: "Cancel login",
750
794
  noSessions: "No sessions yet",
@@ -30,6 +30,7 @@ import { createExtensionUiBridge, type ExtensionUiBridge } from "./extension-ui"
30
30
  import { createGrowthTracker, GROWTH_TRACKER_VERSION, type GrowthTracker } from "./growth-tracker";
31
31
  import { userTurnsFromEntries } from "./growth-turns";
32
32
  import { TrajLedger, buildTrajectoryFromEntries, toTrajTokens } from "./trajectory";
33
+ import { startArchivedSessionRetention } from "./session-retention";
33
34
  import { sanitizeToolOutput } from "./text-sanitize";
34
35
  import { BoundaryError } from "./path-security";
35
36
  import { estimateTokensOf } from "./process-format";
@@ -969,6 +970,17 @@ export function reap(): void {
969
970
 
970
971
  setInterval(reap, 60_000).unref?.();
971
972
 
973
+ /** 归档会话的保留期清理:只删「已归档且长期无活动」的会话;正在使用的会话一律跳过 */
974
+ function isSessionInUse(sessionPath: string): boolean {
975
+ const key = process.platform === "win32" ? sessionPath.toLowerCase() : sessionPath;
976
+ for (const managed of sessions.keys()) {
977
+ if ((process.platform === "win32" ? managed.toLowerCase() : managed) === key) return true;
978
+ }
979
+ return false;
980
+ }
981
+
982
+ startArchivedSessionRetention({ isActive: isSessionInUse, log: (message) => console.log(message) });
983
+
972
984
  // ---------- 资源清单(技能 / 提示模板 / 扩展命令 / 信任 / 诊断) ----------
973
985
 
974
986
  type ResourceBundle = Pick<WebSnapshot, "skills" | "promptTemplates" | "extensionCommands" | "projectTrust" | "resourceDiagnostics">;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * 事件批量器:把高频流式事件合并成"每帧一次"的批量更新。
3
+ *
4
+ * 服务端按 token 推 delta,客户端原本每个事件一次 setState → 一次整树渲染。
5
+ * 合并后一次渲染里按顺序折叠整批事件,token 速率再高也只有每 16ms 一次渲染,
6
+ * 主线程不再被切碎(点击、滚动因此变顺)。
7
+ */
8
+ export interface EventBatcher<T> {
9
+ /** 入队;达到上限立刻冲刷,避免极端情况下无限堆积 */
10
+ push(event: T): void;
11
+ flush(): void;
12
+ /** 丢弃未处理的事件(例如快照到达:旧事件已经过时) */
13
+ cancel(): void;
14
+ size(): number;
15
+ }
16
+
17
+ export function createEventBatcher<T>({
18
+ flush,
19
+ schedule = (run) => {
20
+ const id = setTimeout(run, 16);
21
+ return () => clearTimeout(id);
22
+ },
23
+ limit = 400,
24
+ }: {
25
+ flush: (events: T[]) => void;
26
+ schedule?: (run: () => void) => () => void;
27
+ limit?: number;
28
+ }): EventBatcher<T> {
29
+ let pending: T[] = [];
30
+ let cancelScheduled: (() => void) | null = null;
31
+
32
+ const stopScheduled = () => {
33
+ if (!cancelScheduled) return;
34
+ cancelScheduled();
35
+ cancelScheduled = null;
36
+ };
37
+
38
+ const flushNow = () => {
39
+ stopScheduled();
40
+ if (!pending.length) return;
41
+ const batch = pending;
42
+ pending = [];
43
+ flush(batch);
44
+ };
45
+
46
+ return {
47
+ push(event) {
48
+ pending.push(event);
49
+ if (pending.length >= limit) {
50
+ flushNow();
51
+ return;
52
+ }
53
+ cancelScheduled ??= schedule(flushNow);
54
+ },
55
+ flush: flushNow,
56
+ cancel() {
57
+ stopScheduled();
58
+ pending = [];
59
+ },
60
+ size: () => pending.length,
61
+ };
62
+ }