@rexvane/piweb 0.3.18 → 0.3.20

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 (261) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +8 -6
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/prerender-manifest.json +4 -4
  5. package/.next/react-loadable-manifest.json +23 -9
  6. package/.next/required-server-files.js +2 -1
  7. package/.next/required-server-files.json +2 -1
  8. package/.next/routes-manifest.json +12 -0
  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 +3 -3
  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/config/route.js +1 -1
  32. package/.next/server/app/api/config/route.js.nft.json +1 -1
  33. package/.next/server/app/api/files/route.js +1 -1
  34. package/.next/server/app/api/files/route.js.nft.json +1 -1
  35. package/.next/server/app/api/fs/route.js +1 -0
  36. package/.next/server/app/api/fs/route.js.nft.json +1 -0
  37. package/.next/server/app/api/fs/route_client-reference-manifest.js +1 -0
  38. package/.next/server/app/api/git/route.js +2 -2
  39. package/.next/server/app/api/git/route.js.nft.json +1 -1
  40. package/.next/server/app/api/growth/route.js +1 -1
  41. package/.next/server/app/api/growth/route.js.nft.json +1 -1
  42. package/.next/server/app/api/health/route.js +1 -1
  43. package/.next/server/app/api/models/route.js +1 -1
  44. package/.next/server/app/api/models/route.js.nft.json +1 -1
  45. package/.next/server/app/api/pi-settings/route.js +1 -1
  46. package/.next/server/app/api/pi-settings/route.js.nft.json +1 -1
  47. package/.next/server/app/api/plugins/route.js +1 -1
  48. package/.next/server/app/api/plugins/route.js.nft.json +1 -1
  49. package/.next/server/app/api/prompts/route.js +1 -1
  50. package/.next/server/app/api/prompts/route.js.nft.json +1 -1
  51. package/.next/server/app/api/remote/route.js +8 -0
  52. package/.next/server/app/api/remote/route.js.nft.json +1 -0
  53. package/.next/server/app/api/remote/route_client-reference-manifest.js +1 -0
  54. package/.next/server/app/api/security/route.js +1 -1
  55. package/.next/server/app/api/security/route.js.nft.json +1 -1
  56. package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
  57. package/.next/server/app/api/sessions/[id]/export/route.js.nft.json +1 -1
  58. package/.next/server/app/api/sessions/[id]/route.js +1 -1
  59. package/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
  60. package/.next/server/app/api/sessions/import/route.js +2 -2
  61. package/.next/server/app/api/sessions/import/route.js.nft.json +1 -1
  62. package/.next/server/app/api/sessions/route.js +1 -1
  63. package/.next/server/app/api/sessions/route.js.nft.json +1 -1
  64. package/.next/server/app/api/skills/route.js +1 -1
  65. package/.next/server/app/api/skills/route.js.nft.json +1 -1
  66. package/.next/server/app/api/update/route.js +2 -2
  67. package/.next/server/app/api/update/route.js.nft.json +1 -1
  68. package/.next/server/app/api/version/route.js +1 -1
  69. package/.next/server/app/api/version/route.js.nft.json +1 -1
  70. package/.next/server/app/api/web-auth/route.js +3 -1
  71. package/.next/server/app/api/workspaces/route.js +1 -1
  72. package/.next/server/app/api/workspaces/route.js.nft.json +1 -1
  73. package/.next/server/app/icon.svg/route.js +1 -1
  74. package/.next/server/app/index.html +1 -1
  75. package/.next/server/app/index.rsc +4 -4
  76. package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
  77. package/.next/server/app/index.segments/_full.segment.rsc +4 -4
  78. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  79. package/.next/server/app/login/page.js +1 -1
  80. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  81. package/.next/server/app/login.html +1 -1
  82. package/.next/server/app/login.rsc +3 -3
  83. package/.next/server/app/login.segments/_full.segment.rsc +3 -3
  84. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  85. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +3 -3
  86. package/.next/server/app/page.js +1 -1
  87. package/.next/server/app/page.js.nft.json +1 -1
  88. package/.next/server/app/page_client-reference-manifest.js +1 -1
  89. package/.next/server/app-paths-manifest.json +8 -6
  90. package/.next/server/chunks/229.js +1 -1
  91. package/.next/server/chunks/243.js +1 -0
  92. package/.next/server/chunks/393.js +1 -0
  93. package/.next/server/chunks/496.js +1 -1
  94. package/.next/server/chunks/538.js +1 -0
  95. package/.next/server/chunks/686.js +1 -4
  96. package/.next/server/chunks/802.js +2 -0
  97. package/.next/server/chunks/807.js +1 -0
  98. package/.next/server/chunks/920.js +4 -0
  99. package/.next/server/chunks/924.js +6 -0
  100. package/.next/server/instrumentation.js +1 -0
  101. package/.next/server/instrumentation.js.nft.json +1 -0
  102. package/.next/server/middleware-build-manifest.js +1 -1
  103. package/.next/server/middleware-react-loadable-manifest.js +1 -1
  104. package/.next/server/middleware.js +4 -3
  105. package/.next/server/pages/404.html +1 -1
  106. package/.next/server/pages/500.html +1 -1
  107. package/.next/server/server-reference-manifest.json +1 -1
  108. package/.next/static/_Y3Lq0lI5RJ1tFSOnDi0L/_buildManifest.js +1 -0
  109. package/.next/static/chunks/2716.7ad073038e94a802.js +1 -0
  110. package/.next/static/chunks/316-5ea443ee6b512aa9.js +1 -0
  111. package/.next/static/chunks/4673.8a5a9e6855350e34.js +1 -0
  112. package/.next/static/chunks/4686-b1e9fa876d0ea671.js +1 -0
  113. package/.next/static/chunks/4778.e4c9ed093a413bc4.js +1 -0
  114. package/.next/static/chunks/5173.91ddb803785009f8.js +1 -0
  115. package/.next/static/chunks/839.d3fe337a9117c325.js +1 -0
  116. package/.next/static/chunks/8552.6bfe3e101250b20f.js +1 -0
  117. package/.next/static/chunks/app/_global-error/page-b179d9f7f6f2daf9.js +1 -0
  118. package/.next/static/chunks/app/api/agent/[id]/events/route-b179d9f7f6f2daf9.js +1 -0
  119. package/.next/static/chunks/app/api/agent/[id]/route-b179d9f7f6f2daf9.js +1 -0
  120. package/.next/static/chunks/app/api/agent/[id]/tree/route-b179d9f7f6f2daf9.js +1 -0
  121. package/.next/static/chunks/app/api/agent/new/route-b179d9f7f6f2daf9.js +1 -0
  122. package/.next/static/chunks/app/api/config/route-b179d9f7f6f2daf9.js +1 -0
  123. package/.next/static/chunks/app/api/files/route-b179d9f7f6f2daf9.js +1 -0
  124. package/.next/static/chunks/app/api/fs/route-b179d9f7f6f2daf9.js +1 -0
  125. package/.next/static/chunks/app/api/git/route-b179d9f7f6f2daf9.js +1 -0
  126. package/.next/static/chunks/app/api/growth/route-b179d9f7f6f2daf9.js +1 -0
  127. package/.next/static/chunks/app/api/health/route-b179d9f7f6f2daf9.js +1 -0
  128. package/.next/static/chunks/app/api/models/route-b179d9f7f6f2daf9.js +1 -0
  129. package/.next/static/chunks/app/api/pi-settings/route-b179d9f7f6f2daf9.js +1 -0
  130. package/.next/static/chunks/app/api/plugins/route-b179d9f7f6f2daf9.js +1 -0
  131. package/.next/static/chunks/app/api/prompts/route-b179d9f7f6f2daf9.js +1 -0
  132. package/.next/static/chunks/app/api/remote/route-b179d9f7f6f2daf9.js +1 -0
  133. package/.next/static/chunks/app/api/security/route-b179d9f7f6f2daf9.js +1 -0
  134. package/.next/static/chunks/app/api/sessions/[id]/export/route-b179d9f7f6f2daf9.js +1 -0
  135. package/.next/static/chunks/app/api/sessions/[id]/route-b179d9f7f6f2daf9.js +1 -0
  136. package/.next/static/chunks/app/api/sessions/import/route-b179d9f7f6f2daf9.js +1 -0
  137. package/.next/static/chunks/app/api/sessions/route-b179d9f7f6f2daf9.js +1 -0
  138. package/.next/static/chunks/app/api/skills/route-b179d9f7f6f2daf9.js +1 -0
  139. package/.next/static/chunks/app/api/update/route-b179d9f7f6f2daf9.js +1 -0
  140. package/.next/static/chunks/app/api/version/route-b179d9f7f6f2daf9.js +1 -0
  141. package/.next/static/chunks/app/api/web-auth/route-b179d9f7f6f2daf9.js +1 -0
  142. package/.next/static/chunks/app/api/workspaces/route-b179d9f7f6f2daf9.js +1 -0
  143. package/.next/static/chunks/app/login/page-5e276a1eeb8867df.js +1 -0
  144. package/.next/static/chunks/app/page-2e425ef83fba0782.js +1 -0
  145. package/.next/static/chunks/next/dist/client/components/builtin/app-error-b179d9f7f6f2daf9.js +1 -0
  146. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-b179d9f7f6f2daf9.js +1 -0
  147. package/.next/static/chunks/next/dist/client/components/builtin/not-found-b179d9f7f6f2daf9.js +1 -0
  148. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-b179d9f7f6f2daf9.js +1 -0
  149. package/.next/static/chunks/webpack-3614a8337843456b.js +1 -0
  150. package/.next/static/css/7391c42e1ae617f9.css +1 -0
  151. package/.next/types/app/api/fs/route.ts +353 -0
  152. package/.next/types/app/api/remote/route.ts +353 -0
  153. package/.next/types/routes.d.ts +3 -1
  154. package/.next/types/validator.ts +18 -0
  155. package/README.md +56 -10
  156. package/next.config.ts +3 -2
  157. package/package.json +3 -3
  158. package/scripts/launcher.mjs +194 -13
  159. package/scripts/release.mjs +0 -2
  160. package/src/app/api/agent/[id]/events/route.ts +11 -6
  161. package/src/app/api/fs/route.ts +119 -0
  162. package/src/app/api/growth/route.ts +11 -6
  163. package/src/app/api/models/route.ts +21 -9
  164. package/src/app/api/remote/route.ts +77 -0
  165. package/src/app/api/sessions/[id]/export/route.ts +11 -1
  166. package/src/app/api/sessions/route.ts +3 -1
  167. package/src/app/api/web-auth/route.ts +45 -49
  168. package/src/app/api/workspaces/route.ts +16 -0
  169. package/src/app/globals.css +6 -0
  170. package/src/app/login/page.tsx +8 -60
  171. package/src/components/AppShell.tsx +202 -155
  172. package/src/components/ChatInput.tsx +61 -6
  173. package/src/components/ChatWindow.tsx +274 -132
  174. package/src/components/DirectoryPicker.tsx +189 -0
  175. package/src/components/DrawerPanel.tsx +164 -0
  176. package/src/components/FileViewer.tsx +2 -2
  177. package/src/components/GrowthTree.tsx +6 -5
  178. package/src/components/MarkdownBlock.tsx +134 -0
  179. package/src/components/OutlineRail.tsx +22 -1
  180. package/src/components/ProjectPanel.tsx +197 -14
  181. package/src/components/RemoteSection.tsx +265 -0
  182. package/src/components/SessionImportSection.tsx +60 -38
  183. package/src/components/SettingsPanel.tsx +104 -57
  184. package/src/components/icons.tsx +23 -0
  185. package/src/hooks/useGrowth.ts +47 -25
  186. package/src/hooks/usePiWeb.ts +100 -14
  187. package/src/i18n.tsx +117 -28
  188. package/src/instrumentation-node.ts +35 -0
  189. package/src/instrumentation.ts +18 -0
  190. package/src/lib/agent-config.ts +134 -0
  191. package/src/lib/agent-manager.ts +243 -61
  192. package/src/lib/auth-throttle.ts +102 -0
  193. package/src/lib/auth.ts +35 -27
  194. package/src/lib/files-service.ts +61 -1
  195. package/src/lib/font.ts +84 -0
  196. package/src/lib/git-service.ts +5 -3
  197. package/src/lib/growth-service.ts +78 -30
  198. package/src/lib/growth-tracker.ts +91 -9
  199. package/src/lib/models-service.ts +36 -11
  200. package/src/lib/pi-settings.ts +10 -12
  201. package/src/lib/pi.ts +145 -18
  202. package/src/lib/plugins-service.ts +67 -35
  203. package/src/lib/remote/assets.json +4 -0
  204. package/src/lib/remote/mobile-proxy.ts +155 -0
  205. package/src/lib/remote/supervisor.ts +492 -0
  206. package/src/lib/security-service.ts +6 -11
  207. package/src/lib/session-import/claude.ts +45 -21
  208. package/src/lib/session-reader.ts +106 -3
  209. package/src/lib/skills-service.ts +34 -12
  210. package/src/lib/text-sanitize.ts +12 -0
  211. package/src/lib/theme.ts +3 -0
  212. package/src/lib/types.ts +16 -2
  213. package/src/lib/update-service.ts +0 -1
  214. package/src/lib/web-auth-shared.ts +5 -1
  215. package/src/lib/web-auth.ts +139 -22
  216. package/src/lib/web-launch-token.ts +55 -0
  217. package/src/lib/web-secret.ts +182 -0
  218. package/src/lib/workspace-store.ts +91 -7
  219. package/src/proxy.ts +46 -12
  220. package/.next/server/chunks/532.js +0 -1
  221. package/.next/static/chunks/2155.2f27fe7098987699.js +0 -1
  222. package/.next/static/chunks/2608.ec8ff1f5767c9056.js +0 -1
  223. package/.next/static/chunks/2716.93be8ad5c970c516.js +0 -1
  224. package/.next/static/chunks/4632.112d2e893d48d188.js +0 -1
  225. package/.next/static/chunks/4673.c1fb1580efc6ace7.js +0 -1
  226. package/.next/static/chunks/4686-f4a037d609cea3f2.js +0 -1
  227. package/.next/static/chunks/692-7992db0ae9cc3021.js +0 -1
  228. package/.next/static/chunks/app/_global-error/page-f77e9ede9808b18e.js +0 -1
  229. package/.next/static/chunks/app/api/agent/[id]/events/route-f77e9ede9808b18e.js +0 -1
  230. package/.next/static/chunks/app/api/agent/[id]/route-f77e9ede9808b18e.js +0 -1
  231. package/.next/static/chunks/app/api/agent/[id]/tree/route-f77e9ede9808b18e.js +0 -1
  232. package/.next/static/chunks/app/api/agent/new/route-f77e9ede9808b18e.js +0 -1
  233. package/.next/static/chunks/app/api/config/route-f77e9ede9808b18e.js +0 -1
  234. package/.next/static/chunks/app/api/files/route-f77e9ede9808b18e.js +0 -1
  235. package/.next/static/chunks/app/api/git/route-f77e9ede9808b18e.js +0 -1
  236. package/.next/static/chunks/app/api/growth/route-f77e9ede9808b18e.js +0 -1
  237. package/.next/static/chunks/app/api/health/route-f77e9ede9808b18e.js +0 -1
  238. package/.next/static/chunks/app/api/models/route-f77e9ede9808b18e.js +0 -1
  239. package/.next/static/chunks/app/api/pi-settings/route-f77e9ede9808b18e.js +0 -1
  240. package/.next/static/chunks/app/api/plugins/route-f77e9ede9808b18e.js +0 -1
  241. package/.next/static/chunks/app/api/prompts/route-f77e9ede9808b18e.js +0 -1
  242. package/.next/static/chunks/app/api/security/route-f77e9ede9808b18e.js +0 -1
  243. package/.next/static/chunks/app/api/sessions/[id]/export/route-f77e9ede9808b18e.js +0 -1
  244. package/.next/static/chunks/app/api/sessions/[id]/route-f77e9ede9808b18e.js +0 -1
  245. package/.next/static/chunks/app/api/sessions/import/route-f77e9ede9808b18e.js +0 -1
  246. package/.next/static/chunks/app/api/sessions/route-f77e9ede9808b18e.js +0 -1
  247. package/.next/static/chunks/app/api/skills/route-f77e9ede9808b18e.js +0 -1
  248. package/.next/static/chunks/app/api/update/route-f77e9ede9808b18e.js +0 -1
  249. package/.next/static/chunks/app/api/version/route-f77e9ede9808b18e.js +0 -1
  250. package/.next/static/chunks/app/api/web-auth/route-f77e9ede9808b18e.js +0 -1
  251. package/.next/static/chunks/app/api/workspaces/route-f77e9ede9808b18e.js +0 -1
  252. package/.next/static/chunks/app/login/page-52ef4841cbd199a8.js +0 -1
  253. package/.next/static/chunks/app/page-b51087831582ee37.js +0 -1
  254. package/.next/static/chunks/next/dist/client/components/builtin/app-error-f77e9ede9808b18e.js +0 -1
  255. package/.next/static/chunks/next/dist/client/components/builtin/forbidden-f77e9ede9808b18e.js +0 -1
  256. package/.next/static/chunks/next/dist/client/components/builtin/not-found-f77e9ede9808b18e.js +0 -1
  257. package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f77e9ede9808b18e.js +0 -1
  258. package/.next/static/chunks/webpack-c31263fd83d75ce6.js +0 -1
  259. package/.next/static/css/0cd968b9b62035b5.css +0 -1
  260. package/.next/static/en03hnPiuro0rnf8QkFEe/_buildManifest.js +0 -1
  261. /package/.next/static/{en03hnPiuro0rnf8QkFEe → _Y3Lq0lI5RJ1tFSOnDi0L}/_ssgManifest.js +0 -0
