@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.
- package/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +8 -6
- package/.next/build-manifest.json +3 -3
- package/.next/prerender-manifest.json +4 -4
- package/.next/react-loadable-manifest.json +23 -9
- package/.next/required-server-files.js +2 -1
- package/.next/required-server-files.json +2 -1
- package/.next/routes-manifest.json +12 -0
- package/.next/server/app/_global-error/page.js +2 -2
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page.js +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +2 -2
- package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/api/agent/[id]/events/route.js +3 -3
- package/.next/server/app/api/agent/[id]/events/route.js.nft.json +1 -1
- package/.next/server/app/api/agent/[id]/route.js +1 -1
- package/.next/server/app/api/agent/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/agent/[id]/tree/route.js +1 -1
- package/.next/server/app/api/agent/[id]/tree/route.js.nft.json +1 -1
- package/.next/server/app/api/agent/new/route.js +1 -1
- package/.next/server/app/api/agent/new/route.js.nft.json +1 -1
- package/.next/server/app/api/config/route.js +1 -1
- package/.next/server/app/api/config/route.js.nft.json +1 -1
- package/.next/server/app/api/files/route.js +1 -1
- package/.next/server/app/api/files/route.js.nft.json +1 -1
- package/.next/server/app/api/fs/route.js +1 -0
- package/.next/server/app/api/fs/route.js.nft.json +1 -0
- package/.next/server/app/api/fs/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/git/route.js +2 -2
- package/.next/server/app/api/git/route.js.nft.json +1 -1
- package/.next/server/app/api/growth/route.js +1 -1
- package/.next/server/app/api/growth/route.js.nft.json +1 -1
- package/.next/server/app/api/health/route.js +1 -1
- package/.next/server/app/api/models/route.js +1 -1
- package/.next/server/app/api/models/route.js.nft.json +1 -1
- package/.next/server/app/api/pi-settings/route.js +1 -1
- package/.next/server/app/api/pi-settings/route.js.nft.json +1 -1
- package/.next/server/app/api/plugins/route.js +1 -1
- package/.next/server/app/api/plugins/route.js.nft.json +1 -1
- package/.next/server/app/api/prompts/route.js +1 -1
- package/.next/server/app/api/prompts/route.js.nft.json +1 -1
- package/.next/server/app/api/remote/route.js +8 -0
- package/.next/server/app/api/remote/route.js.nft.json +1 -0
- package/.next/server/app/api/remote/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/security/route.js +1 -1
- package/.next/server/app/api/security/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/export/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/import/route.js +2 -2
- package/.next/server/app/api/sessions/import/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/route.js +1 -1
- package/.next/server/app/api/sessions/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/route.js +1 -1
- package/.next/server/app/api/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/update/route.js +2 -2
- package/.next/server/app/api/update/route.js.nft.json +1 -1
- package/.next/server/app/api/version/route.js +1 -1
- package/.next/server/app/api/version/route.js.nft.json +1 -1
- package/.next/server/app/api/web-auth/route.js +3 -1
- package/.next/server/app/api/workspaces/route.js +1 -1
- package/.next/server/app/api/workspaces/route.js.nft.json +1 -1
- package/.next/server/app/icon.svg/route.js +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +4 -4
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login/page.js +1 -1
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/login.html +1 -1
- package/.next/server/app/login.rsc +3 -3
- package/.next/server/app/login.segments/_full.segment.rsc +3 -3
- package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/page.js +1 -1
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +8 -6
- package/.next/server/chunks/229.js +1 -1
- package/.next/server/chunks/243.js +1 -0
- package/.next/server/chunks/393.js +1 -0
- package/.next/server/chunks/496.js +1 -1
- package/.next/server/chunks/538.js +1 -0
- package/.next/server/chunks/686.js +1 -4
- package/.next/server/chunks/802.js +2 -0
- package/.next/server/chunks/807.js +1 -0
- package/.next/server/chunks/920.js +4 -0
- package/.next/server/chunks/924.js +6 -0
- package/.next/server/instrumentation.js +1 -0
- package/.next/server/instrumentation.js.nft.json +1 -0
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/.next/server/middleware.js +4 -3
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/_Y3Lq0lI5RJ1tFSOnDi0L/_buildManifest.js +1 -0
- package/.next/static/chunks/2716.7ad073038e94a802.js +1 -0
- package/.next/static/chunks/316-5ea443ee6b512aa9.js +1 -0
- package/.next/static/chunks/4673.8a5a9e6855350e34.js +1 -0
- package/.next/static/chunks/4686-b1e9fa876d0ea671.js +1 -0
- package/.next/static/chunks/4778.e4c9ed093a413bc4.js +1 -0
- package/.next/static/chunks/5173.91ddb803785009f8.js +1 -0
- package/.next/static/chunks/839.d3fe337a9117c325.js +1 -0
- package/.next/static/chunks/8552.6bfe3e101250b20f.js +1 -0
- package/.next/static/chunks/app/_global-error/page-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/events/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/tree/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/agent/new/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/config/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/files/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/fs/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/git/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/growth/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/health/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/models/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/pi-settings/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/plugins/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/prompts/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/remote/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/security/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/export/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/sessions/import/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/sessions/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/skills/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/update/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/version/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/web-auth/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/workspaces/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/login/page-5e276a1eeb8867df.js +1 -0
- package/.next/static/chunks/app/page-2e425ef83fba0782.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/webpack-3614a8337843456b.js +1 -0
- package/.next/static/css/7391c42e1ae617f9.css +1 -0
- package/.next/types/app/api/fs/route.ts +353 -0
- package/.next/types/app/api/remote/route.ts +353 -0
- package/.next/types/routes.d.ts +3 -1
- package/.next/types/validator.ts +18 -0
- package/README.md +56 -10
- package/next.config.ts +3 -2
- package/package.json +3 -3
- package/scripts/launcher.mjs +194 -13
- package/scripts/release.mjs +0 -2
- package/src/app/api/agent/[id]/events/route.ts +11 -6
- package/src/app/api/fs/route.ts +119 -0
- package/src/app/api/growth/route.ts +11 -6
- package/src/app/api/models/route.ts +21 -9
- package/src/app/api/remote/route.ts +77 -0
- package/src/app/api/sessions/[id]/export/route.ts +11 -1
- package/src/app/api/sessions/route.ts +3 -1
- package/src/app/api/web-auth/route.ts +45 -49
- package/src/app/api/workspaces/route.ts +16 -0
- package/src/app/globals.css +6 -0
- package/src/app/login/page.tsx +8 -60
- package/src/components/AppShell.tsx +202 -155
- package/src/components/ChatInput.tsx +61 -6
- package/src/components/ChatWindow.tsx +274 -132
- package/src/components/DirectoryPicker.tsx +189 -0
- package/src/components/DrawerPanel.tsx +164 -0
- package/src/components/FileViewer.tsx +2 -2
- package/src/components/GrowthTree.tsx +6 -5
- package/src/components/MarkdownBlock.tsx +134 -0
- package/src/components/OutlineRail.tsx +22 -1
- package/src/components/ProjectPanel.tsx +197 -14
- package/src/components/RemoteSection.tsx +265 -0
- package/src/components/SessionImportSection.tsx +60 -38
- package/src/components/SettingsPanel.tsx +104 -57
- package/src/components/icons.tsx +23 -0
- package/src/hooks/useGrowth.ts +47 -25
- package/src/hooks/usePiWeb.ts +100 -14
- package/src/i18n.tsx +117 -28
- package/src/instrumentation-node.ts +35 -0
- package/src/instrumentation.ts +18 -0
- package/src/lib/agent-config.ts +134 -0
- package/src/lib/agent-manager.ts +243 -61
- package/src/lib/auth-throttle.ts +102 -0
- package/src/lib/auth.ts +35 -27
- package/src/lib/files-service.ts +61 -1
- package/src/lib/font.ts +84 -0
- package/src/lib/git-service.ts +5 -3
- package/src/lib/growth-service.ts +78 -30
- package/src/lib/growth-tracker.ts +91 -9
- package/src/lib/models-service.ts +36 -11
- package/src/lib/pi-settings.ts +10 -12
- package/src/lib/pi.ts +145 -18
- package/src/lib/plugins-service.ts +67 -35
- package/src/lib/remote/assets.json +4 -0
- package/src/lib/remote/mobile-proxy.ts +155 -0
- package/src/lib/remote/supervisor.ts +492 -0
- package/src/lib/security-service.ts +6 -11
- package/src/lib/session-import/claude.ts +45 -21
- package/src/lib/session-reader.ts +106 -3
- package/src/lib/skills-service.ts +34 -12
- package/src/lib/text-sanitize.ts +12 -0
- package/src/lib/theme.ts +3 -0
- package/src/lib/types.ts +16 -2
- package/src/lib/update-service.ts +0 -1
- package/src/lib/web-auth-shared.ts +5 -1
- package/src/lib/web-auth.ts +139 -22
- package/src/lib/web-launch-token.ts +55 -0
- package/src/lib/web-secret.ts +182 -0
- package/src/lib/workspace-store.ts +91 -7
- package/src/proxy.ts +46 -12
- package/.next/server/chunks/532.js +0 -1
- package/.next/static/chunks/2155.2f27fe7098987699.js +0 -1
- package/.next/static/chunks/2608.ec8ff1f5767c9056.js +0 -1
- package/.next/static/chunks/2716.93be8ad5c970c516.js +0 -1
- package/.next/static/chunks/4632.112d2e893d48d188.js +0 -1
- package/.next/static/chunks/4673.c1fb1580efc6ace7.js +0 -1
- package/.next/static/chunks/4686-f4a037d609cea3f2.js +0 -1
- package/.next/static/chunks/692-7992db0ae9cc3021.js +0 -1
- package/.next/static/chunks/app/_global-error/page-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/events/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/tree/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/agent/new/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/config/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/files/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/git/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/growth/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/health/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/models/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/pi-settings/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/plugins/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/prompts/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/security/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/export/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/sessions/import/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/sessions/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/skills/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/update/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/version/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/web-auth/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/workspaces/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/login/page-52ef4841cbd199a8.js +0 -1
- package/.next/static/chunks/app/page-b51087831582ee37.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/webpack-c31263fd83d75ce6.js +0 -1
- package/.next/static/css/0cd968b9b62035b5.css +0 -1
- package/.next/static/en03hnPiuro0rnf8QkFEe/_buildManifest.js +0 -1
- /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 {
|
|
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" | "
|
|
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: "
|
|
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 === "
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 }}
|
package/src/components/icons.tsx
CHANGED
|
@@ -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}>
|
package/src/hooks/useGrowth.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
350
|
+
// 选中永远是一个具体轮次;选中的 commit 不在当前列表里(被截断 / 换了会话)时回落最新一轮
|
|
346
351
|
const selected = useMemo(
|
|
347
|
-
() => (
|
|
348
|
-
[
|
|
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 ||
|
|
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,
|
|
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 (
|
|
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
|
-
}, [
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
625
|
+
selectLatest,
|
|
604
626
|
select,
|
|
605
627
|
prev,
|
|
606
628
|
next,
|