@rexvane/piweb 0.3.24 → 0.3.26

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 (285) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +11 -11
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/prerender-manifest.json +5 -5
  5. package/.next/react-loadable-manifest.json +13 -20
  6. package/.next/required-server-files.js +2 -1
  7. package/.next/required-server-files.json +2 -1
  8. package/.next/routes-manifest.json +6 -8
  9. package/.next/server/app/_global-error/page.js +2 -2
  10. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  11. package/.next/server/app/_global-error.html +1 -1
  12. package/.next/server/app/_global-error.rsc +1 -1
  13. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  14. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  15. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  16. package/.next/server/app/_not-found/page.js +1 -1
  17. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/server/app/_not-found.html +1 -1
  19. package/.next/server/app/_not-found.rsc +2 -2
  20. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  21. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  22. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  23. package/.next/server/app/api/agent/[id]/events/route.js +2 -2
  24. package/.next/server/app/api/agent/[id]/events/route.js.nft.json +1 -1
  25. package/.next/server/app/api/agent/[id]/route.js +1 -1
  26. package/.next/server/app/api/agent/[id]/route.js.nft.json +1 -1
  27. package/.next/server/app/api/agent/[id]/tree/route.js +1 -1
  28. package/.next/server/app/api/agent/[id]/tree/route.js.nft.json +1 -1
  29. package/.next/server/app/api/agent/new/route.js +1 -1
  30. package/.next/server/app/api/agent/new/route.js.nft.json +1 -1
  31. package/.next/server/app/api/agent/resources/route.js +1 -0
  32. package/.next/server/app/api/agent/resources/route.js.nft.json +1 -0
  33. package/.next/server/app/api/agent/resources/route_client-reference-manifest.js +1 -0
  34. package/.next/server/app/api/config/route.js +1 -1
  35. package/.next/server/app/api/config/route.js.nft.json +1 -1
  36. package/.next/server/app/api/files/route.js +1 -1
  37. package/.next/server/app/api/files/route.js.nft.json +1 -1
  38. package/.next/server/app/api/fs/route.js +1 -1
  39. package/.next/server/app/api/fs/route.js.nft.json +1 -1
  40. package/.next/server/app/api/git/route.js +4 -4
  41. package/.next/server/app/api/git/route.js.nft.json +1 -1
  42. package/.next/server/app/api/github/route.js +4 -4
  43. package/.next/server/app/api/github/route.js.nft.json +1 -1
  44. package/.next/server/app/api/growth/route.js +1 -1
  45. package/.next/server/app/api/growth/route.js.nft.json +1 -1
  46. package/.next/server/app/api/health/route.js +1 -1
  47. package/.next/server/app/api/models/defaults/route.js +1 -1
  48. package/.next/server/app/api/models/defaults/route.js.nft.json +1 -1
  49. package/.next/server/app/api/models/route.js +1 -1
  50. package/.next/server/app/api/models/route.js.nft.json +1 -1
  51. package/.next/server/app/api/pi-settings/route.js +1 -1
  52. package/.next/server/app/api/pi-settings/route.js.nft.json +1 -1
  53. package/.next/server/app/api/plugins/route.js +1 -1
  54. package/.next/server/app/api/plugins/route.js.nft.json +1 -1
  55. package/.next/server/app/api/prompts/route.js +1 -1
  56. package/.next/server/app/api/prompts/route.js.nft.json +1 -1
  57. package/.next/server/app/api/remote/route.js +4 -3
  58. package/.next/server/app/api/security/route.js +1 -1
  59. package/.next/server/app/api/security/route.js.nft.json +1 -1
  60. package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
  61. package/.next/server/app/api/sessions/[id]/export/route.js.nft.json +1 -1
  62. package/.next/server/app/api/sessions/[id]/route.js +1 -1
  63. package/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
  64. package/.next/server/app/api/sessions/import/route.js +2 -2
  65. package/.next/server/app/api/sessions/import/route.js.nft.json +1 -1
  66. package/.next/server/app/api/sessions/route.js +1 -1
  67. package/.next/server/app/api/sessions/route.js.nft.json +1 -1
  68. package/.next/server/app/api/skills/route.js +2 -2
  69. package/.next/server/app/api/skills/route.js.nft.json +1 -1
  70. package/.next/server/app/api/update/route.js +2 -2
  71. package/.next/server/app/api/update/route.js.nft.json +1 -1
  72. package/.next/server/app/api/version/route.js +1 -1
  73. package/.next/server/app/api/version/route.js.nft.json +1 -1
  74. package/.next/server/app/api/web-auth/route.js +2 -2
  75. package/.next/server/app/api/workspaces/route.js +1 -1
  76. package/.next/server/app/api/workspaces/route.js.nft.json +1 -1
  77. package/.next/server/app/icon.svg/route.js +1 -1
  78. package/.next/server/app/index.html +1 -1
  79. package/.next/server/app/index.rsc +6 -7
  80. package/.next/server/app/index.segments/__PAGE__.segment.rsc +6 -7
  81. package/.next/server/app/index.segments/_full.segment.rsc +6 -7
  82. package/.next/server/app/index.segments/_tree.segment.rsc +2 -3
  83. package/.next/server/app/login/page.js +1 -1
  84. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  85. package/.next/server/app/login.html +1 -1
  86. package/.next/server/app/login.rsc +3 -3
  87. package/.next/server/app/login.segments/_full.segment.rsc +3 -3
  88. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  89. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +3 -3
  90. package/.next/server/app/page.js +1 -1
  91. package/.next/server/app/page.js.nft.json +1 -1
  92. package/.next/server/app/page_client-reference-manifest.js +1 -1
  93. package/.next/server/app-paths-manifest.json +11 -11
  94. package/.next/server/chunks/3924.js +3 -3
  95. package/.next/server/chunks/538.js +1 -1
  96. package/.next/server/chunks/620.js +1 -0
  97. package/.next/server/chunks/6229.js +1 -1
  98. package/.next/server/chunks/7496.js +1 -1
  99. package/.next/server/chunks/7662.js +2 -2
  100. package/.next/server/chunks/8686.js +1 -1
  101. package/.next/server/functions-config-manifest.json +1 -2
  102. package/.next/server/instrumentation.js.nft.json +1 -1
  103. package/.next/server/middleware-build-manifest.js +1 -1
  104. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  105. package/.next/server/pages/404.html +1 -1
  106. package/.next/server/pages/500.html +1 -1
  107. package/.next/server/preview-props.json +3 -3
  108. package/.next/server/server-reference-manifest.json +1 -1
  109. package/.next/static/QJCtqgDKzGS2BdKABd9ch/_buildManifest.js +1 -0
  110. package/.next/static/chunks/2577.86c8538fbf05a8b2.js +1 -0
  111. package/.next/static/chunks/2716.fa561519675a3ff1.js +1 -0
  112. package/.next/static/chunks/2906.8a0e09bb47605cf1.js +1 -0
  113. package/.next/static/chunks/4673.ea36e55005c0f82f.js +1 -0
  114. package/.next/static/chunks/4686-4dc565817a160351.js +1 -0
  115. package/.next/static/chunks/4778.0d2c5d75b791d352.js +1 -0
  116. package/.next/static/chunks/{618.949981da99be7182.js → 618.9a22656b863f242b.js} +2 -2
  117. package/.next/static/chunks/app/_global-error/page-f2d93969c0bcf406.js +1 -0
  118. package/.next/static/chunks/app/api/agent/[id]/events/route-f2d93969c0bcf406.js +1 -0
  119. package/.next/static/chunks/app/api/agent/[id]/route-f2d93969c0bcf406.js +1 -0
  120. package/.next/static/chunks/app/api/agent/[id]/tree/route-f2d93969c0bcf406.js +1 -0
  121. package/.next/static/chunks/app/api/agent/new/route-f2d93969c0bcf406.js +1 -0
  122. package/.next/static/chunks/app/api/agent/resources/route-f2d93969c0bcf406.js +1 -0
  123. package/.next/static/chunks/app/api/config/route-f2d93969c0bcf406.js +1 -0
  124. package/.next/static/chunks/app/api/files/route-f2d93969c0bcf406.js +1 -0
  125. package/.next/static/chunks/app/api/fs/route-f2d93969c0bcf406.js +1 -0
  126. package/.next/static/chunks/app/api/git/route-f2d93969c0bcf406.js +1 -0
  127. package/.next/static/chunks/app/api/github/route-f2d93969c0bcf406.js +1 -0
  128. package/.next/static/chunks/app/api/growth/route-f2d93969c0bcf406.js +1 -0
  129. package/.next/static/chunks/app/api/health/route-f2d93969c0bcf406.js +1 -0
  130. package/.next/static/chunks/app/api/models/defaults/route-f2d93969c0bcf406.js +1 -0
  131. package/.next/static/chunks/app/api/models/route-f2d93969c0bcf406.js +1 -0
  132. package/.next/static/chunks/app/api/pi-settings/route-f2d93969c0bcf406.js +1 -0
  133. package/.next/static/chunks/app/api/plugins/route-f2d93969c0bcf406.js +1 -0
  134. package/.next/static/chunks/app/api/prompts/route-f2d93969c0bcf406.js +1 -0
  135. package/.next/static/chunks/app/api/remote/route-f2d93969c0bcf406.js +1 -0
  136. package/.next/static/chunks/app/api/security/route-f2d93969c0bcf406.js +1 -0
  137. package/.next/static/chunks/app/api/sessions/[id]/export/route-f2d93969c0bcf406.js +1 -0
  138. package/.next/static/chunks/app/api/sessions/[id]/route-f2d93969c0bcf406.js +1 -0
  139. package/.next/static/chunks/app/api/sessions/import/route-f2d93969c0bcf406.js +1 -0
  140. package/.next/static/chunks/app/api/sessions/route-f2d93969c0bcf406.js +1 -0
  141. package/.next/static/chunks/app/api/skills/route-f2d93969c0bcf406.js +1 -0
  142. package/.next/static/chunks/app/api/update/route-f2d93969c0bcf406.js +1 -0
  143. package/.next/static/chunks/app/api/version/route-f2d93969c0bcf406.js +1 -0
  144. package/.next/static/chunks/app/api/web-auth/route-f2d93969c0bcf406.js +1 -0
  145. package/.next/static/chunks/app/api/workspaces/route-f2d93969c0bcf406.js +1 -0
  146. package/.next/static/chunks/app/page-71f2d3b3ed864b3d.js +1 -0
  147. package/.next/static/chunks/next/dist/client/components/builtin/app-error-f2d93969c0bcf406.js +1 -0
  148. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-f2d93969c0bcf406.js +1 -0
  149. package/.next/static/chunks/next/dist/client/components/builtin/not-found-f2d93969c0bcf406.js +1 -0
  150. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f2d93969c0bcf406.js +1 -0
  151. package/.next/static/chunks/webpack-ffc09b5fcc8aaa6d.js +1 -0
  152. package/.next/static/css/014eb1f21414e615.css +1 -0
  153. package/.next/static/css/56b129a13637fcfd.css +1 -0
  154. package/.next/static/css/7e3caf797e4cb19a.css +1 -0
  155. package/.next/types/app/api/agent/{[id]/side-chat → resources}/route.ts +3 -3
  156. package/.next/types/routes.d.ts +2 -2
  157. package/.next/types/validator.ts +9 -9
  158. package/README.md +1 -1
  159. package/next.config.ts +1 -1
  160. package/package.json +9 -9
  161. package/scripts/apply-global.mjs +87 -0
  162. package/scripts/launcher.mjs +21 -0
  163. package/scripts/testing/prepare-sdk-tools.mjs +19 -0
  164. package/src/app/api/agent/new/route.ts +2 -1
  165. package/src/app/api/agent/resources/route.ts +18 -0
  166. package/src/app/api/models/route.ts +22 -1
  167. package/src/app/api/plugins/route.ts +3 -2
  168. package/src/app/api/prompts/route.ts +3 -2
  169. package/src/app/api/remote/route.ts +22 -4
  170. package/src/app/api/sessions/[id]/export/route.ts +3 -1
  171. package/src/app/api/skills/route.ts +3 -2
  172. package/src/app/globals.css +1002 -1765
  173. package/src/components/AppShell.tsx +128 -93
  174. package/src/components/ChatInput.tsx +177 -98
  175. package/src/components/ChatWindow.tsx +189 -56
  176. package/src/components/ContextMeter.tsx +33 -9
  177. package/src/components/CustomSelect.module.css +142 -0
  178. package/src/components/CustomSelect.tsx +157 -0
  179. package/src/components/DrawerPanel.tsx +1 -1
  180. package/src/components/ExtensionUI.tsx +9 -3
  181. package/src/components/FileViewer.tsx +24 -21
  182. package/src/components/GitPanel.tsx +35 -13
  183. package/src/components/GrowthTree.tsx +6 -2
  184. package/src/components/ImageViewer.tsx +72 -0
  185. package/src/components/MarkdownBlock.tsx +67 -96
  186. package/src/components/ModelSelector.tsx +36 -39
  187. package/src/components/PiMark.tsx +76 -41
  188. package/src/components/PreviewImage.tsx +33 -0
  189. package/src/components/ProjectPanel.tsx +8 -5
  190. package/src/components/PromptPanel.tsx +85 -20
  191. package/src/components/ProviderSetupModal.module.css +12 -11
  192. package/src/components/ProviderSetupModal.tsx +107 -36
  193. package/src/components/RemoteSection.tsx +53 -2
  194. package/src/components/SessionImportSection.tsx +30 -15
  195. package/src/components/SessionSidebar.tsx +72 -62
  196. package/src/components/SettingsPanel.tsx +49 -151
  197. package/src/components/TrajectoryAnalyzer.module.css +26 -11
  198. package/src/components/TrajectoryView.tsx +11 -3
  199. package/src/components/chat/markdown-actions.tsx +23 -0
  200. package/src/components/composer/ComposerFiles.tsx +224 -0
  201. package/src/components/composer/ComposerImages.tsx +59 -127
  202. package/src/hooks/usePiWeb.ts +47 -2
  203. package/src/i18n.tsx +62 -80
  204. package/src/lib/agent-manager.ts +365 -33
  205. package/src/lib/chat/file-path.ts +54 -0
  206. package/src/lib/chat/skill-expansion.ts +51 -0
  207. package/src/lib/extension-ui.ts +25 -1
  208. package/src/lib/font.ts +37 -39
  209. package/src/lib/git-service.ts +4 -2
  210. package/src/lib/growth-turns.ts +3 -1
  211. package/src/lib/model-draft.ts +69 -2
  212. package/src/lib/models-service.ts +53 -3
  213. package/src/lib/pi.ts +2 -2
  214. package/src/lib/remote/assets.json +1 -1
  215. package/src/lib/remote/supervisor.ts +48 -5
  216. package/src/lib/security-service.ts +2 -1
  217. package/src/lib/session-reader.ts +25 -13
  218. package/src/lib/session-retention.ts +11 -8
  219. package/src/lib/skills-service.ts +2 -1
  220. package/src/lib/slash-command-token.ts +14 -0
  221. package/src/lib/trajectory.ts +111 -15
  222. package/src/lib/types.ts +28 -3
  223. package/src/lib/update-service.ts +29 -9
  224. package/src/lib/workspace-store.ts +36 -1
  225. package/.next/server/app/api/agent/[id]/side-chat/route.js +0 -5
  226. package/.next/server/app/api/agent/[id]/side-chat/route.js.nft.json +0 -1
  227. package/.next/server/app/api/agent/[id]/side-chat/route_client-reference-manifest.js +0 -1
  228. package/.next/server/chunks/3421.js +0 -1
  229. package/.next/static/chunks/2716.44fdf1b21454b961.js +0 -1
  230. package/.next/static/chunks/4673.e261707dce391be7.js +0 -1
  231. package/.next/static/chunks/4686-0cd87513f9ea3538.js +0 -1
  232. package/.next/static/chunks/4778.eddd3a5813f85824.js +0 -1
  233. package/.next/static/chunks/7411.5f79671208209b13.js +0 -1
  234. package/.next/static/chunks/8552.25c39ef4c995eba0.js +0 -1
  235. package/.next/static/chunks/app/_global-error/page-cbaaee0d7aacb85a.js +0 -1
  236. package/.next/static/chunks/app/api/agent/[id]/events/route-cbaaee0d7aacb85a.js +0 -1
  237. package/.next/static/chunks/app/api/agent/[id]/route-cbaaee0d7aacb85a.js +0 -1
  238. package/.next/static/chunks/app/api/agent/[id]/side-chat/route-cbaaee0d7aacb85a.js +0 -1
  239. package/.next/static/chunks/app/api/agent/[id]/tree/route-cbaaee0d7aacb85a.js +0 -1
  240. package/.next/static/chunks/app/api/agent/new/route-cbaaee0d7aacb85a.js +0 -1
  241. package/.next/static/chunks/app/api/config/route-cbaaee0d7aacb85a.js +0 -1
  242. package/.next/static/chunks/app/api/files/route-cbaaee0d7aacb85a.js +0 -1
  243. package/.next/static/chunks/app/api/fs/route-cbaaee0d7aacb85a.js +0 -1
  244. package/.next/static/chunks/app/api/git/route-cbaaee0d7aacb85a.js +0 -1
  245. package/.next/static/chunks/app/api/github/route-cbaaee0d7aacb85a.js +0 -1
  246. package/.next/static/chunks/app/api/growth/route-cbaaee0d7aacb85a.js +0 -1
  247. package/.next/static/chunks/app/api/health/route-cbaaee0d7aacb85a.js +0 -1
  248. package/.next/static/chunks/app/api/models/defaults/route-cbaaee0d7aacb85a.js +0 -1
  249. package/.next/static/chunks/app/api/models/route-cbaaee0d7aacb85a.js +0 -1
  250. package/.next/static/chunks/app/api/pi-settings/route-cbaaee0d7aacb85a.js +0 -1
  251. package/.next/static/chunks/app/api/plugins/route-cbaaee0d7aacb85a.js +0 -1
  252. package/.next/static/chunks/app/api/prompts/route-cbaaee0d7aacb85a.js +0 -1
  253. package/.next/static/chunks/app/api/remote/route-cbaaee0d7aacb85a.js +0 -1
  254. package/.next/static/chunks/app/api/security/route-cbaaee0d7aacb85a.js +0 -1
  255. package/.next/static/chunks/app/api/sessions/[id]/export/route-cbaaee0d7aacb85a.js +0 -1
  256. package/.next/static/chunks/app/api/sessions/[id]/route-cbaaee0d7aacb85a.js +0 -1
  257. package/.next/static/chunks/app/api/sessions/import/route-cbaaee0d7aacb85a.js +0 -1
  258. package/.next/static/chunks/app/api/sessions/route-cbaaee0d7aacb85a.js +0 -1
  259. package/.next/static/chunks/app/api/skills/route-cbaaee0d7aacb85a.js +0 -1
  260. package/.next/static/chunks/app/api/update/route-cbaaee0d7aacb85a.js +0 -1
  261. package/.next/static/chunks/app/api/version/route-cbaaee0d7aacb85a.js +0 -1
  262. package/.next/static/chunks/app/api/web-auth/route-cbaaee0d7aacb85a.js +0 -1
  263. package/.next/static/chunks/app/api/workspaces/route-cbaaee0d7aacb85a.js +0 -1
  264. package/.next/static/chunks/app/page-dc3cb41d91c7432e.js +0 -1
  265. package/.next/static/chunks/next/dist/client/components/builtin/app-error-cbaaee0d7aacb85a.js +0 -1
  266. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-cbaaee0d7aacb85a.js +0 -1
  267. package/.next/static/chunks/next/dist/client/components/builtin/not-found-cbaaee0d7aacb85a.js +0 -1
  268. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-cbaaee0d7aacb85a.js +0 -1
  269. package/.next/static/chunks/webpack-2ac3d76b81895f5b.js +0 -1
  270. package/.next/static/css/08815507594e8b8d.css +0 -1
  271. package/.next/static/css/9fc559c2c4ad1d0f.css +0 -1
  272. package/.next/static/css/c6e2227c36ec2fd6.css +0 -1
  273. package/.next/static/css/f15a853af8b5127f.css +0 -1
  274. package/.next/static/lvKCJmjFj8LuqSojRVLRF/_buildManifest.js +0 -1
  275. package/src/app/api/agent/[id]/side-chat/route.ts +0 -77
  276. package/src/components/side-chat/SideChatPanel.module.css +0 -131
  277. package/src/components/side-chat/SideChatPanel.tsx +0 -189
  278. package/src/components/side-chat/icons.tsx +0 -8
  279. package/src/components/side-chat/request-history.ts +0 -29
  280. package/src/components/side-chat/store.ts +0 -248
  281. package/src/lib/chat/stream-settings.ts +0 -192
  282. package/src/lib/side-chat/context.ts +0 -123
  283. package/src/lib/side-chat/service.ts +0 -191
  284. package/src/lib/side-chat/types.ts +0 -61
  285. /package/.next/static/{lvKCJmjFj8LuqSojRVLRF → QJCtqgDKzGS2BdKABd9ch}/_ssgManifest.js +0 -0