@@ -31,16 +31,17 @@ import {
31
31
  type BuiltinProviderSetup,
32
32
  type CustomProviderSetup,
33
33
  } from "@/components/ProviderSetupModal";
34
- import { SessionImportSection } from "@/components/SessionImportSection";
34
+ import { RemoteSection } from "@/components/RemoteSection";
35
35
  import { useI18n } from "@/i18n";
36
36
  import { applyPebrelTheme, loadPebrelTheme, loadThemeMode, type PebrelTheme, type ThemeMode } from "@/lib/theme";
37
+ import { applyFontChoice, CUSTOM_PREFIX, FONT_PRESETS, loadFontChoice } from "@/lib/font";
37
38
  import { modelDraftFromConfig, serializeProviderDraft, validateModelDrafts, type ModelDraft } from "@/lib/model-draft";
38
39
  import { customApiOptions } from "@/lib/provider-display";
39
40
  import { quotaWindowKind, supportsUsageProbe, type BalanceAmount, type BalanceDetailKey, type ProviderUsage, type QuotaWindow, type XaiUsage } from "@/lib/provider-usage";
40
41
  import type { ProviderView } from "@/lib/models-service";
41
42
  import type { ToolPreset } from "@/lib/types";
42
43
 
43
- type Section = "general" | "models" | "tools" | "skills" | "plugins" | "import";
44
+ type Section = "general" | "models" | "tools" | "skills" | "plugins" | "remote";
44
45
 
