@rexvane/piweb 0.3.16 → 0.3.17
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 -11
- package/.next/build-manifest.json +3 -3
- package/.next/prerender-manifest.json +6 -6
- package/.next/react-loadable-manifest.json +4 -16
- package/.next/required-server-files.js +1 -3
- package/.next/required-server-files.json +1 -3
- package/.next/routes-manifest.json +0 -20
- 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 +4 -7
- package/.next/server/app/_not-found.segments/_full.segment.rsc +4 -7
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -5
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -5
- package/.next/server/app/api/agent/[id]/events/route.js +2 -2
- 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/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/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 +3 -3
- 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 +2 -2
- 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 +1 -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 +6 -9
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -7
- package/.next/server/app/index.segments/_full.segment.rsc +6 -9
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -5
- 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 +5 -8
- package/.next/server/app/login.segments/_full.segment.rsc +5 -8
- package/.next/server/app/login.segments/_tree.segment.rsc +2 -5
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +3 -6
- package/.next/server/app/page.js +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +8 -11
- package/.next/server/chunks/496.js +1 -1
- package/.next/server/chunks/532.js +1 -1
- package/.next/server/chunks/686.js +4 -8
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-react-loadable-manifest.js +1 -1
- 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/chunks/2608.ec8ff1f5767c9056.js +1 -0
- package/.next/static/chunks/2716.93be8ad5c970c516.js +1 -0
- package/.next/static/chunks/4632.112d2e893d48d188.js +1 -0
- package/.next/static/chunks/4673.c1fb1580efc6ace7.js +1 -0
- package/.next/static/chunks/4686-f4a037d609cea3f2.js +1 -0
- package/.next/static/chunks/app/_global-error/page-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/events/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/tree/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/agent/new/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/config/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/files/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/git/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/growth/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/health/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/models/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/pi-settings/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/plugins/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/prompts/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/security/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/export/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/sessions/import/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/sessions/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/skills/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/update/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/version/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/web-auth/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/workspaces/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/page-b51087831582ee37.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/webpack-c31263fd83d75ce6.js +1 -0
- package/.next/static/css/850c2fd130ffa2fc.css +1 -0
- package/.next/static/zsynuO_9Kx6nrXgkTUJ04/_buildManifest.js +1 -0
- package/.next/types/routes.d.ts +1 -4
- package/.next/types/validator.ts +0 -27
- package/README.md +17 -29
- package/assets/archive.jpg +0 -0
- package/assets/growth-1.jpg +0 -0
- package/assets/growth-2.jpg +0 -0
- package/assets/import.jpg +0 -0
- package/assets/main.jpg +0 -0
- package/assets/outline.jpg +0 -0
- package/assets/prompts-1.jpg +0 -0
- package/assets/prompts-2.jpg +0 -0
- package/assets/providers-1.jpg +0 -0
- package/assets/providers-2.jpg +0 -0
- package/assets/providers-3.jpg +0 -0
- package/assets/trace-1.jpg +0 -0
- package/assets/trace-2.png +0 -0
- package/assets/trace-3.png +0 -0
- package/assets/trace-4.png +0 -0
- package/assets/viewer-1.jpg +0 -0
- package/assets/viewer-2.jpg +0 -0
- package/bin/pi.js +50 -0
- package/next.config.ts +32 -7
- package/package.json +4 -9
- package/scripts/launcher.mjs +1 -1
- package/scripts/pick-folder.ps1 +2 -11
- package/scripts/release.mjs +31 -11
- package/scripts/verify-package.mjs +0 -53
- package/src/app/api/config/route.ts +1 -4
- package/src/app/api/growth/route.ts +14 -14
- package/src/app/api/models/route.ts +2 -1
- package/src/app/api/security/route.ts +3 -8
- package/src/app/api/update/route.ts +2 -8
- package/src/app/globals.css +90 -777
- package/src/app/layout.tsx +6 -13
- package/src/components/AppShell.tsx +229 -521
- package/src/components/ChatInput.tsx +26 -153
- package/src/components/ChatWindow.tsx +231 -51
- package/src/components/ContextMeter.tsx +17 -11
- package/src/components/FileViewer.tsx +11 -10
- package/src/components/GitPanel.tsx +11 -13
- package/src/components/ModelSelector.tsx +6 -19
- package/src/components/PiMark.tsx +38 -5
- package/src/components/ProjectPanel.tsx +111 -95
- package/src/components/PromptPanel.tsx +51 -132
- package/src/components/SessionImportSection.tsx +37 -53
- package/src/components/SessionSidebar.tsx +57 -159
- package/src/components/SettingsPanel.tsx +193 -330
- package/src/components/icons.tsx +0 -23
- package/src/hooks/useGrowth.ts +123 -156
- package/src/hooks/usePiWeb.ts +15 -122
- package/src/i18n.tsx +42 -98
- package/src/lib/agent-manager.ts +21 -187
- package/src/lib/command-validation.ts +1 -9
- package/src/lib/growth-service.ts +390 -166
- package/src/lib/growth-tracker.ts +64 -264
- package/src/lib/growth-turns.ts +31 -1
- package/src/lib/models/model-scope.ts +15 -0
- package/src/lib/models-service.ts +16 -0
- package/src/lib/pi-settings.ts +7 -0
- package/src/lib/pi.ts +2 -3
- package/src/lib/session-import/claude.ts +2 -13
- package/src/lib/session-import/codex.ts +8 -44
- package/src/lib/session-import/files.ts +0 -14
- package/src/lib/session-import/grok.ts +24 -54
- package/src/lib/session-import/index.ts +0 -5
- package/src/lib/session-import/types.ts +0 -12
- package/src/lib/session-import/writer.ts +3 -8
- package/src/lib/skills-service.ts +9 -16
- package/src/lib/theme.ts +1 -68
- package/src/lib/trajectory.ts +0 -1
- package/src/lib/types.ts +27 -36
- package/tsconfig.json +2 -1
- package/.next/server/app/api/swarm/[id]/route.js +0 -1
- package/.next/server/app/api/swarm/[id]/route.js.nft.json +0 -1
- package/.next/server/app/api/swarm/[id]/route_client-reference-manifest.js +0 -1
- package/.next/server/app/api/swarm/route.js +0 -1
- package/.next/server/app/api/swarm/route.js.nft.json +0 -1
- package/.next/server/app/api/swarm/route_client-reference-manifest.js +0 -1
- package/.next/server/app/api/tests/route.js +0 -2
- package/.next/server/app/api/tests/route.js.nft.json +0 -1
- package/.next/server/app/api/tests/route_client-reference-manifest.js +0 -1
- package/.next/server/chunks/757.js +0 -1
- package/.next/server/chunks/775.js +0 -3
- package/.next/static/0t1jBUAuVaTFTXxRyIo3q/_buildManifest.js +0 -1
- package/.next/static/chunks/2608.43c58236c9cce8e6.js +0 -1
- package/.next/static/chunks/2716.4ec42df1d51354c9.js +0 -1
- package/.next/static/chunks/4632.62139b565b657aac.js +0 -1
- package/.next/static/chunks/4673.7796104395deb8d7.js +0 -1
- package/.next/static/chunks/4686-17f21a006739a596.js +0 -1
- package/.next/static/chunks/7577.10af9bb661652da8.js +0 -1
- package/.next/static/chunks/8866.38ec4ac4ee5a1b74.js +0 -1
- package/.next/static/chunks/app/_global-error/page-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/events/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/tree/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/agent/new/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/config/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/files/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/git/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/growth/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/health/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/models/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/pi-settings/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/plugins/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/prompts/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/security/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/export/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/sessions/import/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/sessions/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/skills/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/swarm/[id]/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/swarm/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/tests/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/update/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/version/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/web-auth/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/api/workspaces/route-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/app/page-14379b8060d5cf07.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-5148173b3f55e415.js +0 -1
- package/.next/static/chunks/webpack-d1a69ad9edb38b11.js +0 -1
- package/.next/static/css/d0a0162cb481abe8.css +0 -1
- package/.next/types/app/api/swarm/[id]/route.ts +0 -353
- package/.next/types/app/api/swarm/route.ts +0 -353
- package/.next/types/app/api/tests/route.ts +0 -353
- package/assets/archive.png +0 -0
- package/assets/growth-1.png +0 -0
- package/assets/growth-2.png +0 -0
- package/assets/import.png +0 -0
- package/assets/main.png +0 -0
- package/assets/outline.png +0 -0
- package/assets/prompts-1.png +0 -0
- package/assets/prompts-2.png +0 -0
- package/assets/providers-1.png +0 -0
- package/assets/providers-2.png +0 -0
- package/assets/providers-3.png +0 -0
- package/assets/trace-1.png +0 -0
- package/assets/viewer-1.png +0 -0
- package/assets/viewer-2.png +0 -0
- package/scripts/managed-niubash-assets.mjs +0 -146
- package/scripts/prepare-niubash-packages.mjs +0 -182
- package/scripts/run-test-file.mjs +0 -34
- package/scripts/smoke-managed-niubash.mjs +0 -62
- package/scripts/verify-release-config.mjs +0 -24
- package/src/app/api/swarm/[id]/route.ts +0 -28
- package/src/app/api/swarm/route.ts +0 -24
- package/src/app/api/tests/route.ts +0 -27
- package/src/components/SwarmCoordinatorPod.tsx +0 -1021
- package/src/components/TestInspectorPod.tsx +0 -549
- package/src/lib/delegation.ts +0 -93
- package/src/lib/growth-rounds.ts +0 -84
- package/src/lib/managed-niubash.ts +0 -231
- package/src/lib/runtime-activity.ts +0 -27
- package/src/lib/session-import/lock.ts +0 -25
- package/src/lib/swarm-service.ts +0 -371
- package/src/lib/test-run-service.ts +0 -122
- package/src/lib/workflow.ts +0 -55
- /package/.next/static/{0t1jBUAuVaTFTXxRyIo3q → zsynuO_9Kx6nrXgkTUJ04}/_ssgManifest.js +0 -0
|
@@ -21,9 +21,6 @@ export function ModelSelector({
|
|
|
21
21
|
thinkingLevel,
|
|
22
22
|
thinkingLevels,
|
|
23
23
|
models,
|
|
24
|
-
modelLoading = false,
|
|
25
|
-
modelLoadError,
|
|
26
|
-
onRetryModels,
|
|
27
24
|
providerNames,
|
|
28
25
|
authByProvider,
|
|
29
26
|
onSelectModel,
|
|
@@ -35,9 +32,6 @@ export function ModelSelector({
|
|
|
35
32
|
thinkingLevel?: string;
|
|
36
33
|
thinkingLevels: string[];
|
|
37
34
|
models: ModelChoice[];
|
|
38
|
-
modelLoading?: boolean;
|
|
39
|
-
modelLoadError?: string | null;
|
|
40
|
-
onRetryModels?: () => void;
|
|
41
35
|
providerNames: Record<string, string>;
|
|
42
36
|
authByProvider: Record<string, boolean>;
|
|
43
37
|
onSelectModel: (provider: string, id: string) => void;
|
|
@@ -108,8 +102,7 @@ export function ModelSelector({
|
|
|
108
102
|
});
|
|
109
103
|
}, [open, pane]);
|
|
110
104
|
|
|
111
|
-
|
|
112
|
-
const hasModel = Boolean(model?.id && !(model.provider === "unknown" && model.id === "unknown"));
|
|
105
|
+
const hasModel = Boolean(model?.id);
|
|
113
106
|
const modelName = hasModel ? (model?.name || model?.id || "") : "";
|
|
114
107
|
const effortLabel = hasModel && thinkingLevel && thinkingLevel !== "off" ? thinkingLevel : undefined;
|
|
115
108
|
const triggerTitle = hasModel
|
|
@@ -117,10 +110,10 @@ export function ModelSelector({
|
|
|
117
110
|
: t.selectModel;
|
|
118
111
|
|
|
119
112
|
return (
|
|
120
|
-
<div ref={ref} className="
|
|
113
|
+
<div ref={ref} className="relative">
|
|
121
114
|
<button
|
|
122
115
|
type="button"
|
|
123
|
-
className="chip
|
|
116
|
+
className="chip"
|
|
124
117
|
data-open={open}
|
|
125
118
|
title={triggerTitle}
|
|
126
119
|
aria-label={triggerTitle}
|
|
@@ -131,7 +124,7 @@ export function ModelSelector({
|
|
|
131
124
|
setPane("model");
|
|
132
125
|
}}
|
|
133
126
|
>
|
|
134
|
-
<span className="
|
|
127
|
+
<span className="max-w-[220px] truncate" suppressHydrationWarning>{hasModel ? modelName : t.selectModel}</span>
|
|
135
128
|
{effortLabel && (
|
|
136
129
|
<span style={{ color: "var(--dsw-label-caption)", flex: "none", fontSize: 13 }}>
|
|
137
130
|
{effortLabel}
|
|
@@ -205,15 +198,9 @@ export function ModelSelector({
|
|
|
205
198
|
{pane === "model" && (
|
|
206
199
|
<div className="flex min-h-0 flex-col">
|
|
207
200
|
<div ref={listRef} className="max-h-[340px] overflow-y-auto py-1">
|
|
208
|
-
{
|
|
209
|
-
<div role="alert" className="px-3 py-2" style={{ fontSize: 12, color: "var(--dsw-danger)" }}>
|
|
210
|
-
<span title={modelLoadError}>{t.modelsLoadFailed}</span>{" "}
|
|
211
|
-
{onRetryModels && <button type="button" className="underline" onClick={onRetryModels}>{t.settingsRetry}</button>}
|
|
212
|
-
</div>
|
|
213
|
-
)}
|
|
214
|
-
{groups.length === 0 && (modelLoading || !modelLoadError) && (
|
|
201
|
+
{groups.length === 0 && (
|
|
215
202
|
<div className="px-3 py-3" style={{ fontSize: 13, color: "var(--dsw-label-caption)" }}>
|
|
216
|
-
{
|
|
203
|
+
{t.emptyModels}
|
|
217
204
|
</div>
|
|
218
205
|
)}
|
|
219
206
|
{groups.map((g) => (
|
|
@@ -34,13 +34,46 @@ export function PiMark({
|
|
|
34
34
|
/** 品牌簇:对齐 dsh 品牌模板 [Logo] [Name] [BADGE] */
|
|
35
35
|
export function BrandPi({ compact = false }: { compact?: boolean }) {
|
|
36
36
|
if (compact) {
|
|
37
|
-
return <PiMark size={18}
|
|
37
|
+
return <PiMark size={18} />;
|
|
38
38
|
}
|
|
39
39
|
return (
|
|
40
|
-
<div
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
<div
|
|
41
|
+
className="inline-flex items-center select-none"
|
|
42
|
+
style={{
|
|
43
|
+
color: "var(--dsw-label-primary)",
|
|
44
|
+
gap: 6,
|
|
45
|
+
}}
|
|
46
|
+
>
|
|
47
|
+
<PiMark size={17} />
|
|
48
|
+
<span
|
|
49
|
+
className="inline-flex items-center"
|
|
50
|
+
style={{
|
|
51
|
+
fontSize: 16.5,
|
|
52
|
+
fontWeight: 600,
|
|
53
|
+
letterSpacing: "-0.02em",
|
|
54
|
+
color: "var(--dsw-label-primary)",
|
|
55
|
+
lineHeight: 1,
|
|
56
|
+
}}
|
|
57
|
+
>
|
|
58
|
+
pi
|
|
59
|
+
</span>
|
|
60
|
+
<span
|
|
61
|
+
className="inline-flex items-center justify-center font-bold"
|
|
62
|
+
style={{
|
|
63
|
+
fontSize: 9.5,
|
|
64
|
+
letterSpacing: "0.06em",
|
|
65
|
+
height: 15,
|
|
66
|
+
paddingLeft: 4.5,
|
|
67
|
+
paddingRight: 4.5,
|
|
68
|
+
borderRadius: 4,
|
|
69
|
+
background: "var(--dsw-label-primary)",
|
|
70
|
+
color: "var(--dsw-bg-base)",
|
|
71
|
+
lineHeight: 1,
|
|
72
|
+
textTransform: "uppercase",
|
|
73
|
+
}}
|
|
74
|
+
>
|
|
75
|
+
HARNESS
|
|
76
|
+
</span>
|
|
44
77
|
</div>
|
|
45
78
|
);
|
|
46
79
|
}
|
|
@@ -1,40 +1,54 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
* 头部:工作区名 +
|
|
6
|
-
*
|
|
4
|
+
* 项目栏(侧栏与对话之间的第四列):项目生长 = 每轮一个 git commit。
|
|
5
|
+
* 头部:工作区名 + 立即记录 / 收起;所选轮:提问首行、时间、文件数与行数、在对话里看;
|
|
6
|
+
* 工具行:范围(本轮 / 本会话累计)、只看改动、筛选;中间:项目树(GrowthTree);
|
|
7
|
+
* 底部:时间轴每轮一根柱(高度 = 改动行数,灰色细柱 = 你在两轮之间自己的修改),默认跟随最新一轮。
|
|
7
8
|
*/
|
|
8
9
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
9
10
|
import { GrowthTree } from "@/components/GrowthTree";
|
|
10
11
|
import { IconChevronLeft14, IconChevronRight14, IconCloseOutline14, IconGitOutline16, IconRefreshOutline14, IconSearchOutline16 } from "@/components/icons";
|
|
11
|
-
import type { GrowthApi } from "@/hooks/useGrowth";
|
|
12
|
+
import type { GrowthApi, GrowthEntry } from "@/hooks/useGrowth";
|
|
12
13
|
import { useI18n } from "@/i18n";
|
|
13
14
|
import type { GitInfo } from "@/lib/git-service";
|
|
14
15
|
import type { TreeNode } from "@/lib/growth-tree";
|
|
15
|
-
import type {
|
|
16
|
+
import type { GrowthChange } from "@/lib/types";
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
export function
|
|
19
|
-
if (
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if (step.kind === "manual") return step.label || t.growthKindManual;
|
|
23
|
-
return step.label || step.toolName || "";
|
|
18
|
+
/** 「第 7 轮 · 把保存按钮改成红色」/「你的修改」 */
|
|
19
|
+
export function roundTitle(round: GrowthEntry, t: Record<string, string>): string {
|
|
20
|
+
if (round.kind !== "round") return t.growthKindUser;
|
|
21
|
+
const label = t.growthRoundLabel.replace("{n}", String(round.n ?? ""));
|
|
22
|
+
return round.title ? `${label} · ${round.title}` : label;
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (
|
|
29
|
-
const st =
|
|
25
|
+
/** 柱的颜色:你的修改为灰;pi 的一轮按主要改动类型(删除为主红、新增为主绿、否则黄) */
|
|
26
|
+
function roundColor(round: GrowthEntry): string {
|
|
27
|
+
if (round.kind !== "round") return "var(--dsw-label-caption)";
|
|
28
|
+
const st = round.stats;
|
|
29
|
+
if (!st.added && !st.modified && !st.deleted && !st.renamed) return "var(--dsw-border-l3)";
|
|
30
30
|
if (st.deleted && st.deleted >= st.added && st.deleted >= st.modified) return "var(--dsw-danger)";
|
|
31
31
|
if (st.added >= st.modified) return "var(--dsw-success)";
|
|
32
32
|
return "var(--dsw-warn)";
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
function changedLines(round: GrowthEntry): number {
|
|
36
|
+
return round.stats.add + round.stats.del;
|
|
37
|
+
}
|
|
38
|
+
|
|
35
39
|
function fmtTime(ts: number): string {
|
|
36
40
|
const d = new Date(ts);
|
|
37
|
-
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}
|
|
41
|
+
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function sumChanges(changes: GrowthChange[]): { files: number; add: number; del: number } {
|
|
45
|
+
let add = 0;
|
|
46
|
+
let del = 0;
|
|
47
|
+
for (const c of changes) {
|
|
48
|
+
add += c.add ?? 0;
|
|
49
|
+
del += c.del ?? 0;
|
|
50
|
+
}
|
|
51
|
+
return { files: changes.length, add, del };
|
|
38
52
|
}
|
|
39
53
|
|
|
40
54
|
export function ProjectPanel({
|
|
@@ -42,9 +56,11 @@ export function ProjectPanel({
|
|
|
42
56
|
workspaceName,
|
|
43
57
|
cwd,
|
|
44
58
|
hasSession,
|
|
59
|
+
running = false,
|
|
45
60
|
onOpenFile,
|
|
46
61
|
onReference,
|
|
47
62
|
onOpenEditor,
|
|
63
|
+
onJumpToChat,
|
|
48
64
|
gitRefreshKey = 0,
|
|
49
65
|
onAskCommit,
|
|
50
66
|
onOpenGit,
|
|
@@ -55,9 +71,13 @@ export function ProjectPanel({
|
|
|
55
71
|
workspaceName: string;
|
|
56
72
|
cwd: string;
|
|
57
73
|
hasSession: boolean;
|
|
74
|
+
/** 本会话正在运行:时间轴末尾显示「本轮进行中」 */
|
|
75
|
+
running?: boolean;
|
|
58
76
|
onOpenFile: (node: TreeNode) => void;
|
|
59
77
|
onReference?: (path: string) => void;
|
|
60
78
|
onOpenEditor?: (path: string) => void;
|
|
79
|
+
/** 跳到对话里这一轮的提问(用户消息 entryId) */
|
|
80
|
+
onJumpToChat?: (messageId: string) => void;
|
|
61
81
|
gitRefreshKey?: number;
|
|
62
82
|
onAskCommit?: () => void;
|
|
63
83
|
onOpenGit?: () => void;
|
|
@@ -67,14 +87,15 @@ export function ProjectPanel({
|
|
|
67
87
|
const { t } = useI18n();
|
|
68
88
|
const tt = t as unknown as Record<string, string>;
|
|
69
89
|
const [filter, setFilter] = useState("");
|
|
70
|
-
const [onlyChanges, setOnlyChanges] = useState(
|
|
71
|
-
const [
|
|
90
|
+
const [onlyChanges, setOnlyChanges] = useState(true);
|
|
91
|
+
const [recording, setRecording] = useState(false);
|
|
72
92
|
const [notice, setNotice] = useState("");
|
|
73
93
|
const [gitState, setGitState] = useState<{ cwd: string; info: GitInfo | null; error?: string } | null>(null);
|
|
74
94
|
const git = gitState?.cwd === cwd ? gitState.info : null;
|
|
75
95
|
const gitError = gitState?.cwd === cwd ? gitState.error ?? git?.statusError : undefined;
|
|
76
|
-
useEffect(() => setOnlyChanges(
|
|
77
|
-
const
|
|
96
|
+
useEffect(() => setOnlyChanges(true), [cwd]);
|
|
97
|
+
const { rounds, selected } = growth;
|
|
98
|
+
const latestCommit = rounds[rounds.length - 1]?.commit ?? "";
|
|
78
99
|
useEffect(() => {
|
|
79
100
|
if (!cwd) return;
|
|
80
101
|
const controller = new AbortController();
|
|
@@ -93,28 +114,17 @@ export function ProjectPanel({
|
|
|
93
114
|
clearTimeout(timer);
|
|
94
115
|
controller.abort();
|
|
95
116
|
};
|
|
96
|
-
}, [cwd, gitRefreshKey,
|
|
97
|
-
|
|
98
|
-
const totals = useMemo(() => {
|
|
99
|
-
const out = { added: 0, modified: 0, deleted: 0, renamed: 0 };
|
|
100
|
-
for (const c of growth.scope === "step" ? growth.changes : growth.sessionChanges) {
|
|
101
|
-
if (c.status === "A") out.added += 1;
|
|
102
|
-
else if (c.status === "M") out.modified += 1;
|
|
103
|
-
else if (c.status === "D") out.deleted += 1;
|
|
104
|
-
else out.renamed += 1;
|
|
105
|
-
}
|
|
106
|
-
return out;
|
|
107
|
-
}, [growth.scope, growth.changes, growth.sessionChanges]);
|
|
117
|
+
}, [cwd, gitRefreshKey, latestCommit]);
|
|
108
118
|
|
|
109
|
-
const
|
|
119
|
+
const totals = useMemo(() => sumChanges(growth.changes), [growth.changes]);
|
|
120
|
+
const piRounds = useMemo(() => rounds.filter((round) => round.kind === "round").length, [rounds]);
|
|
121
|
+
const maxLines = useMemo(() => Math.max(1, ...rounds.map(changedLines)), [rounds]);
|
|
110
122
|
const railRef = useRef<HTMLDivElement>(null);
|
|
111
|
-
const maxSteps = useMemo(() => Math.max(1, ...rounds.map((r) => r.steps.filter((step) => step.kind !== "turn").length)), [rounds]);
|
|
112
123
|
useEffect(() => {
|
|
113
|
-
if (!
|
|
114
|
-
railRef.current?.querySelector<HTMLElement>(`[data-
|
|
115
|
-
}, [
|
|
116
|
-
const selectedIdx =
|
|
117
|
-
const roundLabel = selectedRound ? !selectedRound.recorded ? t.growthUnrecorded : selectedRound.steps.every((step) => step.kind === "turn") ? t.growthNoChangesRound : stepTitle(selectedRound.steps.find((step) => step.kind !== "turn") ?? selectedRound.last!, tt) : "";
|
|
124
|
+
if (!selected) return;
|
|
125
|
+
railRef.current?.querySelector<HTMLElement>(`[data-commit="${selected.commit}"]`)?.scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
126
|
+
}, [selected]);
|
|
127
|
+
const selectedIdx = selected ? rounds.findIndex((round) => round.commit === selected.commit) : -1;
|
|
118
128
|
const conflicts = git?.files.filter((file) => file.kind === "conflict").length ?? 0;
|
|
119
129
|
const staged = git?.files.filter((file) => file.kind !== "conflict" && file.indexStatus !== " ").length ?? 0;
|
|
120
130
|
const unstaged = git?.files.filter((file) => file.kind !== "conflict" && file.workStatus !== " " && file.kind !== "untracked").length ?? 0;
|
|
@@ -126,24 +136,21 @@ export function ProjectPanel({
|
|
|
126
136
|
return () => clearTimeout(timer);
|
|
127
137
|
}, [notice]);
|
|
128
138
|
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const snapshotNow = async () => {
|
|
134
|
-
if (snapshotting) return;
|
|
135
|
-
setSnapshotting(true);
|
|
139
|
+
const recordNow = async () => {
|
|
140
|
+
if (recording) return;
|
|
141
|
+
setRecording(true);
|
|
136
142
|
try {
|
|
137
|
-
const
|
|
138
|
-
if (!
|
|
143
|
+
const round = await growth.recordNow();
|
|
144
|
+
if (!round) setNotice(t.growthRecordNone);
|
|
139
145
|
} catch (e) {
|
|
140
146
|
onError(e instanceof Error ? e.message : "failed");
|
|
141
147
|
} finally {
|
|
142
|
-
|
|
148
|
+
setRecording(false);
|
|
143
149
|
}
|
|
144
150
|
};
|
|
145
151
|
|
|
146
|
-
const emptyText = !cwd || !hasSession ? t.growthNoSession : growth.loading && !
|
|
152
|
+
const emptyText = !cwd || !hasSession ? t.growthNoSession : growth.loading && !rounds.length ? t.growthLoading : !growth.available ? t.growthUnavailable : rounds.length ? undefined : t.growthNoRounds;
|
|
153
|
+
const statusText = selected?.status === "aborted" ? t.growthStatusAborted : selected?.status === "error" ? t.growthStatusError : "";
|
|
147
154
|
|
|
148
155
|
return (
|
|
149
156
|
<div className="pw-project flex h-full min-h-0 flex-col" data-testid="project-panel">
|
|
@@ -153,15 +160,15 @@ export function ProjectPanel({
|
|
|
153
160
|
{workspaceName || t.projectPanel}
|
|
154
161
|
</span>
|
|
155
162
|
{hasSession && (
|
|
156
|
-
<button className="icon-btn" style={{ width: 22, height: 22 }} title={t.
|
|
157
|
-
<IconRefreshOutline14 size={13} className={
|
|
163
|
+
<button className="icon-btn" style={{ width: 22, height: 22 }} title={t.growthRecordNow} onClick={() => void recordNow()} disabled={recording}>
|
|
164
|
+
<IconRefreshOutline14 size={13} className={recording ? "piweb-spin" : undefined} />
|
|
158
165
|
</button>
|
|
159
166
|
)}
|
|
160
167
|
<button className="icon-btn" style={{ width: 22, height: 22 }} title={t.close} onClick={onClose}>
|
|
161
168
|
<IconCloseOutline14 size={13} />
|
|
162
169
|
</button>
|
|
163
170
|
</div>
|
|
164
|
-
{/*
|
|
171
|
+
{/* 生长记录不可用(找不到 git 等):明确提示 + 已退化为当前目录结构 */}
|
|
165
172
|
{cwd && hasSession && !growth.available && !growth.loading && (
|
|
166
173
|
<div className="mx-3 mb-2 rounded-lg px-2 py-1" style={{ fontSize: 11.5, color: "var(--dsw-warn)", background: "var(--dsw-hover)" }} role="status">
|
|
167
174
|
{t.growthUnavailableFallback}
|
|
@@ -190,30 +197,39 @@ export function ProjectPanel({
|
|
|
190
197
|
{onOpenGit && <button type="button" className="pw-chip" onClick={onOpenGit}>{t.gitDetails}</button>}
|
|
191
198
|
</div>
|
|
192
199
|
)}
|
|
193
|
-
{/*
|
|
200
|
+
{/* 所选这一轮:提问首行 / 时间 · 文件数 · 行数 / 在对话里看 */}
|
|
201
|
+
{selected && (
|
|
202
|
+
<div className="px-3 pb-2" data-testid="growth-round-summary">
|
|
203
|
+
<div className="truncate" style={{ fontSize: 12.5, fontWeight: 600, color: "var(--dsw-label-primary)" }} title={roundTitle(selected, tt)}>
|
|
204
|
+
{roundTitle(selected, tt)}
|
|
205
|
+
</div>
|
|
206
|
+
<div className="mt-0.5 flex items-center gap-2" style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }}>
|
|
207
|
+
<span>{fmtTime(selected.ts)}</span>
|
|
208
|
+
<span>·</span>
|
|
209
|
+
<span>{t.growthRoundFiles.replace("{n}", String(totals.files))}</span>
|
|
210
|
+
<span style={{ color: "var(--dsw-success)" }}>+{totals.add}</span>
|
|
211
|
+
<span style={{ color: "var(--dsw-danger)" }}>−{totals.del}</span>
|
|
212
|
+
{statusText && <span style={{ color: "var(--dsw-warn)" }}>{statusText}</span>}
|
|
213
|
+
<span className="flex-1" />
|
|
214
|
+
{onJumpToChat && selected.promptIds[0] && (
|
|
215
|
+
<button type="button" className="pw-chip" onClick={() => onJumpToChat(selected.promptIds[0])}>
|
|
216
|
+
{t.growthJumpToChat}
|
|
217
|
+
</button>
|
|
218
|
+
)}
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
)}
|
|
222
|
+
{/* 范围 + 只看改动 */}
|
|
194
223
|
<div className="flex items-center gap-2 px-3 pb-2" style={{ fontSize: 11.5 }}>
|
|
195
224
|
<span className="pw-seg" role="radiogroup">
|
|
196
|
-
{(["
|
|
225
|
+
{(["round", "session"] as const).map((s) => (
|
|
197
226
|
<button key={s} type="button" role="radio" aria-checked={growth.scope === s} data-active={growth.scope === s} onClick={() => growth.setScope(s)}>
|
|
198
|
-
{s === "
|
|
227
|
+
{s === "round" ? t.growthScopeRound : t.growthScopeSession}
|
|
199
228
|
</button>
|
|
200
229
|
))}
|
|
201
230
|
</span>
|
|
202
|
-
<span className="min-w-0 flex-1 truncate" style={{ color: "var(--dsw-label-
|
|
203
|
-
|
|
204
|
-
<span style={{ color: "var(--dsw-label-secondary)" }}>{notice}</span>
|
|
205
|
-
) : (
|
|
206
|
-
<>
|
|
207
|
-
<span style={{ color: "var(--dsw-success)" }}>+{totals.added}</span>
|
|
208
|
-
{" "}
|
|
209
|
-
<span style={{ color: "var(--dsw-warn)" }}>~{totals.modified}</span>
|
|
210
|
-
{" "}
|
|
211
|
-
<span style={{ color: "var(--dsw-danger)" }}>−{totals.deleted}</span>
|
|
212
|
-
{totals.renamed ? <span style={{ color: "var(--dsw-accent)" }}> ⇄{totals.renamed}</span> : null}
|
|
213
|
-
</>
|
|
214
|
-
)}
|
|
215
|
-
</span>
|
|
216
|
-
<button type="button" className="pw-chip" data-active={onlyChanges} aria-pressed={onlyChanges} onClick={toggleOnly}>
|
|
231
|
+
<span className="min-w-0 flex-1 truncate" style={{ color: "var(--dsw-label-secondary)" }}>{notice}</span>
|
|
232
|
+
<button type="button" className="pw-chip" data-active={onlyChanges} aria-pressed={onlyChanges} onClick={() => setOnlyChanges((value) => !value)}>
|
|
217
233
|
{t.growthOnlyChanges}
|
|
218
234
|
</button>
|
|
219
235
|
</div>
|
|
@@ -223,7 +239,6 @@ export function ProjectPanel({
|
|
|
223
239
|
<input
|
|
224
240
|
value={filter}
|
|
225
241
|
onChange={(e) => setFilter(e.target.value)}
|
|
226
|
-
aria-label={t.growthFilter}
|
|
227
242
|
placeholder={t.growthFilter}
|
|
228
243
|
className="min-w-0 flex-1 bg-transparent"
|
|
229
244
|
style={{ fontSize: 12 }}
|
|
@@ -242,14 +257,14 @@ export function ProjectPanel({
|
|
|
242
257
|
{growth.error}
|
|
243
258
|
</div>
|
|
244
259
|
)}
|
|
245
|
-
{
|
|
260
|
+
{selected?.truncated && growth.scope === "round" && (
|
|
246
261
|
<div className="mx-3 mb-1 rounded-lg px-2 py-1" style={{ fontSize: 11, color: "var(--dsw-warn)", background: "var(--dsw-hover)" }}>
|
|
247
262
|
{t.growthTruncated}
|
|
248
263
|
</div>
|
|
249
264
|
)}
|
|
250
|
-
{
|
|
265
|
+
{selected && growth.scope === "round" && !growth.changes.length && !emptyText && (
|
|
251
266
|
<div className="mx-3 mb-1 rounded-lg px-2 py-1" style={{ fontSize: 11, color: "var(--dsw-label-caption)", background: "var(--dsw-hover)" }} role="status">
|
|
252
|
-
{t.
|
|
267
|
+
{t.growthNoChangesRound}
|
|
253
268
|
</div>
|
|
254
269
|
)}
|
|
255
270
|
{/* 树 */}
|
|
@@ -258,7 +273,7 @@ export function ProjectPanel({
|
|
|
258
273
|
expanded={growth.expanded}
|
|
259
274
|
fresh={growth.fresh}
|
|
260
275
|
filter={filter}
|
|
261
|
-
onlyChanges={onlyChanges &&
|
|
276
|
+
onlyChanges={onlyChanges && growth.changes.length > 0}
|
|
262
277
|
onToggleDir={growth.toggleDir}
|
|
263
278
|
onExpandLazy={growth.expandLazy}
|
|
264
279
|
onOpenFile={onOpenFile}
|
|
@@ -266,29 +281,30 @@ export function ProjectPanel({
|
|
|
266
281
|
onOpenEditor={onOpenEditor}
|
|
267
282
|
emptyText={emptyText}
|
|
268
283
|
/>
|
|
269
|
-
{/*
|
|
284
|
+
{/* 时间轴 */}
|
|
270
285
|
{hasSession && (
|
|
271
286
|
<div className="pw-timeline hairline-t" data-testid="growth-timeline">
|
|
272
|
-
{
|
|
273
|
-
{rounds.length > 0 && (
|
|
287
|
+
{(rounds.length > 0 || running) && (
|
|
274
288
|
<div ref={railRef} className="pw-tl-rail">
|
|
275
289
|
{rounds.map((round) => {
|
|
276
|
-
|
|
277
|
-
const
|
|
278
|
-
const
|
|
290
|
+
// 对数缩放:几十行和几千行的轮都看得出来,又不会一根柱子压扁其余
|
|
291
|
+
const lines = changedLines(round);
|
|
292
|
+
const h = round.changes.length || lines ? Math.max(5, Math.round(4 + (Math.log1p(lines) / Math.log1p(maxLines)) * 20)) : 3;
|
|
279
293
|
return (
|
|
280
294
|
<button
|
|
281
|
-
key={round.
|
|
295
|
+
key={round.commit}
|
|
282
296
|
type="button"
|
|
283
297
|
className="pw-tl-bar"
|
|
284
|
-
data-
|
|
285
|
-
data-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
298
|
+
data-commit={round.commit}
|
|
299
|
+
data-kind={round.kind}
|
|
300
|
+
data-active={selected?.commit === round.commit || undefined}
|
|
301
|
+
style={{ height: h, background: roundColor(round) }}
|
|
302
|
+
title={`${roundTitle(round, tt)} · ${fmtTime(round.ts)} · ${t.growthRoundFiles.replace("{n}", String(round.changes.length))} +${round.stats.add} −${round.stats.del}`}
|
|
303
|
+
onClick={() => growth.select(round.commit)}
|
|
289
304
|
/>
|
|
290
305
|
);
|
|
291
306
|
})}
|
|
307
|
+
{running && <span className="pw-tl-bar" data-running="" title={t.growthRunning} aria-label={t.growthRunning} />}
|
|
292
308
|
</div>
|
|
293
309
|
)}
|
|
294
310
|
<div className="flex items-center gap-1 px-2 pb-2 pt-1" style={{ fontSize: 11.5 }}>
|
|
@@ -298,12 +314,12 @@ export function ProjectPanel({
|
|
|
298
314
|
<button className="icon-btn" style={{ width: 20, height: 20 }} title={t.growthNext} onClick={growth.next} disabled={selectedIdx < 0 || selectedIdx >= rounds.length - 1}>
|
|
299
315
|
<IconChevronRight14 size={12} />
|
|
300
316
|
</button>
|
|
301
|
-
<span className="min-w-0 flex-1 truncate" style={{ color: "var(--dsw-label-secondary)" }}
|
|
302
|
-
{
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
317
|
+
<span className="min-w-0 flex-1 truncate" style={{ color: "var(--dsw-label-secondary)" }}>
|
|
318
|
+
{selected?.n
|
|
319
|
+
? t.growthRoundPosition.replace("{current}", String(selected.n)).replace("{total}", String(piRounds))
|
|
320
|
+
: selected
|
|
321
|
+
? t.growthKindUser
|
|
322
|
+
: t.growthRoundPosition.replace("{current}", "0").replace("{total}", String(piRounds))}
|
|
307
323
|
</span>
|
|
308
324
|
<button type="button" className="pw-chip" data-active={growth.following} onClick={growth.follow} title={t.growthFollow}>
|
|
309
325
|
{growth.following ? t.growthFollowing : t.growthFollow}
|