@@ -39,16 +39,6 @@ import { useI18n } from "@/i18n";
39
39
  import { fmtTokens } from "@/lib/format";
40
40
  import { applyPebrelTheme, loadPebrelTheme, loadThemeMode, type PebrelTheme, type ThemeMode } from "@/lib/theme";
41
41
  import { applyFontChoice, CUSTOM_PREFIX, FONT_PRESETS, loadFontChoice } from "@/lib/font";
42
- import {
43
- DEFAULT_STREAM_SETTINGS,
44
- getStreamSettings,
45
- setStreamSettings,
46
- STREAM_SPEED_MAX,
47
- STREAM_SPEED_MIN,
48
- STREAM_STYLES,
49
- type StreamSettings,
50
- type StreamStyle,
51
- } from "@/lib/chat/stream-settings";
52
42
  import {
53
43
  customModelGaps,
54
44
  fillModelFields,
@@ -134,32 +124,26 @@ export function SettingsPanel({
134
124
  boxShadow: "var(--dsw-elevation-prominent)",
135
125
  }}
136
126
  >
137
- {/* 左导航(dsh trigger:h42 r12,active 填充) */}
138
- <div className="settings-nav flex w-52 flex-none flex-col gap-0.5 py-4 pl-4 pr-3">
127
+ {/* 左导航(Iconsax .isx-menu-item:h42 r12,悬停/激活高对比反转 + 文字右移 + 右侧短竖条) */}
128
+ <div className="settings-nav flex w-52 flex-none flex-col gap-1 py-4 pl-4 pr-3">
139
129
  <div className="settings-title px-2 pb-3" style={{ fontSize: 16, fontWeight: 500 }}>
140
130
  {t.settings}
141
131
  </div>
142
132
  {navItems.map((n) => (
143
133
  <button
144
134
  key={n.id}
145
- className="flex h-[42px] items-center gap-2 rounded-xl px-3 text-left transition-colors"
135
+ data-active={section === n.id ? "true" : undefined}
136
+ className="isx-menu-item flex h-[42px] items-center gap-2.5 rounded-xl px-3 text-left"
146
137
  style={{
147
- paddingLeft: 8,
148
- paddingRight: 10,
138
+ paddingLeft: 10,
139
+ paddingRight: 14,
149
140
  fontSize: 14,
150
- background: section === n.id ? "var(--dsw-active)" : "transparent",
151
- color: section === n.id ? "var(--dsw-label-primary)" : "var(--dsw-label-secondary)",
152
- }}
153
- onMouseEnter={(e) => {
154
- if (section !== n.id) e.currentTarget.style.background = "var(--dsw-hover)";
155
- }}
156
- onMouseLeave={(e) => {
157
- if (section !== n.id) e.currentTarget.style.background = "transparent";
141
+ color: section === n.id ? "var(--dsw-bg-base)" : "var(--dsw-label-secondary)",
158
142
  }}
159
143
  onClick={() => setSection(n.id)}
160
144
  >
161
- {section === n.id ? <span style={{ color: "var(--dsw-accent)" }}>{n.icon}</span> : n.icon}
162
- {n.label}
145
+ <span className="isx-menu-icon" style={{ display: "inline-flex" }}>{n.icon}</span>
146
+ <span className="isx-menu-shift">{n.label}</span>
163
147
  </button>
164
148
  ))}
165
149
  </div>
@@ -203,15 +187,14 @@ function GeneralSection({
203
187
  const [themeMode, setThemeMode] = useState<ThemeMode>("system");
204
188
  const [fontSize, setFontSize] = useState(14);
205
189
  const [chatFontSize, setChatFontSize] = useState(14);
206
- const [stream, setStream] = useState<StreamSettings>(DEFAULT_STREAM_SETTINGS);
207
190
  const [enterBehavior, setEnterBehavior] = useState<EnterBehavior>("steer");
208
- const [piSettings, setPiSettings] = useState<{ compaction: { enabled: boolean; reserveTokens: number; keepRecentTokens: number }; retry: { enabled: boolean; maxRetries: number; baseDelayMs: number } } | null>(null);
191
+ const [piSettings, setPiSettings] = useState<{ compaction: { enabled: boolean; reserveTokens: number }; retry: { enabled: boolean; maxRetries: number } } | null>(null);
209
192
  const [versions, setVersions] = useState<{ piWeb: string; piEngine: string } | null>(null);
210
193
  const [webAuth, setWebAuth] = useState<{ enabled: boolean; authenticated: boolean } | null>(null);
211
194
  const [settingsLoading, setSettingsLoading] = useState(true);
212
195
  const [settingsSaving, setSettingsSaving] = useState(false);
213
196
  const [settingsError, setSettingsError] = useState<"load" | "save" | null>(null);
214
- const [numberDrafts, setNumberDrafts] = useState({ reserveTokens: "", keepRecentTokens: "", maxRetries: "", baseDelayMs: "" });
197
+ const [numberDrafts, setNumberDrafts] = useState({ reserveTokens: "", maxRetries: "" });
215
198
 
216
199
  const loadRemoteSettings = async () => {
217
200
  setSettingsLoading(true);
@@ -232,9 +215,7 @@ function GeneralSection({
232
215
  setPiSettings(ps.value);
233
216
  setNumberDrafts({
234
217
  reserveTokens: String(ps.value.compaction.reserveTokens),
235
- keepRecentTokens: String(ps.value.compaction.keepRecentTokens),
236
218
  maxRetries: String(ps.value.retry.maxRetries),
237
- baseDelayMs: String(ps.value.retry.baseDelayMs),
238
219
  });
239
220
  } else setPiSettings(null);
240
221
  if (health.status === "fulfilled" && health.value?.piWeb) setVersions({ piWeb: health.value.piWeb, piEngine: health.value.piEngine });
@@ -252,8 +233,6 @@ function GeneralSection({
252
233
  if (!Number.isNaN(fs)) setFontSize(fs);
253
234
  const chatFs = parseInt(localStorage.getItem("piweb.chatFontSize") ?? "14", 10);
254
235
  if (!Number.isNaN(chatFs)) setChatFontSize(chatFs);
255
- // 流式输出偏好与主题/字体同源(localStorage),同样等挂载后再读,避免水合不一致
256
- setStream(getStreamSettings());
257
236
  setEnterBehavior(getEnterBehavior());
258
237
  void loadRemoteSettings();
259
238
  }, []);
@@ -286,9 +265,6 @@ function GeneralSection({
286
265
  document.documentElement.style.setProperty("--piweb-chat-font-size", `${v}px`);
287
266
  };
288
267
 
289
- /** 流式输出:样式 / 速度 / 光标。改完即持久化,正在流出的回答下一帧就换(订阅在 MarkdownBlock 里) */
290
- const pickStream = (patch: Partial<StreamSettings>) => setStream(setStreamSettings(patch));
291
-
292
268
  const pickEnter = (v: EnterBehavior) => {
293
269
  setEnterBehavior(v);
294
270
  saveEnterBehavior(v);
@@ -316,7 +292,7 @@ function GeneralSection({
316
292
  }
317
293
  };
318
294
 
319
- const commitNumber = async (section: "compaction" | "retry", field: "reserveTokens" | "keepRecentTokens" | "maxRetries" | "baseDelayMs") => {
295
+ const commitNumber = async (section: "compaction" | "retry", field: "reserveTokens" | "maxRetries") => {
320
296
  if (!piSettings) return;
321
297
  const raw = numberDrafts[field].trim();
322
298
  const value = Number(raw);
@@ -327,7 +303,7 @@ function GeneralSection({
327
303
  if (value === (piSettings[section] as unknown as Record<string, number>)[field]) return;
328
304
  await patchPiSettings({ [section]: { [field]: value } });
329
305
  };
330
- const numberInput = (section: "compaction" | "retry", field: "reserveTokens" | "keepRecentTokens" | "maxRetries" | "baseDelayMs", title: string) => (
306
+ const numberInput = (section: "compaction" | "retry", field: "reserveTokens" | "maxRetries", title: string) => (
331
307
  <input
332
308
  type="number"
333
309
  min={0}
@@ -343,7 +319,7 @@ function GeneralSection({
343
319
  );
344
320
 
345
321
  const themes: { id: PebrelTheme; label: string; light: string; dark: string; accent: string }[] = [
346
- { id: "dsh", label: t.themeDsh, light: "#F9FAFB", dark: "#151517", accent: "#4176E6" },
322
+ { id: "dsh", label: t.themeDsh, light: "#FFFFFF", dark: "#040405", accent: "#02DEFC" },
347
323
  { id: "silver-steel", label: t.themeSilverSteel, light: "#F3F4F6", dark: "#1A1C24", accent: "#94A3B8" },
348
324
  { id: "limestone-coal", label: t.themeLimestoneCoal, light: "#F0EFEB", dark: "#171717", accent: "#CEB27E" },
349
325
  { id: "linen-moss", label: t.themeLinenMoss, light: "#F2F2EC", dark: "#1E211E", accent: "#A3B3A3" },
@@ -351,20 +327,6 @@ function GeneralSection({
351
327
  { id: "pebrel", label: t.themePebrel, light: "#F3F4F6", dark: "#0F111A", accent: "#52A8FF" },
352
328
  ];
353
329
 
354
- const streamStyleText: Record<StreamStyle, { label: string; desc: string }> = {
355
- smooth: { label: t.streamStyleSmooth, desc: t.streamStyleSmoothDesc },
356
- typewriter: { label: t.streamStyleTypewriter, desc: t.streamStyleTypewriterDesc },
357
- fade: { label: t.streamStyleFade, desc: t.streamStyleFadeDesc },
358
- instant: { label: t.streamStyleInstant, desc: t.streamStyleInstantDesc },
359
- };
360
- const streamSpeedText: Record<number, string> = {
361
- 1: t.streamSpeed1,
362
- 2: t.streamSpeed2,
363
- 3: t.streamSpeed3,
364
- 4: t.streamSpeed4,
365
- 5: t.streamSpeed5,
366
- };
367
-
368
330
  return (
369
331
  <div className="flex flex-col gap-6">
370
332
  {(settingsLoading || settingsError) && (
@@ -388,13 +350,12 @@ function GeneralSection({
388
350
  {themes.map((th) => (
389
351
  <button
390
352
  key={th.id}
391
- className="flex flex-col items-center justify-center gap-1 transition-colors"
353
+ className="isx-card flex flex-col items-center justify-center gap-1"
392
354
  style={{
393
355
  flex: "1 1 150px",
394
356
  padding: "20px 32px",
395
357
  borderRadius: 20,
396
- border: `0.5px solid ${theme === th.id ? "var(--dsw-border-strong)" : "var(--dsw-border-l4)"}`,
397
- background: theme === th.id ? "var(--dsw-module-platform)" : "transparent",
358
+ background: theme === th.id ? "var(--dsw-module-platform)" : "var(--dsw-bg-elevated)",
398
359
  color: "var(--dsw-label-primary)",
399
360
  fontSize: 14,
400
361
  lineHeight: "22px",
@@ -402,7 +363,7 @@ function GeneralSection({
402
363
  aria-pressed={theme === th.id}
403
364
  onClick={() => pickTheme(th.id)}
404
365
  >
405
- <span className="flex items-center overflow-hidden rounded-md" style={{ border: "0.5px solid var(--dsw-border-l3)", width: 42, height: 20 }}>
366
+ <span className="flex items-center overflow-hidden rounded-md" style={{ border: "1px solid var(--dsw-border-l3)", width: 42, height: 20 }}>
406
367
  <span style={{ background: th.light, flex: 1, height: "100%" }} />
407
368
  <span style={{ background: th.dark, flex: 1, height: "100%" }} />
408
369
  </span>
@@ -423,13 +384,12 @@ function GeneralSection({
423
384
  ] as { id: ThemeMode; label: string }[]).map((mode) => (
424
385
  <button
425
386
  key={mode.id}
426
- className="flex flex-col items-center justify-center gap-1 transition-colors"
387
+ className="isx-card flex flex-col items-center justify-center gap-1"
427
388
  style={{
428
389
  flex: "1 1 120px",
429
390
  padding: "16px 24px",
430
391
  borderRadius: 20,
431
- border: `0.5px solid ${themeMode === mode.id ? "var(--dsw-border-strong)" : "var(--dsw-border-l4)"}`,
432
- background: themeMode === mode.id ? "var(--dsw-module-platform)" : "transparent",
392
+ background: themeMode === mode.id ? "var(--dsw-module-platform)" : "var(--dsw-bg-elevated)",
433
393
  color: "var(--dsw-label-primary)",
434
394
  fontSize: 14,
435
395
  lineHeight: "22px",
@@ -449,83 +409,7 @@ function GeneralSection({
449
409
  <FontSizeStepper value={chatFontSize} onChange={pickChatFont} max={20} />
450
410
  </Row>
451
411
  <Row title={t.fontUiFamily} desc={t.fontUiFamilyDesc}>
452
- <FontFamilyPicker scope="ui" />
453
- </Row>
454
- <Row title={t.fontMonoFamily} desc={t.fontMonoFamilyDesc}>
455
- <FontFamilyPicker scope="mono" />
456
- </Row>
457
- <RowColumn title={t.streamStyleTitle} desc={t.streamStyleDesc}>
458
- <div className="flex w-full flex-wrap gap-2">
459
- {STREAM_STYLES.map((id) => {
460
- const active = stream.style === id;
461
- return (
462
- <button
463
- key={id}
464
- type="button"
465
- className="flex flex-col items-start gap-1 text-left transition-colors"
466
- style={{
467
- flex: "1 1 150px",
468
- padding: "12px 16px",
469
- borderRadius: 14,
470
- border: `0.5px solid ${active ? "var(--dsw-border-strong)" : "var(--dsw-border-l4)"}`,
471
- background: active ? "var(--dsw-module-platform)" : "transparent",
472
- color: "var(--dsw-label-primary)",
473
- lineHeight: "18px",
474
- }}
475
- aria-pressed={active}
476
- onClick={() => pickStream({ style: id })}
477
- >
478
- <span className="flex items-center gap-1.5" style={{ fontSize: 14 }}>
479
- {active ? <IconCheckOutline14 size={13} /> : null}
480
- {streamStyleText[id].label}
481
- </span>
482
- <span style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }}>{streamStyleText[id].desc}</span>
483
- </button>
484
- );
485
- })}
486
- </div>
487
- </RowColumn>
488
- <RowColumn title={t.streamSpeedTitle} desc={t.streamSpeedDesc}>
489
- <div className="flex w-full flex-wrap gap-2">
490
- {Array.from({ length: STREAM_SPEED_MAX - STREAM_SPEED_MIN + 1 }, (_, i) => STREAM_SPEED_MIN + i).map((level) => {
491
- const active = stream.speed === level;
492
- return (
493
- <button
494
- key={level}
495
- type="button"
496
- className="transition-colors disabled:opacity-50"
497
- // 即时样式不排队,速度对它没有意义:置灰,免得以为调了没反应
498
- disabled={stream.style === "instant"}
499
- style={{
500
- flex: "1 1 84px",
501
- padding: "10px 8px",
502
- borderRadius: 12,
503
- border: `0.5px solid ${active ? "var(--dsw-border-strong)" : "var(--dsw-border-l4)"}`,
504
- background: active ? "var(--dsw-module-platform)" : "transparent",
505
- color: active ? "var(--dsw-label-primary)" : "var(--dsw-label-secondary)",
506
- fontSize: 13,
507
- }}
508
- aria-pressed={active}
509
- onClick={() => pickStream({ speed: level })}
510
- >
511
- {streamSpeedText[level]}
512
- </button>
513
- );
514
- })}
515
- </div>
516
- </RowColumn>
517
- <Row title={t.streamCaretTitle} desc={t.streamCaretDesc}>
518
- <button
519
- type="button"
520
- className="relative h-5 w-9 flex-none rounded-full transition-colors"
521
- style={{ background: stream.caret ? "var(--dsw-accent)" : "var(--dsw-border-l3)" }}
522
- role="switch"
523
- aria-checked={stream.caret}
524
- aria-label={t.streamCaretTitle}
525
- onClick={() => pickStream({ caret: !stream.caret })}
526
- >
527
- <span className="absolute top-0.5 h-4 w-4 rounded-full bg-white transition-all" style={{ left: stream.caret ? 18 : 2 }} />
528
- </button>
412
+ <FontFamilyPicker />
529
413
  </Row>
530
414
  <Row title={t.enterBehavior} desc={t.enterBehaviorDesc}>
531
415
  <SelectOption
@@ -543,7 +427,6 @@ function GeneralSection({
543
427
  <span className="absolute top-0.5 h-4 w-4 rounded-full bg-white transition-all" style={{ left: piSettings?.compaction.enabled ? 18 : 2 }} />
544
428
  </button>
545
429
  {numberInput("compaction", "reserveTokens", t.reserveTokens)}
546
- {numberInput("compaction", "keepRecentTokens", t.keepRecentTokens)}
547
430
  </div>
548
431
  </Row>
549
432
  <Row title={t.autoRetry} desc={t.autoRetryDesc}>
@@ -552,7 +435,6 @@ function GeneralSection({
552
435
  <span className="absolute top-0.5 h-4 w-4 rounded-full bg-white transition-all" style={{ left: piSettings?.retry.enabled ? 18 : 2 }} />
553
436
  </button>
554
437
  {numberInput("retry", "maxRetries", t.maxRetries)}
555
- {numberInput("retry", "baseDelayMs", t.baseDelayMs)}
556
438
  </div>
557
439
  </Row>
558
440
  <Row title={t.versionPiweb} desc={t.versionPiwebDesc}>
@@ -662,7 +544,7 @@ function UpdateControl({ target }: { target: UpdateTarget }) {
662
544
  }
663
545
 
664
546
  /** 通用下选芯片:自定义深色圆角浮层,替代原生 select */
665
- function SelectOption({
547
+ export function SelectOption({
666
548
  value,
667
549
  options,
668
550
  onChange,
@@ -694,6 +576,18 @@ function SelectOption({
694
576
  if (disabled) setOpen(false);
695
577
  }, [disabled]);
696
578
  const current = options.find((o) => o.value === value);
579
+ // 下方放不下(按每项约 37px 估高,封顶 280)且上方更宽裕时向上翻转,并把高度限制在可用空间内。
580
+ // 以前只把 top 钳到窗口底部上方 40px,靠近底部时只露出第一项,其余落到屏幕外
581
+ const placement = (() => {
582
+ if (!rect || typeof window === "undefined") return null;
583
+ const margin = 8;
584
+ const wanted = Math.min(280, options.length * 37 + 12);
585
+ const below = window.innerHeight - rect.top - margin;
586
+ const above = rect.bottom - margin;
587
+ return below < wanted && above > below
588
+ ? { bottom: window.innerHeight - rect.bottom, maxHeight: Math.min(280, above) }
589
+ : { top: rect.top, maxHeight: Math.min(280, Math.max(below, 80)) };
590
+ })();
697
591
  return (
698
592
  <div className="relative">
699
593
  <button ref={anchorRef} className="select-chip" data-open={open} disabled={disabled} onClick={() => setOpen((o) => !o)}>
@@ -702,12 +596,13 @@ function SelectOption({
702
596
  <IconChevronDown14 size={14} />
703
597
  </span>
704
598
  </button>
705
- {open && !disabled && rect && createPortal(
599
+ {open && !disabled && rect && placement && createPortal(
706
600
  <div
707
601
  ref={popRef}
708
602
  className="popover fixed z-[130] p-1.5 shadow-xl"
709
- // 下方空间不足时向上翻转;左右都对齐 chip 左缘
710
- style={{ left: rect.left, top: Math.min(rect.top, window.innerHeight - 40), minWidth: width ?? 140, width: "max-content", maxHeight: 280, overflowY: "auto" }}
603
+ data-placement={"bottom" in placement ? "top" : "bottom"}
604
+ // 左右都对齐 chip 左缘
605
+ style={{ left: rect.left, ...placement, minWidth: width ?? 140, width: "max-content", overflowY: "auto" }}
711
606
  >
712
607
  {options.map((o) => {
713
608
  const selected = o.value === value;
@@ -813,8 +708,9 @@ function FontSizeStepper({ value, onChange, max }: { value: number; onChange: (v
813
708
  );
814
709
  }
815
710
 
816
- /** 字体族选择:预设下拉 + 「自定义」文本框。改动即生效并持久化。 */
817
- function FontFamilyPicker({ scope }: { scope: "ui" | "mono" }) {
711
+ /** 界面字体族选择:预设下拉 + 「自定义」文本框。改动即生效并持久化。
712
+ * 代码字体已固定为系统等宽、不再可配(见 lib/font.ts)。 */
713
+ function FontFamilyPicker() {
818
714
  const { t, lang } = useI18n();
819
715
  const [stored, setStored] = useState<string | null>(null);
820
716
  const [custom, setCustom] = useState("");
@@ -823,18 +719,18 @@ function FontFamilyPicker({ scope }: { scope: "ui" | "mono" }) {
823
719
  const [customMode, setCustomMode] = useState(false);
824
720
 
825
721
  useEffect(() => {
826
- const saved = loadFontChoice(scope);
722
+ const saved = loadFontChoice();
827
723
  const savedCustom = saved?.startsWith(CUSTOM_PREFIX) ? saved.slice(CUSTOM_PREFIX.length) : null;
828
724
  setStored(saved);
829
725
  setCustomMode(savedCustom !== null);
830
726
  setCustom(savedCustom ?? "");
831
- }, [scope]);
727
+ }, []);
832
728
 
833
729
  const applyCustom = (v: string) => {
834
730
  setCustom(v);
835
731
  const next = v.trim() ? CUSTOM_PREFIX + v.trim() : null;
836
732
  setStored(next);
837
- applyFontChoice(scope, next);
733
+ applyFontChoice(next);
838
734
  };
839
735
 
840
736
  const pick = (v: string) => {
@@ -846,10 +742,10 @@ function FontFamilyPicker({ scope }: { scope: "ui" | "mono" }) {
846
742
  }
847
743
  setCustomMode(false);
848
744
  setStored(v);
849
- applyFontChoice(scope, v);
745
+ applyFontChoice(v);
850
746
  };
851
747
 
852
- const selectValue = customMode ? CUSTOM_PREFIX : stored ?? FONT_PRESETS[scope][0].id;
748
+ const selectValue = customMode ? CUSTOM_PREFIX : stored ?? FONT_PRESETS[0].id;
853
749
 
854
750
  return (
855
751
  <div className="flex flex-col items-end gap-2">
@@ -857,7 +753,7 @@ function FontFamilyPicker({ scope }: { scope: "ui" | "mono" }) {
857
753
  value={selectValue}
858
754
  width={220}
859
755
  options={[
860
- ...FONT_PRESETS[scope].map((p) => ({
756
+ ...FONT_PRESETS.map((p) => ({
861
757
  value: p.id,
862
758
  label: lang === "zh" ? p.label.zh : p.label.en,
863
759
  })),
@@ -2628,7 +2524,9 @@ export function PluginsSection({ cwd }: { cwd: string }) {
2628
2524
  role="menuitem"
2629
2525
  className="flex w-full items-center gap-2 px-3 py-1.5 text-left text-xs transition-colors hover:bg-[var(--dsw-hover)] cursor-pointer disabled:opacity-50"
2630
2526
  style={{ color: "var(--dsw-label-secondary)" }}
2631
- disabled={busy}
2527
+ // 停用的包挪到了 PiWeb 私有的 disabledPackages,SDK 的 update 只认 packages,点了只会报「找不到包」
2528
+ disabled={busy || isDisabled}
2529
+ title={isDisabled ? t.updateNeedsEnable : undefined}
2632
2530
  onClick={() => {
2633
2531
  setMenuKey(null);
2634
2532
  void act(
@@ -59,23 +59,30 @@
59
59
  }
60
60
 
61
61
  .toolbarButton:hover,
62
- .toolbarButton[aria-pressed="true"] { color: var(--dsw-label-primary); background: var(--dsw-hover); }
62
+ .toolbarButton[aria-pressed="true"] {
63
+ color: var(--dsw-label-primary);
64
+ background: var(--dsw-hover);
65
+ transform: translateY(-0.5px);
66
+ }
63
67
 
64
68
  .searchBox {
65
69
  display: flex;
66
70
  width: min(230px, 30vw);
67
- height: 25px;
71
+ height: 26px;
68
72
  align-items: center;
69
73
  gap: 5px;
70
74
  margin-left: auto;
71
- padding: 0 7px;
72
- border: 0.5px solid var(--dsw-border-l3);
73
- border-radius: 5px;
74
- color: var(--dsw-label-caption);
75
+ padding: 0 8px;
76
+ border: 1.5px solid var(--dsw-border-l2);
77
+ border-radius: 12px;
78
+ color: var(--dsw-label-secondary);
75
79
  background: var(--dsw-layer-2);
80
+ transition: border-color 0.22s var(--isx-ease);
76
81
  }
77
82
 
78
- .searchBox:focus-within { border-color: var(--dsw-accent); }
83
+ .searchBox:focus-within {
84
+ border-color: var(--dsw-label-primary);
85
+ }
79
86
  .searchBox input { width: 100%; min-width: 0; font-size: 12px; }
80
87
 
81
88
  .timeline {
@@ -145,10 +152,17 @@
145
152
  border-bottom: 0.5px solid var(--dsw-border-l1);
146
153
  background: var(--dsw-bg-base);
147
154
  text-align: left;
155
+ transition: background 0.2s var(--isx-ease), transform 0.2s var(--isx-ease);
148
156
  }
149
157
 
150
- .row:hover { background: var(--dsw-hover); }
151
- .row[data-selected="true"] { background: var(--dsw-hover); box-shadow: inset 3px 0 0 var(--dsw-accent); }
158
+ .row:hover {
159
+ background: var(--dsw-hover);
160
+ transform: translateX(2px);
161
+ }
162
+ .row[data-selected="true"] {
163
+ background: var(--dsw-hover);
164
+ box-shadow: inset 3px 0 0 var(--dsw-label-primary);
165
+ }
152
166
  .row[data-environment="true"] { background: var(--dsw-bg-elevated); }
153
167
  .row[data-environment="true"]:hover,
154
168
  .row[data-environment="true"][data-selected="true"] { background: var(--dsw-hover); }
@@ -161,9 +175,10 @@
161
175
  max-width: 100%;
162
176
  height: 22px;
163
177
  align-items: center;
164
- padding: 0 6px;
178
+ padding: 0 8px;
165
179
  overflow: hidden;
166
- border-radius: 5px;
180
+ border-radius: 999px;
181
+ border: 1px solid var(--dsw-border-l1);
167
182
  color: var(--dsw-label-secondary);
168
183
  background: var(--dsw-module-platform);
169
184
  font-family: var(--font-mono);
@@ -236,12 +236,20 @@ export function TrajectoryView({ entries, selected, onSelect }: {
236
236
  );
237
237
  }
238
238
 
239
- export function TrajInspector({ entry, onClose }: { entry: TrajEntry; onClose: () => void }) {
239
+ /**
240
+ * context 条目的全文回退:轨迹里的 detail 只有头部(快照不再与 contextResources 双份带全文),
241
+ * inspector 展开时按路径回快照资源表里取。
242
+ */
243
+ type ContextLookup = (path: string) => string | undefined;
244
+
245
+ export function TrajInspector({ entry, onClose, contextLookup }: { entry: TrajEntry; onClose: () => void; contextLookup?: ContextLookup }) {
240
246
  const { t } = useI18n();
241
247
  const [tab, setTab] = useState<DetailTab>("summary");
242
248
  const kind = entry.toolName || KIND_NAME[entry.kind];
243
249
  const position = entry.kind === "system" ? t.trajSystem : entry.turn ? `${t.trajTurn} ${entry.turn}` : t.trajSession;
244
- const preview = entry.detail || entry.preview || entry.thinking || "";
250
+ const fullText = entry.kind === "context" && entry.title ? contextLookup?.(entry.title) : undefined;
251
+ const body = fullText ?? entry.detail;
252
+ const preview = body || entry.preview || entry.thinking || "";
245
253
  const tabs: Array<{ id: DetailTab; label: string }> = [
246
254
  { id: "summary", label: t.trajSummary },
247
255
  { id: "preview", label: t.trajPreview },
@@ -274,7 +282,7 @@ export function TrajInspector({ entry, onClose }: { entry: TrajEntry; onClose: (
274
282
  </dl>
275
283
  {preview && <InspectorPayload title={t.trajPreview} value={preview} />}
276
284
  </>}
277
- {tab === "preview" && <InspectorPayload title={entry.thinking ? t.trajThink : t.trajPreview} value={[entry.thinking, entry.detail || entry.preview].filter(Boolean).join("\n\n")} empty={t.trajNoContent} />}
285
+ {tab === "preview" && <InspectorPayload title={entry.thinking ? t.trajThink : t.trajPreview} value={[entry.thinking, body || entry.preview].filter(Boolean).join("\n\n")} empty={t.trajNoContent} />}
278
286
  {tab === "raw" && <InspectorPayload title="JSON" value={JSON.stringify(entry, null, 2)} mono />}
279
287
  {tab === "source" && <InspectorPayload title={t.trajSource} value={`${sourceOf(entry)}\n\nseq: ${entry.seq}\ntimestamp: ${new Date(entry.ts).toISOString()}`} mono />}
280
288
  </div>
@@ -0,0 +1,23 @@
1
+ "use client";
2
+
3
+ /**
4
+ * 正文(Markdown)里可交互元素的接线。
5
+ *
6
+ * 用 context 而不是逐层传 props:MarkdownBlock 在对话正文、轨迹详情、提示词面板等处复用,
7
+ * 中间隔着好几层,逐层透传会把签名污染到与之无关的组件上。
8
+ * 没有 Provider 时(例如文件查看器里渲染 Markdown)这些交互自然不生效,正文照常显示。
9
+ */
10
+ import { createContext, useContext } from "react";
11
+
12
+ export interface MarkdownActions {
13
+ /** 用文件查看器打开项目内路径;没有工作区时不给(此时路径只作展示) */
14
+ openFile?: (path: string) => void;
15
+ }
16
+
17
+ const MarkdownActionsContext = createContext<MarkdownActions>({});
18
+
19
+ export const MarkdownActionsProvider = MarkdownActionsContext.Provider;
20
+
21
+ export function useMarkdownActions(): MarkdownActions {
22
+ return useContext(MarkdownActionsContext);
23
+ }