45
46
  export function SettingsPanel({
46
47
  open,
@@ -84,7 +85,7 @@ export function SettingsPanel({
84
85
  { id: "tools", label: t.toolsSection, icon: <IconAgentPresetOutline16 size={15} /> },
85
86
  { id: "plugins", label: t.setPlugins, icon: <IconPluginOutline16 size={15} /> },
86
87
  { id: "skills", label: t.setSkills, icon: <IconSkillOutline16 size={15} /> },
87
- { id: "import", label: t.importSessions, icon: <IconDownloadOutline16 size={15} /> },
88
+ { id: "remote", label: t.setRemote, icon: <IconShieldOutline16 size={15} /> },
88
89
  ];
89
90
 
90
91
  return (
@@ -151,7 +152,7 @@ export function SettingsPanel({
151
152
  {section === "skills" && <SkillsSection cwd={cwd} onOpenFileContent={onOpenFileContent} />}
152
153
  {section === "tools" && <ToolsSection toolPreset={toolPreset} onToolPresetChange={onToolPresetChange} tools={tools} onSetTools={onSetTools} />}
153
154
  {section === "plugins" && <PluginsSection cwd={cwd} />}
154
- {section === "import" && <SessionImportSection cwd={cwd} />}
155
+ {section === "remote" && <RemoteSection />}
155
156
  </div>
156
157
  </div>
157
158
  </div>
@@ -174,8 +175,6 @@ function GeneralSection({
174
175
  const [fontSize, setFontSize] = useState(14);
175
176
  const [chatFontSize, setChatFontSize] = useState(14);
176
177
  const [enterBehavior, setEnterBehavior] = useState<EnterBehavior>("steer");
177
- const [trust, setTrust] = useState<"ask" | "always" | "never">("ask");
178
- const [trustLoaded, setTrustLoaded] = useState(false);
179
178
  const [piSettings, setPiSettings] = useState<{ compaction: { enabled: boolean; reserveTokens: number; keepRecentTokens: number }; retry: { enabled: boolean; maxRetries: number; baseDelayMs: number } } | null>(null);
180
179
  const [versions, setVersions] = useState<{ piWeb: string; piEngine: string } | null>(null);
181
180
  const [webAuth, setWebAuth] = useState<{ enabled: boolean; authenticated: boolean } | null>(null);
@@ -187,7 +186,6 @@ function GeneralSection({
187
186
  const loadRemoteSettings = async () => {
188
187
  setSettingsLoading(true);
189
188
  setSettingsError(null);
190
- setTrustLoaded(false);
191
189
  const request = async (url: string) => {
192
190
  const response = await fetch(url);
193
191
  const json = await response.json();
@@ -200,10 +198,6 @@ function GeneralSection({
200
198
  request("/api/version"),
201
199
  request("/api/web-auth"),
202
200
  ]);
203
- if (sec.status === "fulfilled") {
204
- setTrust(sec.value.defaultProjectTrust);
205
- setTrustLoaded(true);
206
- }
207
201
  if (ps.status === "fulfilled") {
208
202
  setPiSettings(ps.value);
209
203
  setNumberDrafts({
@@ -313,26 +307,6 @@ function GeneralSection({
313
307
  />
314
308
  );
315
309
 
316
- const pickTrust = async (v: "ask" | "always" | "never") => {
317
- if (!trustLoaded || settingsSaving) return;
318
- setSettingsSaving(true);
319
- setSettingsError(null);
320
- try {
321
- const response = await fetch("/api/security", {
322
- method: "PUT",
323
- headers: { "Content-Type": "application/json" },
324
- body: JSON.stringify({ defaultProjectTrust: v }),
325
- });
326
- const result = await response.json();
327
- if (!response.ok || !result.success) throw new Error(result.error || "save failed");
328
- setTrust(v);
329
- } catch {
330
- setSettingsError("save");
331
- } finally {
332
- setSettingsSaving(false);
333
- }
334
- };
335
-
336
310
  const themes: { id: PebrelTheme; label: string; light: string; dark: string; accent: string }[] = [
337
311
  { id: "dsh", label: t.themeDsh, light: "#F9FAFB", dark: "#151517", accent: "#4176E6" },
338
312
  { id: "silver-steel", label: t.themeSilverSteel, light: "#F3F4F6", dark: "#1A1C24", accent: "#94A3B8" },
@@ -425,6 +399,12 @@ function GeneralSection({
425
399
  <Row title={t.fontChatSize} desc={t.fontChatSizeDesc}>
426
400
  <FontSizeStepper value={chatFontSize} onChange={pickChatFont} max={20} />
427
401
  </Row>
402
+ <Row title={t.fontUiFamily} desc={t.fontUiFamilyDesc}>
403
+ <FontFamilyPicker scope="ui" />
404
+ </Row>
405
+ <Row title={t.fontMonoFamily} desc={t.fontMonoFamilyDesc}>
406
+ <FontFamilyPicker scope="mono" />
407
+ </Row>
428
408
  <Row title={t.enterBehavior} desc={t.enterBehaviorDesc}>
429
409
  <SelectOption
430
410
  value={enterBehavior}
@@ -453,18 +433,6 @@ function GeneralSection({
453
433
  {numberInput("retry", "baseDelayMs", t.baseDelayMs)}
454
434
  </div>
455
435
  </Row>
456
- <Row title={t.securityTrust} desc={t.securityTrustDesc}>
457
- <SelectOption
458
- value={trust}
459
- disabled={!trustLoaded || settingsLoading || settingsSaving}
460
- options={[
461
- { value: "ask", label: t.trustAsk },
462
- { value: "always", label: t.trustAlways },
463
- { value: "never", label: t.trustNever },
464
- ]}
465
- onChange={(v) => pickTrust(v as "ask" | "always" | "never")}
466
- />
467
- </Row>
468
436
  <Row title={t.versionPiweb} desc={t.versionPiwebDesc}>
469
437
  <div className="flex items-center gap-2.5">
470
438
  <VersionLink href="https://github.com/RexVane/PiWeb" value={versions?.piWeb} />
@@ -478,7 +446,7 @@ function GeneralSection({
478
446
  </div>
479
447
  </Row>
480
448
  {webAuth?.enabled ? (
481
- <Row title={t.webAuth} desc={t.webAuthOn}>
449
+ <Row title={t.webAuth} desc={t.webAuthTokenOn}>
482
450
  <button
483
451
  className="btn-outline"
484
452
  style={{ height: 30, padding: "0 14px", fontSize: 12.5 }}
@@ -759,6 +727,79 @@ function FontSizeStepper({ value, onChange, max }: { value: number; onChange: (v
759
727
  );
760
728
  }
761
729
 
730
+ /** 字体族选择:预设下拉 + 「自定义」文本框。改动即生效并持久化。 */
731
+ function FontFamilyPicker({ scope }: { scope: "ui" | "mono" }) {
732
+ const { t, lang } = useI18n();
733
+ const [stored, setStored] = useState<string | null>(null);
734
+ const [custom, setCustom] = useState("");
735
+ // 「自定义」是下拉自己的选中状态,不能从输入框内容推断:删空输入框不该把下拉弹回预设、
736
+ // 连输入框一起收掉;选回预设也不该因为输入框里还留着字而停在「自定义」
737
+ const [customMode, setCustomMode] = useState(false);
738
+
739
+ useEffect(() => {
740
+ const saved = loadFontChoice(scope);
741
+ const savedCustom = saved?.startsWith(CUSTOM_PREFIX) ? saved.slice(CUSTOM_PREFIX.length) : null;
742
+ setStored(saved);
743
+ setCustomMode(savedCustom !== null);
744
+ setCustom(savedCustom ?? "");
745
+ }, [scope]);
746
+
747
+ const applyCustom = (v: string) => {
748
+ setCustom(v);
749
+ const next = v.trim() ? CUSTOM_PREFIX + v.trim() : null;
750
+ setStored(next);
751
+ applyFontChoice(scope, next);
752
+ };
753
+
754
+ const pick = (v: string) => {
755
+ if (v === CUSTOM_PREFIX) {
756
+ setCustomMode(true);
757
+ // 输入框里留着上次的字体栈就接着用;空的先回落默认,等你输入
758
+ applyCustom(custom);
759
+ return;
760
+ }
761
+ setCustomMode(false);
762
+ setStored(v);
763
+ applyFontChoice(scope, v);
764
+ };
765
+
766
+ const selectValue = customMode ? CUSTOM_PREFIX : stored ?? FONT_PRESETS[scope][0].id;
767
+
768
+ return (
769
+ <div className="flex flex-col items-end gap-2">
770
+ <SelectOption
771
+ value={selectValue}
772
+ width={220}
773
+ options={[
774
+ ...FONT_PRESETS[scope].map((p) => ({
775
+ value: p.id,
776
+ label: lang === "zh" ? p.label.zh : p.label.en,
777
+ })),
778
+ { value: CUSTOM_PREFIX, label: t.fontCustom },
779
+ ]}
780
+ onChange={pick}
781
+ />
782
+ {customMode && (
783
+ <input
784
+ value={custom}
785
+ onChange={(e) => applyCustom(e.target.value)}
786
+ placeholder={t.fontCustomPlaceholder}
787
+ style={{
788
+ height: 30,
789
+ width: 260,
790
+ padding: "0 10px",
791
+ fontSize: 13,
792
+ background: "var(--dsw-input-major)",
793
+ border: "0.5px solid var(--dsw-border-l3)",
794
+ borderRadius: 8,
795
+ color: "var(--dsw-label-primary)",
796
+ }}
797
+ />
798
+ )}
799
+ </div>
800
+ );
801
+ }
802
+
762
803
  // ---------- Models ----------
763
804
 
764
805
  function ModelsSection() {
@@ -1234,10 +1275,12 @@ function ModelsSection() {
1234
1275
  return (
1235
1276
  <div key={p.id}>
1236
1277
  {/* 卡片:身份/操作行在上,额度显示放在同一个框内的第二段(分隔线区分) */}
1237
- <div className="flex flex-col rounded-2xl px-4 py-3" style={{ border: "0.5px solid var(--dsw-border-l2)" }}>
1238
- <div className="flex items-center gap-3">
1278
+ <div className="flex min-w-0 flex-col overflow-hidden rounded-2xl px-4 py-3" style={{ border: "0.5px solid var(--dsw-border-l2)" }}>
1279
+ {/* 空间不够时整行换行,而不是把按钮压窄——中文按钮被压窄会一个字一行 */}
1280
+ <div className="flex min-w-0 flex-wrap items-center gap-x-3 gap-y-2">
1239
1281
  <ProviderBrand id={p.id} name={providerName(p.id)} size={34} />
1240
- <div className="min-w-0 flex-1">
1282
+ {/* 名称列给最小宽度:窄屏时宁可换行,也不要把名字压成几个像素 */}
1283
+ <div className="min-w-[132px] flex-1">
1241
1284
  <div className="truncate" style={{ fontSize: 14, fontWeight: 500 }}>{providerName(p.id)}</div>
1242
1285
  <div className="truncate" style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }}>
1243
1286
  {p.id}{p.baseUrl ? ` · ${p.baseUrl}` : ""}
@@ -1245,28 +1288,30 @@ function ModelsSection() {
1245
1288
  </div>
1246
1289
  {isCustom && (
1247
1290
  <span
1248
- className="rounded-md px-1.5 py-0.5"
1291
+ className="flex-none rounded-md px-1.5 py-0.5"
1249
1292
  style={{ fontSize: 10.5, border: "0.5px solid var(--dsw-border-l3)", color: "var(--dsw-label-tertiary)" }}
1250
1293
  >
1251
1294
  {t.customBadge}
1252
1295
  </span>
1253
1296
  )}
1254
1297
  <span
1255
- className="inline-block h-2 w-2 rounded-full"
1298
+ className="inline-block h-2 w-2 flex-none rounded-full"
1256
1299
  style={{ background: p.authReady ? "var(--dsw-success)" : "var(--dsw-danger)" }}
1257
1300
  title={p.authError || (p.authReady ? t.providerReady : t.authUnavailable)}
1258
1301
  />
1259
1302
  <span
1260
- className="rounded-md px-1.5 py-0.5"
1303
+ className="flex-none rounded-md px-1.5 py-0.5"
1261
1304
  style={{ fontSize: 10.5, border: "0.5px solid var(--dsw-border-l3)", color: "var(--dsw-label-tertiary)" }}
1262
1305
  >
1263
1306
  {t.credentialSource.replace("{source}", credentialSource(p))}
1264
1307
  </span>
1265
- <button className="btn-outline" style={{ height: 28, padding: "0 12px", fontSize: 12 }} onClick={() => startEdit(p.id)}>
1308
+ {/* 操作组整体不压缩:按钮加 whitespace-nowrap,中文标签永不竖排 */}
1309
+ <button className="btn-outline flex-none whitespace-nowrap" style={{ height: 28, padding: "0 12px", fontSize: 12 }} onClick={() => startEdit(p.id)}>
1266
1310
  {t.edit}
1267
1311
  </button>
1268
1312
  {p.storedAuthType === "oauth" && (
1269
1313
  <button
1314
+ className="flex-none whitespace-nowrap"
1270
1315
  style={{ height: 28, padding: "0 12px", fontSize: 12, color: "var(--dsw-danger)" }}
1271
1316
  onClick={async () => {
1272
1317
  if (!window.confirm(t.confirmRemoveOAuth.replace("{name}", providerName(p.id)))) return;
@@ -1278,7 +1323,7 @@ function ModelsSection() {
1278
1323
  )}
1279
1324
  {p.authTypes?.includes("oauth") && (
1280
1325
  <button
1281
- className="btn-outline"
1326
+ className="btn-outline flex-none whitespace-nowrap"
1282
1327
  style={{ height: 28, padding: "0 12px", fontSize: 12 }}
1283
1328
  onClick={() => {
1284
1329
  setOauthDone(null);
@@ -1290,7 +1335,7 @@ function ModelsSection() {
1290
1335
  </button>
1291
1336
  )}
1292
1337
  {customIds.has(p.id) && (
1293
- <button style={{ fontSize: 12.5, color: "var(--dsw-danger)" }} onClick={() => deleteCustom(p.id)}>
1338
+ <button className="flex-none whitespace-nowrap" style={{ fontSize: 12.5, color: "var(--dsw-danger)" }} onClick={() => deleteCustom(p.id)}>
1294
1339
  {t.delete}
1295
1340
  </button>
1296
1341
  )}
@@ -1965,7 +2010,9 @@ function PluginsSection({ cwd }: { cwd: string }) {
1965
2010
  return next;
1966
2011
  });
1967
2012
 
1968
- const classify = (path: string): "global" | "project" | "package" => {
2013
+ const classify = (path: string): "global" | "project" | "package" | "builtin" => {
2014
+ // SDK 内置扩展(MCP / codemode / tool_search)没有文件路径,只有 builtin:<name>
2015
+ if (path.startsWith("builtin:")) return "builtin";
1969
2016
  const norm = path.replace(/\\/g, "/");
1970
2017
  if (norm.includes("/.pi/agent/npm/") || norm.includes("/.pi/agent/git/")) return "package";
1971
2018
  if (norm.includes("/.pi/agent/")) return "global";
@@ -1973,8 +2020,8 @@ function PluginsSection({ cwd }: { cwd: string }) {
1973
2020
  return "project";
1974
2021
  };
1975
2022
 
1976
- const scopeLabel = (s: "global" | "project" | "package") =>
1977
- s === "global" ? t.scopeGlobal : s === "project" ? t.scopeProject : t.scopePackage;
2023
+ const scopeLabel = (s: "global" | "project" | "package" | "builtin") =>
2024
+ s === "global" ? t.scopeGlobal : s === "project" ? t.scopeProject : s === "builtin" ? t.promptToolBuiltin : t.scopePackage;
1978
2025
 
1979
2026
  // 独立扩展 = 排除随包安装的(包已在 packages 里展示)
1980
2027
  const standalone = extensions.filter((e: any) => classify(e.path) !== "package");
@@ -2194,7 +2241,7 @@ function PluginsSection({ cwd }: { cwd: string }) {
2194
2241
  border: "0.5px solid var(--dsw-border-l2)",
2195
2242
  }}
2196
2243
  >
2197
- <div className="flex h-11 items-center justify-between px-3.5 gap-2">
2244
+ <div className="flex min-w-0 items-center justify-between gap-2 px-3.5" style={{ height: 44 }}>
2198
2245
  <span
2199
2246
  className="min-w-0 flex-1 truncate font-medium text-[var(--dsw-label-primary)] cursor-pointer select-none"
2200
2247
  style={{ fontSize: 13.5 }}
@@ -51,6 +51,11 @@ export const IconPanelLeftOutline16 = ({ size = 16, ...rest }: P) => (
51
51
  </svg>
52
52
  );
53
53
 
54
+ /** ic_ds_panel_right_outline_16 — 面板在右(左面板图标水平镜像);右侧抽屉开关用 */
55
+ export const IconPanelRightOutline16 = ({ size = 16, style, ...rest }: P) => (
56
+ <IconPanelLeftOutline16 size={size} style={{ transform: "scaleX(-1)", ...style }} {...rest} />
57
+ );
58
+
54
59
  /** ic_ds_plus_outline_16 */
55
60
  export const IconPlusOutline16 = ({ size = 16, ...rest }: P) => (
56
61
  <svg width={size} height={size} viewBox="0 0 16 16" fill="none" {...rest}>
@@ -108,6 +113,24 @@ export const IconBranchOutline16 = ({ size = 16, ...rest }: P) => (
108
113
  </svg>
109
114
  );
110
115
 
116
+ /** 引用到输入框:@ 号(描边画法,11px 小尺寸下也清晰) */
117
+ export const IconAtOutline16 = ({ size = 16, ...rest }: P) => (
118
+ <svg
119
+ width={size}
120
+ height={size}
121
+ viewBox="0 0 24 24"
122
+ fill="none"
123
+ stroke="currentColor"
124
+ strokeWidth={2}
125
+ strokeLinecap="round"
126
+ strokeLinejoin="round"
127
+ {...rest}
128
+ >
129
+ <circle cx="12" cy="12" r="4" />
130
+ <path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-3.92 7.94" />
131
+ </svg>
132
+ );
133
+
111
134
  /** ic_ds_edit_outline_16 */
112
135
  export const IconEditOutline16 = ({ size = 16, ...rest }: P) => (
113
136
  <svg width={size} height={size} viewBox="0 0 16 16" fill="none" {...rest}>
@@ -2,8 +2,12 @@
2
2
 
3
3
  /**
4
4
  * useGrowth:项目生长视图的状态(每轮一个 git commit)。
5
- * 时间轴(拉取 + 实时 commit 去重合并)、轮次导航 / 默认跟随、范围(本轮 / 本会话累计)、
5
+ * 时间轴(拉取 + 实时 commit 去重合并)、轮次导航:选中某个具体轮次,不再有「跟随」这层含义、范围(本轮 / 本会话累计)、
6
6
  * 所选轮的文件与变更、当前磁盘全量目录懒加载、展开状态与新变更动画。
7
+ *
8
+ * 选中语义:selectedCommit 永远是一个具体 commit(不再用 null 表示「跟随最新」)。
9
+ * 新轮次到来时不动选中——用户停在他正在看的那轮;要回到最新轮由调用方显式 selectLatest()
10
+ * (打开面板时就是这么做的)。
7
11
  */
8
12
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
9
13
  import { allDirPaths, buildTree, dirsToReveal, graftLazy, type TreeNode } from "@/lib/growth-tree";
@@ -28,8 +32,8 @@ export interface GrowthApi {
28
32
  error: string | null;
29
33
  /** 本会话的时间轴(时间正序,不含工作区基线) */
30
34
  rounds: GrowthEntry[];
35
+ /** 所选轮:永远是具体一轮;选中的 commit 不在当前列表里时回落最新一轮 */
31
36
  selected: GrowthEntry | null;
32
- following: boolean;
33
37
  scope: GrowthScope;
34
38
  /** 所选范围的 from/to tree(查看器取 diff 用) */
35
39
  range: { from: string; to: string } | null;
@@ -41,8 +45,9 @@ export interface GrowthApi {
41
45
  expanded: Set<string>;
42
46
  /** 最新一轮刚改动的路径(入场动画) */
43
47
  fresh: Set<string>;
44
- follow(): void;
45
- /** 选中某一轮(时间轴柱);选到最后一轮等于回到跟随 */
48
+ /** 显式选中最新一轮(打开面板时落在最新轮);没有轮次时不动 */
49
+ selectLatest(): void;
50
+ /** 选中某一轮(时间轴柱);选中最新一轮也只是普通选中,不再是「回到跟随」 */
46
51
  select(commit: string): void;
47
52
  prev(): void;
48
53
  next(): void;
@@ -239,7 +244,7 @@ export function useGrowth({
239
244
  [cwd],
240
245
  );
241
246
 
242
- // 切会话:回到跟随、清实时相关状态
247
+ // 切会话:清空选中(selected 会回落到新会话的最新一轮)、清实时相关状态
243
248
  useEffect(() => {
244
249
  setSelectedCommit(null);
245
250
  setFresh(new Set());
@@ -342,13 +347,21 @@ export function useGrowth({
342
347
  };
343
348
  }, [active, cwd, diskPulse, beginRequest, isCurrent, requestScope, updateLazy]);
344
349
 
345
- const following = selectedCommit === null;
350
+ // 选中永远是一个具体轮次;选中的 commit 不在当前列表里(被截断 / 换了会话)时回落最新一轮
346
351
  const selected = useMemo(
347
- () => (following ? latest : rounds.find((round) => round.commit === selectedCommit) ?? latest) ?? null,
348
- [following, latest, rounds, selectedCommit],
352
+ () => (rounds.find((round) => round.commit === selectedCommit) ?? latest) ?? null,
353
+ [latest, rounds, selectedCommit],
349
354
  );
350
355
  const sessionStart = rounds[0]?.parentTree ?? null;
351
356
 
357
+ // 把「回落来的」选中固化成具体一轮:selectedCommit 为空(null,还没选过)或指向的 commit
358
+ // 已不在列表里时,selected 走的是 ?? latest 兜底。若不在下一拍把它写实,之后新的一轮到来
359
+ // 会让兜底跟着 latest 一起往前跳——用户正看着旧的一轮却被拽走。写实一次之后,新轮次就不再影响选中。
360
+ useEffect(() => {
361
+ if (!selected || selectedCommit === selected.commit) return;
362
+ setSelectedCommit(selected.commit);
363
+ }, [selected, selectedCommit]);
364
+
352
365
  // 所选轮的文件清单;缓存身份包含工作区与会话,迟到结果不写缓存。
353
366
  useEffect(() => {
354
367
  if (!active) return;
@@ -440,11 +453,13 @@ export function useGrowth({
440
453
  return [...paths];
441
454
  }, [files, selected, lazyChildren, key]);
442
455
 
443
- // 新的一轮到来(跟随中):自动展开改动路径上的目录(用户手动收起过的除外)+ 入场动画
456
+ // 新的一轮到来(不再跟随):用户正看着最新这轮时,自动展开改动路径上的目录(用户手动收起过的除外)+ 入场动画
444
457
  useEffect(() => {
445
- if (!latest || !following || latest.commit === lastRevealed.current) return;
458
+ if (!latest || latest.commit === lastRevealed.current) return;
446
459
  const first = lastRevealed.current === "";
447
460
  lastRevealed.current = latest.commit;
461
+ // 用户停在旧的一轮看历史时,新轮次不打扰他:不展开、不改选中
462
+ if (selected && selected.commit !== latest.commit) return;
448
463
  // 打开面板时已有的历史不播动画,只对之后新提交的一轮
449
464
  if (first && !liveRounds.some((round) => round.commit === latest.commit)) return;
450
465
  const list = latest.changes;
@@ -461,14 +476,16 @@ export function useGrowth({
461
476
  setFresh(new Set(list.map((c) => c.path)));
462
477
  if (freshTimer.current) clearTimeout(freshTimer.current);
463
478
  freshTimer.current = setTimeout(() => setFresh(new Set()), FRESH_MS);
464
- }, [latest, following, liveRounds, persistExpanded]);
479
+ }, [latest, selected, liveRounds, persistExpanded]);
480
+ /** 用户主动选中的那一轮(决定是否自动展开目录);打开面板落到最新轮不写它 */
481
+ const expandCommit = useRef<string | null>(null);
465
482
  useEffect(() => () => {
466
483
  if (freshTimer.current) clearTimeout(freshTimer.current);
467
484
  }, []);
468
485
 
469
- // 手动选轮:展开这一轮改动的目录
486
+ // 手动选轮(用户主动点选):展开这一轮改动的目录
470
487
  useEffect(() => {
471
- if (following || !selected) return;
488
+ if (!selected || expandCommit.current !== selected.commit) return;
472
489
  const list = changes.slice(0, REVEAL_LIMIT);
473
490
  if (!list.length) return;
474
491
  const dirs = dirsToReveal(list);
@@ -478,25 +495,31 @@ export function useGrowth({
478
495
  for (const d of dirs) next.add(d);
479
496
  return next;
480
497
  });
481
- }, [following, selected, changes]);
482
-
483
- const follow = useCallback(() => {
484
- setSelectedCommit(null);
498
+ }, [selected, changes]);
499
+
500
+ /** 显式选到最新一轮:打开面板时用,不触发上面的目录自动展开 */
501
+ const selectLatest = useCallback(() => {
502
+ const last = rounds[rounds.length - 1];
503
+ if (last) setSelectedCommit(last.commit);
504
+ }, [rounds]);
505
+ const select = useCallback((commit: string) => {
506
+ expandCommit.current = commit;
507
+ setSelectedCommit(commit);
485
508
  }, []);
486
- const select = useCallback(
487
- (commit: string) => setSelectedCommit(rounds.length && rounds[rounds.length - 1].commit === commit ? null : commit),
488
- [rounds],
489
- );
490
509
  const prev = useCallback(() => {
491
510
  if (!selected) return;
492
511
  const idx = rounds.findIndex((round) => round.commit === selected.commit);
493
- if (idx > 0) setSelectedCommit(rounds[idx - 1].commit);
512
+ if (idx > 0) {
513
+ expandCommit.current = rounds[idx - 1].commit;
514
+ setSelectedCommit(rounds[idx - 1].commit);
515
+ }
494
516
  }, [selected, rounds]);
495
517
  const next = useCallback(() => {
496
518
  if (!selected) return;
497
519
  const idx = rounds.findIndex((round) => round.commit === selected.commit);
498
520
  if (idx < 0 || idx >= rounds.length - 1) return;
499
- setSelectedCommit(idx + 1 === rounds.length - 1 ? null : rounds[idx + 1].commit);
521
+ expandCommit.current = rounds[idx + 1].commit;
522
+ setSelectedCommit(rounds[idx + 1].commit);
500
523
  }, [selected, rounds]);
501
524
 
502
525
  const toggleDir = useCallback(
@@ -592,7 +615,6 @@ export function useGrowth({
592
615
  error: runtimeError ?? (fetched.key === key ? fetched.error : null) ?? (range && rangeChanges?.key === `${key}|${range.from}..${range.to}` ? rangeChanges.error : null) ?? null,
593
616
  rounds,
594
617
  selected,
595
- following,
596
618
  scope,
597
619
  range,
598
620
  changes,
@@ -600,7 +622,7 @@ export function useGrowth({
600
622
  filePaths,
601
623
  expanded,
602
624
  fresh,
603
- follow,
625
+ selectLatest,
604
626
  select,
605
627
  prev,
606
628
  next,