@rexvane/piweb 0.3.15 → 0.3.16
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 +9 -6
- package/.next/build-manifest.json +3 -3
- package/.next/prerender-manifest.json +6 -6
- package/.next/react-loadable-manifest.json +16 -4
- package/.next/routes-manifest.json +20 -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 +7 -4
- package/.next/server/app/_not-found.segments/_full.segment.rsc +7 -4
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +5 -2
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +5 -2
- 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/swarm/[id]/route.js +1 -0
- package/.next/server/app/api/swarm/[id]/route.js.nft.json +1 -0
- package/.next/server/app/api/swarm/[id]/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/swarm/route.js +1 -0
- package/.next/server/app/api/swarm/route.js.nft.json +1 -0
- package/.next/server/app/api/swarm/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/tests/route.js +2 -0
- package/.next/server/app/api/tests/route.js.nft.json +1 -0
- package/.next/server/app/api/tests/route_client-reference-manifest.js +1 -0
- 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 +9 -6
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +7 -4
- package/.next/server/app/index.segments/_full.segment.rsc +9 -6
- package/.next/server/app/index.segments/_tree.segment.rsc +5 -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 +8 -5
- package/.next/server/app/login.segments/_full.segment.rsc +8 -5
- package/.next/server/app/login.segments/_tree.segment.rsc +5 -2
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +6 -3
- 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 +9 -6
- package/.next/server/chunks/496.js +1 -1
- package/.next/server/chunks/532.js +1 -1
- package/.next/server/chunks/686.js +6 -2
- package/.next/server/chunks/757.js +1 -0
- package/.next/server/chunks/775.js +3 -0
- 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/0t1jBUAuVaTFTXxRyIo3q/_buildManifest.js +1 -0
- package/.next/static/chunks/2608.43c58236c9cce8e6.js +1 -0
- package/.next/static/chunks/{2716.a9e808190a4e2424.js → 2716.4ec42df1d51354c9.js} +1 -1
- package/.next/static/chunks/4632.62139b565b657aac.js +1 -0
- package/.next/static/chunks/{4673.d3d0a7f2599080fb.js → 4673.7796104395deb8d7.js} +1 -1
- package/.next/static/chunks/4686-17f21a006739a596.js +1 -0
- package/.next/static/chunks/7577.10af9bb661652da8.js +1 -0
- package/.next/static/chunks/8866.38ec4ac4ee5a1b74.js +1 -0
- package/.next/static/chunks/app/_global-error/page-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/events/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/tree/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/agent/new/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/config/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/files/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/git/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/growth/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/health/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/models/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/pi-settings/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/plugins/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/prompts/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/security/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/export/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/sessions/import/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/sessions/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/skills/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/swarm/[id]/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/swarm/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/tests/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/update/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/version/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/web-auth/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/api/workspaces/route-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/app/page-14379b8060d5cf07.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-5148173b3f55e415.js +1 -0
- package/.next/static/chunks/webpack-d1a69ad9edb38b11.js +1 -0
- package/.next/static/css/d0a0162cb481abe8.css +1 -0
- package/.next/types/app/api/swarm/[id]/route.ts +353 -0
- package/.next/types/app/api/swarm/route.ts +353 -0
- package/.next/types/app/api/tests/route.ts +353 -0
- package/.next/types/routes.d.ts +4 -1
- package/.next/types/validator.ts +27 -0
- package/README.md +10 -0
- package/package.json +4 -3
- package/scripts/pick-folder.ps1 +11 -2
- package/scripts/release.mjs +11 -31
- package/scripts/run-test-file.mjs +34 -0
- package/scripts/verify-release-config.mjs +24 -0
- package/src/app/api/config/route.ts +4 -1
- package/src/app/api/security/route.ts +8 -3
- package/src/app/api/swarm/[id]/route.ts +28 -0
- package/src/app/api/swarm/route.ts +24 -0
- package/src/app/api/tests/route.ts +27 -0
- package/src/app/api/update/route.ts +8 -2
- package/src/app/globals.css +777 -0
- package/src/app/layout.tsx +13 -6
- package/src/components/AppShell.tsx +505 -199
- package/src/components/ChatInput.tsx +153 -26
- package/src/components/ChatWindow.tsx +46 -40
- package/src/components/ContextMeter.tsx +11 -17
- package/src/components/FileViewer.tsx +1 -1
- package/src/components/GitPanel.tsx +13 -11
- package/src/components/ModelSelector.tsx +19 -6
- package/src/components/PiMark.tsx +5 -38
- package/src/components/ProjectPanel.tsx +1 -0
- package/src/components/PromptPanel.tsx +132 -51
- package/src/components/SessionImportSection.tsx +17 -17
- package/src/components/SessionSidebar.tsx +159 -57
- package/src/components/SettingsPanel.tsx +330 -193
- package/src/components/SwarmCoordinatorPod.tsx +1021 -0
- package/src/components/TestInspectorPod.tsx +549 -0
- package/src/components/icons.tsx +23 -0
- package/src/hooks/usePiWeb.ts +112 -8
- package/src/i18n.tsx +59 -3
- package/src/lib/agent-manager.ts +180 -12
- package/src/lib/command-validation.ts +9 -1
- package/src/lib/delegation.ts +93 -0
- package/src/lib/managed-niubash.ts +8 -4
- package/src/lib/pi.ts +3 -2
- package/src/lib/runtime-activity.ts +27 -0
- package/src/lib/session-import/index.ts +5 -0
- package/src/lib/session-import/lock.ts +25 -0
- package/src/lib/session-import/writer.ts +8 -3
- package/src/lib/skills-service.ts +16 -9
- package/src/lib/swarm-service.ts +371 -0
- package/src/lib/test-run-service.ts +122 -0
- package/src/lib/theme.ts +68 -1
- package/src/lib/trajectory.ts +1 -0
- package/src/lib/types.ts +14 -0
- package/src/lib/workflow.ts +55 -0
- package/.next/static/DdBwPYoHBKtaJ97QIr6Co/_buildManifest.js +0 -1
- package/.next/static/chunks/2608.ec8ff1f5767c9056.js +0 -1
- package/.next/static/chunks/4632.0ed92ae42905ce40.js +0 -1
- package/.next/static/chunks/4686-7d694def920ff093.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/page-9a5440047c5a415c.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-42c5aae79b0e5d8c.js +0 -1
- package/.next/static/css/b2d35abc828d4713.css +0 -1
- /package/.next/static/{DdBwPYoHBKtaJ97QIr6Co → 0t1jBUAuVaTFTXxRyIo3q}/_ssgManifest.js +0 -0
|
@@ -23,14 +23,21 @@ const samePath = (a: string, b: string): boolean =>
|
|
|
23
23
|
const ProjectPanel = dynamic(() => import("@/components/ProjectPanel").then((m) => m.ProjectPanel), { ssr: false });
|
|
24
24
|
const FileViewer = dynamic(() => import("@/components/FileViewer").then((m) => m.FileViewer), { ssr: false });
|
|
25
25
|
const GitPanel = dynamic(() => import("@/components/GitPanel").then((m) => m.GitPanel), { ssr: false });
|
|
26
|
+
const TestInspectorPod = dynamic(() => import("@/components/TestInspectorPod").then((m) => m.TestInspectorPod), { ssr: false });
|
|
27
|
+
const SwarmCoordinatorPod = dynamic(() => import("@/components/SwarmCoordinatorPod").then((m) => m.SwarmCoordinatorPod), { ssr: false });
|
|
26
28
|
import {
|
|
27
29
|
IconAgentPresetOutline16,
|
|
30
|
+
IconArchiveOutline20,
|
|
31
|
+
IconBranchOutline16,
|
|
28
32
|
IconCheckOutline14,
|
|
29
33
|
IconChevronDown14,
|
|
30
34
|
IconFolderClose16,
|
|
31
35
|
IconFolderOpenOutline16,
|
|
32
36
|
IconPanelLeftOutline16,
|
|
33
37
|
IconProjectAddOutline16,
|
|
38
|
+
IconSettingsOutline16,
|
|
39
|
+
IconShieldOutline16,
|
|
40
|
+
IconWorkflowAgent16,
|
|
34
41
|
} from "@/components/icons";
|
|
35
42
|
import { useI18n } from "@/i18n";
|
|
36
43
|
import { usePiWeb } from "@/hooks/usePiWeb";
|
|
@@ -39,7 +46,7 @@ import { useFileViewer } from "@/hooks/useFileViewer";
|
|
|
39
46
|
import type { TreeNode } from "@/lib/growth-tree";
|
|
40
47
|
import { syncPebrelTheme } from "@/lib/theme";
|
|
41
48
|
import type { ModelChoice } from "@/components/ModelSelector";
|
|
42
|
-
import type { ImageAttachment, TrajEntry } from "@/lib/types";
|
|
49
|
+
import type { ImageAttachment, TrajEntry, WorkflowMode } from "@/lib/types";
|
|
43
50
|
|
|
44
51
|
// dsh ui-layout columns.ts 几何常量
|
|
45
52
|
const SIDEBAR_MIN = 264;
|
|
@@ -72,6 +79,8 @@ export function AppShell() {
|
|
|
72
79
|
currentPath,
|
|
73
80
|
state,
|
|
74
81
|
models,
|
|
82
|
+
modelLoading,
|
|
83
|
+
modelLoadError,
|
|
75
84
|
addedWorkspaces,
|
|
76
85
|
removedWorkspaces,
|
|
77
86
|
workspaceAliases,
|
|
@@ -86,9 +95,11 @@ export function AppShell() {
|
|
|
86
95
|
setGroupBy,
|
|
87
96
|
setOrderBy,
|
|
88
97
|
addWorkspaceByPicker,
|
|
98
|
+
addWorkspaceByPath,
|
|
89
99
|
removeWorkspace,
|
|
90
100
|
refreshModels,
|
|
91
101
|
sendCommand,
|
|
102
|
+
editAndResend,
|
|
92
103
|
newSession,
|
|
93
104
|
openSession,
|
|
94
105
|
closeSession,
|
|
@@ -144,6 +155,8 @@ export function AppShell() {
|
|
|
144
155
|
const [heroCwd, setHeroCwd] = useState("");
|
|
145
156
|
const [heroModel, setHeroModel] = useState<{ provider: string; id: string } | null>(null);
|
|
146
157
|
const [heroThinking, setHeroThinking] = useState("");
|
|
158
|
+
const [heroMode, setHeroMode] = useState<WorkflowMode>("agent");
|
|
159
|
+
const [activeTopView, setActiveTopView] = useState<"workbench" | "diff" | "tests" | "swarm" | "archive">("workbench");
|
|
147
160
|
const [isNarrow, setIsNarrow] = useState(false);
|
|
148
161
|
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
|
|
149
162
|
useEffect(() => {
|
|
@@ -357,15 +370,9 @@ export function AppShell() {
|
|
|
357
370
|
const doEditMessage = useCallback(
|
|
358
371
|
async (entryId: string, text: string) => {
|
|
359
372
|
if (!currentId) return;
|
|
360
|
-
|
|
361
|
-
if (!done?.success) {
|
|
362
|
-
setError(done?.error ?? t.editMessageFailed);
|
|
363
|
-
return;
|
|
364
|
-
}
|
|
365
|
-
// 被编辑消息之后的尾部要从视图里消失(服务端已切断该分支)
|
|
366
|
-
resync();
|
|
373
|
+
await editAndResend(entryId, text);
|
|
367
374
|
},
|
|
368
|
-
[currentId,
|
|
375
|
+
[currentId, editAndResend],
|
|
369
376
|
);
|
|
370
377
|
|
|
371
378
|
const doArchiveSession = useCallback(
|
|
@@ -374,6 +381,7 @@ export function AppShell() {
|
|
|
374
381
|
await archiveSession(path);
|
|
375
382
|
} catch {
|
|
376
383
|
// usePiWeb exposes the request failure in the shared error banner.
|
|
384
|
+
return;
|
|
377
385
|
}
|
|
378
386
|
// 归档后会话立刻离开工作区列表(服务端同时让它收工)。正在看的会话被归档时
|
|
379
387
|
// 必须切走:先取同一工作区的另一个可见会话,没有就回到空态(工作区仍留在侧栏)。
|
|
@@ -432,6 +440,7 @@ export function AppShell() {
|
|
|
432
440
|
provider: effective?.provider,
|
|
433
441
|
modelId: effective?.id,
|
|
434
442
|
thinking: heroThinking || undefined,
|
|
443
|
+
mode: heroMode,
|
|
435
444
|
});
|
|
436
445
|
if (!p) return { success: false };
|
|
437
446
|
// Bind before the new-session render. In-flight ChatInput callbacks still own
|
|
@@ -521,6 +530,8 @@ export function AppShell() {
|
|
|
521
530
|
|
|
522
531
|
// 项目栏/文件查看器的工作区:当前会话的工作区优先,快照未到时回落到会话列表里的 cwd
|
|
523
532
|
const panelCwd = snapshot?.cwd || currentSession?.cwd || heroCwd || knownCwds[0] || "";
|
|
533
|
+
const activeSnapshot = currentPath && snapshot?.sessionPath && samePath(snapshot.sessionPath, currentPath) ? snapshot : null;
|
|
534
|
+
const activeWorkspaceCwd = currentPath ? (activeSnapshot?.cwd || currentSession?.cwd || "") : heroCwd;
|
|
524
535
|
|
|
525
536
|
// Git 面板自动刷新:pi 每完成一个会改动文件的工具,或一轮结束时,静默重拉
|
|
526
537
|
const mutatingDone = useMemo(
|
|
@@ -528,6 +539,8 @@ export function AppShell() {
|
|
|
528
539
|
[state.tools],
|
|
529
540
|
);
|
|
530
541
|
const [panelRefreshKey, setPanelRefreshKey] = useState(0);
|
|
542
|
+
const [gitSummary, setGitSummary] = useState<{ available: boolean; isRepo: boolean; branch: string | null; detached: boolean; files: unknown[]; statusError?: string } | null>(null);
|
|
543
|
+
const [gitSummaryError, setGitSummaryError] = useState(false);
|
|
531
544
|
const prevMutatingRef = useRef(mutatingDone);
|
|
532
545
|
const prevStreamingRef = useRef(isStreaming);
|
|
533
546
|
useEffect(() => {
|
|
@@ -540,6 +553,31 @@ export function AppShell() {
|
|
|
540
553
|
if (prevStreamingRef.current && !isStreaming) setPanelRefreshKey((k) => k + 1);
|
|
541
554
|
prevStreamingRef.current = isStreaming;
|
|
542
555
|
}, [isStreaming]);
|
|
556
|
+
useEffect(() => {
|
|
557
|
+
const controller = new AbortController();
|
|
558
|
+
setGitSummary(null);
|
|
559
|
+
setGitSummaryError(false);
|
|
560
|
+
if (!activeWorkspaceCwd) return () => controller.abort();
|
|
561
|
+
void (async () => {
|
|
562
|
+
try {
|
|
563
|
+
const response = await fetch(`/api/git?cwd=${encodeURIComponent(activeWorkspaceCwd)}`, { signal: controller.signal });
|
|
564
|
+
const body = await response.json();
|
|
565
|
+
if (!response.ok || !body.success) throw new Error(body.error || "Git 状态读取失败");
|
|
566
|
+
if (!controller.signal.aborted) setGitSummary(body.data);
|
|
567
|
+
} catch {
|
|
568
|
+
if (!controller.signal.aborted) setGitSummaryError(true);
|
|
569
|
+
}
|
|
570
|
+
})();
|
|
571
|
+
return () => controller.abort();
|
|
572
|
+
}, [activeWorkspaceCwd, panelRefreshKey]);
|
|
573
|
+
const gitBranchLabel = gitSummary?.isRepo ? (gitSummary.detached ? "HEAD" : gitSummary.branch || "—") : "—";
|
|
574
|
+
const gitStatusLabel = !activeWorkspaceCwd ? "未选择"
|
|
575
|
+
: gitSummaryError || gitSummary?.statusError ? "状态未知"
|
|
576
|
+
: !gitSummary ? "读取中"
|
|
577
|
+
: !gitSummary.available ? "Git 不可用"
|
|
578
|
+
: !gitSummary.isRepo ? "非仓库"
|
|
579
|
+
: gitSummary.files.length ? `${gitSummary.files.length} 改动` : "干净";
|
|
580
|
+
const gitCanCommit = Boolean(gitSummary?.isRepo && !gitSummary.statusError && gitSummary.files.length);
|
|
543
581
|
|
|
544
582
|
const insertIntoComposer = useCallback((text: string) => {
|
|
545
583
|
const textarea = document.querySelector<HTMLTextAreaElement>('[data-testid="composer"] textarea');
|
|
@@ -553,12 +591,12 @@ export function AppShell() {
|
|
|
553
591
|
requestAnimationFrame(() => textarea?.focus());
|
|
554
592
|
}, [draftKey, updateDraft]);
|
|
555
593
|
const openInEditor = useCallback(
|
|
556
|
-
async (filePath: string) => {
|
|
594
|
+
async (filePath: string, cwdOverride?: string) => {
|
|
557
595
|
try {
|
|
558
596
|
const r = await fetch("/api/files", {
|
|
559
597
|
method: "POST",
|
|
560
598
|
headers: { "Content-Type": "application/json" },
|
|
561
|
-
body: JSON.stringify({ action: "open", cwd: panelCwd, path: filePath }),
|
|
599
|
+
body: JSON.stringify({ action: "open", cwd: cwdOverride || panelCwd, path: filePath }),
|
|
562
600
|
});
|
|
563
601
|
const j = await r.json();
|
|
564
602
|
if (!j.success) throw new Error(j.error || "failed to open file");
|
|
@@ -635,28 +673,159 @@ export function AppShell() {
|
|
|
635
673
|
);
|
|
636
674
|
|
|
637
675
|
return (
|
|
638
|
-
<div
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
676
|
+
<div className="flex flex-col h-screen w-screen overflow-hidden bg-clay-bg text-clay-slate-text antialiased">
|
|
677
|
+
{/* 全局粘土顶部导航栏 (Screen 1, 2, 3, 8) */}
|
|
678
|
+
<header className="pw-top-navbar h-14 px-6 flex items-center justify-between border-b border-white/60 bg-white/70 backdrop-blur-md z-30 shrink-0 select-none">
|
|
679
|
+
{/* Left: Engine brand badge + Code Workbench capsule */}
|
|
680
|
+
<div className="flex items-center gap-3 min-w-0">
|
|
681
|
+
<div className="w-9 h-9 clay-badge bg-gradient-to-tr from-emerald-500 to-teal-400 flex items-center justify-center text-white font-black text-lg shadow-sm">
|
|
682
|
+
π
|
|
683
|
+
</div>
|
|
684
|
+
<div className="flex items-baseline gap-2">
|
|
685
|
+
<span className="font-extrabold text-lg tracking-tight text-slate-900 dark:text-slate-100">piweb</span>
|
|
686
|
+
<span className="text-xs font-bold px-2 py-0.5 rounded-full bg-emerald-100 text-emerald-800 dark:bg-emerald-950/70 dark:text-emerald-300 tracking-wide uppercase border border-emerald-300 dark:border-emerald-800">Code Workbench</span>
|
|
687
|
+
</div>
|
|
688
|
+
</div>
|
|
689
|
+
|
|
690
|
+
{/* Center: Session Breadcrumb Status Pill */}
|
|
691
|
+
<div className="hidden lg:flex items-center gap-2 px-3 py-1.5 clay-inset bg-white dark:bg-slate-900 rounded-full text-xs font-medium border border-slate-200 dark:border-slate-800 min-w-0">
|
|
692
|
+
<span className={`w-2.5 h-2.5 rounded-full flex-none ${state.connected ? "bg-emerald-500 animate-pulse" : "bg-slate-400"}`} />
|
|
693
|
+
<span className="font-bold text-slate-800 dark:text-slate-200">PiWeb</span>
|
|
694
|
+
<span className="text-slate-400">/</span>
|
|
695
|
+
<span className="font-semibold text-slate-600 dark:text-slate-400">SESSION</span>
|
|
696
|
+
<span className="text-slate-400">/</span>
|
|
697
|
+
<span className="text-slate-900 dark:text-slate-100 max-w-xs truncate font-bold">
|
|
698
|
+
{currentId ? title : (heroCwd ? (getWorkspaceName ? getWorkspaceName(heroCwd) : basename(heroCwd)) : t.newChat)}
|
|
699
|
+
</span>
|
|
700
|
+
</div>
|
|
701
|
+
|
|
702
|
+
{/* Center/Nav: View Switcher Pill Tabs */}
|
|
703
|
+
<nav aria-label="视图切换" className="clay-inset flex min-w-0 items-center overflow-x-auto p-1 rounded-full bg-slate-100 dark:bg-slate-900 border border-slate-200 dark:border-slate-800 gap-1 text-xs font-semibold">
|
|
704
|
+
<button
|
|
705
|
+
type="button"
|
|
706
|
+
className={`px-3.5 py-1 rounded-full transition-all duration-150 ${
|
|
707
|
+
activeTopView === "workbench"
|
|
708
|
+
? "clay-btn-peach text-white font-bold shadow-sm"
|
|
709
|
+
: "text-slate-700 hover:text-slate-950 dark:text-slate-300 dark:hover:text-white"
|
|
710
|
+
}`}
|
|
711
|
+
onClick={() => setActiveTopView("workbench")}
|
|
712
|
+
>
|
|
713
|
+
工作台
|
|
714
|
+
</button>
|
|
715
|
+
<button
|
|
716
|
+
type="button"
|
|
717
|
+
className={`flex items-center gap-1.5 px-3.5 py-1 rounded-full transition-all duration-150 ${
|
|
718
|
+
activeTopView === "diff"
|
|
719
|
+
? "clay-btn-peach text-white font-bold shadow-sm"
|
|
720
|
+
: "text-slate-700 hover:text-slate-950 dark:text-slate-300 dark:hover:text-white"
|
|
721
|
+
}`}
|
|
722
|
+
onClick={() => { setPanelRefreshKey((key) => key + 1); setActiveTopView("diff"); }}
|
|
723
|
+
>
|
|
724
|
+
<span>差异对比</span>
|
|
725
|
+
<span className={`text-[10px] px-1.5 py-0.2 rounded-full font-bold ${
|
|
726
|
+
activeTopView === "diff" ? "bg-white/30 text-white" : "bg-emerald-100 text-emerald-800 dark:bg-emerald-950/70 dark:text-emerald-300"
|
|
727
|
+
}`}>
|
|
728
|
+
{gitSummary?.isRepo ? gitSummary.files.length : "—"}
|
|
729
|
+
</span>
|
|
730
|
+
</button>
|
|
731
|
+
<button
|
|
732
|
+
type="button"
|
|
733
|
+
className={`shrink-0 px-3.5 py-1 rounded-full transition-all duration-150 ${
|
|
734
|
+
activeTopView === "tests"
|
|
735
|
+
? "clay-btn-peach text-white font-bold shadow-sm"
|
|
736
|
+
: "text-slate-700 hover:text-slate-950 dark:text-slate-300 dark:hover:text-white"
|
|
737
|
+
}`}
|
|
738
|
+
onClick={() => setActiveTopView("tests")}
|
|
739
|
+
>
|
|
740
|
+
测试检查器
|
|
741
|
+
</button>
|
|
742
|
+
<button
|
|
743
|
+
type="button"
|
|
744
|
+
className={`flex items-center gap-1.5 px-3.5 py-1 rounded-full transition-all duration-150 ${
|
|
745
|
+
activeTopView === "swarm"
|
|
746
|
+
? "clay-btn-peach text-white font-bold shadow-sm"
|
|
747
|
+
: "text-slate-700 hover:text-slate-950 dark:text-slate-300 dark:hover:text-white"
|
|
748
|
+
}`}
|
|
749
|
+
onClick={() => setActiveTopView("swarm")}
|
|
750
|
+
>
|
|
751
|
+
<span>智能体群</span>
|
|
752
|
+
</button>
|
|
753
|
+
</nav>
|
|
754
|
+
|
|
755
|
+
{/* Right: Actions */}
|
|
756
|
+
<div className="flex items-center gap-2">
|
|
757
|
+
<button
|
|
758
|
+
type="button"
|
|
759
|
+
className="clay-badge hidden md:flex items-center gap-1.5 px-3 py-1 text-xs font-mono text-slate-800 dark:text-slate-200 bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 shadow-sm font-semibold hover:border-slate-300 transition-all cursor-pointer"
|
|
760
|
+
title="Git 工作区状态(点击进入差异对比)"
|
|
761
|
+
onClick={() => {
|
|
762
|
+
setPanelRefreshKey((key) => key + 1);
|
|
763
|
+
setActiveTopView("diff");
|
|
764
|
+
}}
|
|
765
|
+
>
|
|
766
|
+
<IconBranchOutline16 size={13} className="text-emerald-500" />
|
|
767
|
+
<span>{gitBranchLabel}</span>
|
|
768
|
+
<span className="text-slate-400">·</span>
|
|
769
|
+
<span className="text-amber-600 dark:text-amber-400 font-bold">{gitStatusLabel}</span>
|
|
770
|
+
</button>
|
|
771
|
+
<button
|
|
772
|
+
type="button"
|
|
773
|
+
className="w-8 h-8 clay-btn bg-white dark:bg-slate-800 text-slate-600 dark:text-slate-300 hover:text-slate-900 dark:hover:text-white transition-colors"
|
|
774
|
+
title={t.projectPanel}
|
|
775
|
+
aria-label={t.projectPanel}
|
|
776
|
+
data-testid="project-toggle"
|
|
777
|
+
onClick={() => toggleProject()}
|
|
778
|
+
>
|
|
779
|
+
<IconFolderOpenOutline16 size={15} />
|
|
780
|
+
</button>
|
|
781
|
+
<button
|
|
782
|
+
type="button"
|
|
783
|
+
className="w-8 h-8 clay-btn bg-white dark:bg-slate-800 text-slate-600 dark:text-slate-300 hover:text-slate-900 dark:hover:text-white transition-colors"
|
|
784
|
+
title={t.promptPanel}
|
|
785
|
+
aria-label={t.promptPanel}
|
|
786
|
+
data-testid="prompt-panel-toggle"
|
|
787
|
+
onClick={() => {
|
|
788
|
+
setPromptsOpen((open) => !open);
|
|
789
|
+
setSelected(null);
|
|
790
|
+
setGitDetailsOpen(false);
|
|
791
|
+
}}
|
|
792
|
+
>
|
|
793
|
+
<IconAgentPresetOutline16 size={15} />
|
|
794
|
+
</button>
|
|
795
|
+
<button
|
|
796
|
+
type="button"
|
|
797
|
+
className="w-8 h-8 clay-btn bg-white dark:bg-slate-800 text-slate-600 dark:text-slate-300 hover:text-slate-900 dark:hover:text-white transition-colors"
|
|
798
|
+
title={t.settings}
|
|
799
|
+
aria-label={t.settings}
|
|
800
|
+
data-testid="settings-toggle"
|
|
801
|
+
onClick={() => setSettingsOpen(true)}
|
|
802
|
+
>
|
|
803
|
+
<IconSettingsOutline16 size={15} />
|
|
804
|
+
</button>
|
|
805
|
+
</div>
|
|
806
|
+
</header>
|
|
807
|
+
|
|
808
|
+
{/* 三栏/四栏悬浮粘土卡片主体容器 */}
|
|
809
|
+
<main
|
|
810
|
+
className="pw-shell grid flex-1 min-h-0 overflow-hidden p-3 gap-3 bg-clay-bg"
|
|
811
|
+
style={{
|
|
812
|
+
gridTemplateColumns: gridCols,
|
|
813
|
+
transition: dragging ? "none" : "grid-template-columns var(--ds-duration-slow) var(--ds-ease-in-out)",
|
|
814
|
+
}}
|
|
659
815
|
>
|
|
816
|
+
{/* 侧栏卡片 */}
|
|
817
|
+
<div
|
|
818
|
+
className="pw-sidebar-surface clay-surface rounded-3xl p-3 shrink-0 flex flex-col justify-between overflow-hidden shadow-md"
|
|
819
|
+
style={isNarrow ? {
|
|
820
|
+
position: "fixed",
|
|
821
|
+
inset: "0 auto 0 0",
|
|
822
|
+
width: "min(86vw, 340px)",
|
|
823
|
+
zIndex: 90,
|
|
824
|
+
transform: mobileSidebarOpen ? "translateX(0)" : "translateX(-105%)",
|
|
825
|
+
transition: "transform var(--ds-duration-normal) var(--ds-ease-in-out)",
|
|
826
|
+
boxShadow: mobileSidebarOpen ? "var(--dsw-elevation-prominent)" : "none",
|
|
827
|
+
} : undefined}
|
|
828
|
+
>
|
|
660
829
|
<SessionSidebar
|
|
661
830
|
sessions={sessions}
|
|
662
831
|
sessionListError={sessionListError}
|
|
@@ -701,15 +870,104 @@ export function AppShell() {
|
|
|
701
870
|
}}
|
|
702
871
|
onAddWorkspace={() => void addWorkspaceByPicker()}
|
|
703
872
|
draftCwd={!currentId ? heroCwd || null : null}
|
|
873
|
+
gitBranch={gitBranchLabel}
|
|
874
|
+
gitStatus={gitStatusLabel}
|
|
875
|
+
canAskCommit={gitCanCommit}
|
|
876
|
+
onAskCommit={() => { setActiveTopView("workbench"); insertIntoComposer(t.gitAskCommitPrompt); }}
|
|
877
|
+
activeTopView={activeTopView}
|
|
878
|
+
onSelectTopView={(v) => { setActiveTopView(v); setMobileSidebarOpen(false); }}
|
|
704
879
|
/>
|
|
705
880
|
</div>
|
|
706
881
|
|
|
707
|
-
|
|
708
|
-
|
|
882
|
+
{/* 四列网格中始终保留项目列 */}
|
|
883
|
+
{!isNarrow && (projectColumn ? (
|
|
884
|
+
<div className="clay-surface rounded-3xl p-3 shrink-0 flex flex-col overflow-hidden shadow-md">
|
|
885
|
+
{projectColumn}
|
|
886
|
+
</div>
|
|
887
|
+
) : <div aria-hidden="true" />)}
|
|
709
888
|
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
889
|
+
{/* 会话区与多视图卡片 (Screen 1 & Screen 3) */}
|
|
890
|
+
<section className={`pi-main pw-main flex flex-col clay-card rounded-3xl overflow-hidden min-h-0 min-w-0 relative bg-white shadow-md${currentId ? "" : " pw-main-hero"}`}>
|
|
891
|
+
|
|
892
|
+
|
|
893
|
+
{/* 视图内容切换 */}
|
|
894
|
+
{activeTopView === "diff" ? (
|
|
895
|
+
<div className="flex-1 min-h-0 overflow-hidden p-4 sm:p-6 bg-slate-50/50 dark:bg-slate-950/30">
|
|
896
|
+
<div className="clay-card mx-auto h-full max-w-6xl overflow-hidden rounded-3xl bg-white/85 dark:bg-slate-900/80">
|
|
897
|
+
<GitPanel cwd={activeWorkspaceCwd} refreshKey={panelRefreshKey} onClose={() => setActiveTopView("workbench")} onOpenFile={(filePath) => void openInEditor(filePath, activeWorkspaceCwd)} />
|
|
898
|
+
</div>
|
|
899
|
+
</div>
|
|
900
|
+
) : activeTopView === "tests" ? (
|
|
901
|
+
<div className="flex-1 min-h-0 overflow-y-auto p-4 sm:p-6 bg-slate-50/50 dark:bg-slate-950/30">
|
|
902
|
+
<TestInspectorPod
|
|
903
|
+
cwd={activeWorkspaceCwd}
|
|
904
|
+
onClose={() => setActiveTopView("workbench")}
|
|
905
|
+
onSwitchToWorkbench={() => setActiveTopView("workbench")}
|
|
906
|
+
onSwitchToDiff={() => setActiveTopView("diff")}
|
|
907
|
+
/>
|
|
908
|
+
</div>
|
|
909
|
+
) : activeTopView === "swarm" ? (
|
|
910
|
+
<div className="flex-1 min-h-0 overflow-hidden p-3 sm:p-4 bg-slate-50/50 dark:bg-slate-950/30">
|
|
911
|
+
<SwarmCoordinatorPod
|
|
912
|
+
cwd={activeWorkspaceCwd}
|
|
913
|
+
onClose={() => setActiveTopView("workbench")}
|
|
914
|
+
onSwitchToWorkbench={() => setActiveTopView("workbench")}
|
|
915
|
+
projectTrust={activeSnapshot?.projectTrust}
|
|
916
|
+
onWorkspaceChanged={() => setPanelRefreshKey((key) => key + 1)}
|
|
917
|
+
/>
|
|
918
|
+
</div>
|
|
919
|
+
) : activeTopView === "archive" ? (
|
|
920
|
+
<div className="flex-1 min-h-0 overflow-y-auto p-6 flex flex-col items-center bg-slate-50/40 dark:bg-slate-950/20">
|
|
921
|
+
<div className="clay-card max-w-2xl w-full p-6 rounded-3xl space-y-4">
|
|
922
|
+
<div className="flex items-center justify-between pb-3 border-b border-slate-200/60 dark:border-slate-800/60">
|
|
923
|
+
<h2 className="text-base font-bold text-slate-800 dark:text-slate-100 flex items-center gap-2">
|
|
924
|
+
<IconArchiveOutline20 size={18} className="text-slate-400" />
|
|
925
|
+
已归档会话 ({archivedSessions.length})
|
|
926
|
+
</h2>
|
|
927
|
+
<span className="text-xs text-slate-400">保留历史记录,不占用主工作台</span>
|
|
928
|
+
</div>
|
|
929
|
+
{archivedSessions.length === 0 ? (
|
|
930
|
+
<div className="text-center py-12 text-sm text-slate-400">暂无归档会话</div>
|
|
931
|
+
) : (
|
|
932
|
+
<div className="space-y-2">
|
|
933
|
+
{archivedSessions.map((session) => (
|
|
934
|
+
<div
|
|
935
|
+
key={session.path}
|
|
936
|
+
className="clay-card flex items-center justify-between p-3.5 rounded-2xl hover:scale-[1.01] transition-transform"
|
|
937
|
+
>
|
|
938
|
+
<div className="min-w-0 flex-1">
|
|
939
|
+
<div className="font-semibold text-sm text-slate-800 dark:text-slate-200 truncate">{session.name || session.firstMessage || basename(session.path)}</div>
|
|
940
|
+
<div className="text-xs text-slate-400 truncate mt-0.5">{session.cwd}</div>
|
|
941
|
+
</div>
|
|
942
|
+
<div className="flex items-center gap-2 ml-4">
|
|
943
|
+
<button
|
|
944
|
+
type="button"
|
|
945
|
+
className="clay-btn clay-btn-soft px-3 py-1.5 text-xs text-slate-700 dark:text-slate-200"
|
|
946
|
+
onClick={() => {
|
|
947
|
+
doUnarchiveSession(session.path);
|
|
948
|
+
setActiveTopView("workbench");
|
|
949
|
+
}}
|
|
950
|
+
>
|
|
951
|
+
恢复会话
|
|
952
|
+
</button>
|
|
953
|
+
<button
|
|
954
|
+
type="button"
|
|
955
|
+
className="clay-btn clay-btn-peach px-3 py-1.5 text-xs text-white"
|
|
956
|
+
onClick={() => {
|
|
957
|
+
openSession(session.path);
|
|
958
|
+
setActiveTopView("workbench");
|
|
959
|
+
}}
|
|
960
|
+
>
|
|
961
|
+
查看
|
|
962
|
+
</button>
|
|
963
|
+
</div>
|
|
964
|
+
</div>
|
|
965
|
+
))}
|
|
966
|
+
</div>
|
|
967
|
+
)}
|
|
968
|
+
</div>
|
|
969
|
+
</div>
|
|
970
|
+
) : !currentId ? (
|
|
713
971
|
<div className="flex min-h-0 flex-1 flex-col">
|
|
714
972
|
<Hero
|
|
715
973
|
draft={composerDrafts.current.get(heroDraftKey) ?? EMPTY_CHAT_DRAFT}
|
|
@@ -728,6 +986,9 @@ export function AppShell() {
|
|
|
728
986
|
getWorkspaceName={getWorkspaceName}
|
|
729
987
|
onSend={heroSend}
|
|
730
988
|
models={modelChoices}
|
|
989
|
+
modelLoading={modelLoading}
|
|
990
|
+
modelLoadError={modelLoadError}
|
|
991
|
+
onRetryModels={() => void refreshModels()}
|
|
731
992
|
providerNames={providerNames}
|
|
732
993
|
authByProvider={authByProvider}
|
|
733
994
|
addWorkspaceByPicker={addWorkspaceByPicker}
|
|
@@ -739,8 +1000,11 @@ export function AppShell() {
|
|
|
739
1000
|
}}
|
|
740
1001
|
defaultModel={defaultModel}
|
|
741
1002
|
heroModelLevels={heroModelLevels}
|
|
742
|
-
|
|
743
|
-
|
|
1003
|
+
heroThinking={heroThinking}
|
|
1004
|
+
onSelectHeroThinking={setHeroThinking}
|
|
1005
|
+
heroMode={heroMode}
|
|
1006
|
+
onSelectHeroMode={setHeroMode}
|
|
1007
|
+
onOpenSwarm={() => setActiveTopView("swarm")}
|
|
744
1008
|
/>
|
|
745
1009
|
{/* 草稿阶段的错误行内显示(不再弹右下角) */}
|
|
746
1010
|
{state.error && (
|
|
@@ -758,43 +1022,9 @@ export function AppShell() {
|
|
|
758
1022
|
</div>
|
|
759
1023
|
) : (
|
|
760
1024
|
<>
|
|
761
|
-
{/*
|
|
762
|
-
<div className="hairline-b px-5 pb-0 pt-3">
|
|
763
|
-
<div className="flex items-center gap-3">
|
|
764
|
-
<span className="min-w-0 flex-1 truncate" style={{ fontSize: 14.5, fontWeight: 600 }}>
|
|
765
|
-
{title}
|
|
766
|
-
</span>
|
|
767
|
-
{/* 项目生长入口;轨迹详情由对话内工具行点击唤起 */}
|
|
768
|
-
<button
|
|
769
|
-
type="button"
|
|
770
|
-
className="icon-btn"
|
|
771
|
-
style={{ width: 30, height: 30, background: projectOpen ? "var(--dsw-active)" : undefined }}
|
|
772
|
-
title={`${t.projectPanel} (Ctrl/⌘+Shift+E)`}
|
|
773
|
-
aria-label={t.projectPanel}
|
|
774
|
-
data-testid="project-toggle"
|
|
775
|
-
onClick={() => toggleProject()}
|
|
776
|
-
>
|
|
777
|
-
<IconFolderOpenOutline16 size={15} />
|
|
778
|
-
</button>
|
|
779
|
-
{/* 提示词来源:系统提示词 / 记忆 / 技能 / 模板 / 工具提示词集中列出,点开即看 */}
|
|
780
|
-
<button
|
|
781
|
-
type="button"
|
|
782
|
-
className="icon-btn"
|
|
783
|
-
style={{ width: 30, height: 30, background: promptsOpen ? "var(--dsw-active)" : undefined }}
|
|
784
|
-
title={t.promptPanel}
|
|
785
|
-
aria-label={t.promptPanel}
|
|
786
|
-
data-testid="prompt-panel-toggle"
|
|
787
|
-
onClick={() => {
|
|
788
|
-
setPromptsOpen((open) => !open);
|
|
789
|
-
setSelected(null);
|
|
790
|
-
setGitDetailsOpen(false);
|
|
791
|
-
}}
|
|
792
|
-
>
|
|
793
|
-
<IconAgentPresetOutline16 size={15} />
|
|
794
|
-
</button>
|
|
795
|
-
</div>
|
|
1025
|
+
{/* 会话信任横幅 */}
|
|
796
1026
|
{projectTrust?.required && !projectTrust.trusted && (
|
|
797
|
-
<div className="mt-2 flex flex-wrap items-center gap-2 rounded-xl px-3 py-2" style={{ fontSize: 12.5, background: "var(--dsw-hover)", border: "0.5px solid var(--dsw-border-l2)" }} role="status">
|
|
1027
|
+
<div className="mx-5 mt-2 flex flex-wrap items-center gap-2 rounded-xl px-3 py-2" style={{ fontSize: 12.5, background: "var(--dsw-hover)", border: "0.5px solid var(--dsw-border-l2)" }} role="status">
|
|
798
1028
|
<span style={{ color: "var(--dsw-warn)", fontWeight: 600 }}>⚠</span>
|
|
799
1029
|
<span className="min-w-0 flex-1">{projectTrust.source === "default-never" || projectTrust.source === "remembered" ? t.projectTrustBannerNever : t.projectTrustBanner}</span>
|
|
800
1030
|
<button className="btn-primary-white" style={{ height: 26, padding: "0 10px", fontSize: 12 }} onClick={() => void setProjectTrust(true)}>{t.projectTrustAllow}</button>
|
|
@@ -803,8 +1033,6 @@ export function AppShell() {
|
|
|
803
1033
|
)}
|
|
804
1034
|
</div>
|
|
805
1035
|
)}
|
|
806
|
-
{/* 轨迹页签已移除:轨迹按需查看(对话里的工具行 → 轨迹详情),不再占用主区域 */}
|
|
807
|
-
</div>
|
|
808
1036
|
|
|
809
1037
|
{/* 内容:只有对话区,轨迹按需在详情栏查看 */}
|
|
810
1038
|
{!snapshot ? (
|
|
@@ -846,19 +1074,19 @@ export function AppShell() {
|
|
|
846
1074
|
onFork={handleFork}
|
|
847
1075
|
onEditMessage={doEditMessage}
|
|
848
1076
|
/>
|
|
849
|
-
<div className="px-4 pb-3 pt-2">
|
|
1077
|
+
<div className="pw-session-composer px-4 pb-3 pt-2">
|
|
850
1078
|
<div className="mx-auto w-full" style={{ maxWidth: "var(--dsh-composer-card-max-width)" }}>
|
|
851
1079
|
{/* 运行状态指示由 ChatWindow 内的 WorkingIndicator 承担(含工具/输出 token 信息) */}
|
|
852
1080
|
<ChatInput
|
|
853
1081
|
key={draftKey}
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
1082
|
+
draft={composerDrafts.current.get(draftKey) ?? EMPTY_CHAT_DRAFT}
|
|
1083
|
+
onDraftChange={saveSessionDraft}
|
|
1084
|
+
onUploadError={(message) => failUpload(draftKey, message)}
|
|
1085
|
+
onUploadProgress={(delta) => trackUpload(draftKey, delta)}
|
|
1086
|
+
uploadFailure={uploadFailures[draftKey]}
|
|
1087
|
+
pendingUploadCount={uploadCounts[draftKey] ?? 0}
|
|
1088
|
+
pendingSend={pendingSends[draftKey] ?? false}
|
|
1089
|
+
onSendPendingChange={(pending) => trackSend(draftKey, pending)}
|
|
862
1090
|
isStreaming={isStreaming}
|
|
863
1091
|
contextPercent={snapshot?.contextUsage?.percent ?? null}
|
|
864
1092
|
contextTokens={snapshot?.contextUsage?.tokens ?? null}
|
|
@@ -869,9 +1097,15 @@ export function AppShell() {
|
|
|
869
1097
|
thinkingLevel={snapshot?.thinkingLevel}
|
|
870
1098
|
thinkingLevels={snapshot?.thinkingLevels ?? ["off", "minimal", "low", "medium", "high", "xhigh", "max"]}
|
|
871
1099
|
models={modelChoices}
|
|
1100
|
+
modelLoading={modelLoading}
|
|
1101
|
+
modelLoadError={modelLoadError}
|
|
1102
|
+
onRetryModels={() => void refreshModels()}
|
|
872
1103
|
providerNames={providerNames}
|
|
873
1104
|
authByProvider={authByProvider}
|
|
874
1105
|
queue={snapshot?.queue ?? { steering: [], followUp: [] }}
|
|
1106
|
+
workflow={snapshot?.workflow}
|
|
1107
|
+
onWorkflowModeChange={(mode) => sendCommand({ cmd: "setWorkflowMode", mode })}
|
|
1108
|
+
onApprovePlan={() => sendCommand({ cmd: "approvePlan" })}
|
|
875
1109
|
commands={slashCommands}
|
|
876
1110
|
onCommand={runSlashCommand}
|
|
877
1111
|
onSend={sendPrompt}
|
|
@@ -893,45 +1127,46 @@ export function AppShell() {
|
|
|
893
1127
|
{Object.entries(state.extensionStatuses).map(([k, v]) => <span key={k}>{v}</span>)}
|
|
894
1128
|
</div>
|
|
895
1129
|
)}
|
|
896
|
-
|
|
1130
|
+
</div>
|
|
897
1131
|
</div>
|
|
898
1132
|
</div>
|
|
899
1133
|
)
|
|
900
1134
|
}
|
|
901
1135
|
</>
|
|
902
1136
|
)}
|
|
903
|
-
|
|
1137
|
+
</section>
|
|
904
1138
|
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
1139
|
+
{/* 提示词来源 / 轨迹 / Git 详情栏卡片 */}
|
|
1140
|
+
{(selected || gitDetailsOpen || promptsOpen) && (
|
|
1141
|
+
<aside
|
|
1142
|
+
className="pw-details-surface clay-surface rounded-3xl p-4 shrink-0 flex flex-col overflow-hidden shadow-md"
|
|
1143
|
+
data-purpose="inspector-panel"
|
|
1144
|
+
style={isNarrow ? {
|
|
1145
|
+
position: "fixed",
|
|
1146
|
+
inset: "0 0 0 auto",
|
|
1147
|
+
width: "min(92vw, 640px)",
|
|
1148
|
+
zIndex: 90,
|
|
1149
|
+
boxShadow: "var(--dsw-elevation-prominent)",
|
|
1150
|
+
} : undefined}
|
|
1151
|
+
>
|
|
1152
|
+
<div className="min-h-0 flex-1 flex flex-col">
|
|
1153
|
+
{promptsOpen ? (
|
|
1154
|
+
<PromptPanel
|
|
1155
|
+
cwd={panelCwd}
|
|
1156
|
+
sessionId={currentId}
|
|
1157
|
+
refreshKey={currentId}
|
|
1158
|
+
onOpenContent={(path, content) => viewer.openStatic(path, content)}
|
|
1159
|
+
onClose={() => setPromptsOpen(false)}
|
|
1160
|
+
/>
|
|
1161
|
+
) : selected ? (
|
|
1162
|
+
<TrajInspector entry={selected} onClose={() => setSelected(null)} />
|
|
1163
|
+
) : (
|
|
1164
|
+
<GitPanel cwd={panelCwd} refreshKey={panelRefreshKey} onClose={() => setGitDetailsOpen(false)} onAskCommit={() => insertIntoComposer(t.gitAskCommitPrompt)} onOpenFile={openInEditor} />
|
|
1165
|
+
)}
|
|
1166
|
+
</div>
|
|
1167
|
+
</aside>
|
|
1168
|
+
)}
|
|
1169
|
+
</main>
|
|
935
1170
|
|
|
936
1171
|
{/* 拖拽手柄 */}
|
|
937
1172
|
{!isNarrow && !sidebarCollapsed && (
|
|
@@ -1045,6 +1280,9 @@ function Hero({
|
|
|
1045
1280
|
getWorkspaceName,
|
|
1046
1281
|
onSend,
|
|
1047
1282
|
models,
|
|
1283
|
+
modelLoading,
|
|
1284
|
+
modelLoadError,
|
|
1285
|
+
onRetryModels,
|
|
1048
1286
|
providerNames,
|
|
1049
1287
|
authByProvider,
|
|
1050
1288
|
addWorkspaceByPicker,
|
|
@@ -1054,6 +1292,9 @@ function Hero({
|
|
|
1054
1292
|
heroModelLevels,
|
|
1055
1293
|
heroThinking,
|
|
1056
1294
|
onSelectHeroThinking,
|
|
1295
|
+
heroMode,
|
|
1296
|
+
onSelectHeroMode,
|
|
1297
|
+
onOpenSwarm,
|
|
1057
1298
|
}: {
|
|
1058
1299
|
draft: ChatDraft;
|
|
1059
1300
|
onDraftChange: (update: ChatDraftUpdate) => void;
|
|
@@ -1071,6 +1312,9 @@ function Hero({
|
|
|
1071
1312
|
getWorkspaceName?: (cwd: string) => string;
|
|
1072
1313
|
onSend: (text: string, images: ImageAttachment[]) => Promise<{ success: boolean }>;
|
|
1073
1314
|
models: ModelChoice[];
|
|
1315
|
+
modelLoading: boolean;
|
|
1316
|
+
modelLoadError: string | null;
|
|
1317
|
+
onRetryModels: () => void;
|
|
1074
1318
|
providerNames: Record<string, string>;
|
|
1075
1319
|
authByProvider: Record<string, boolean>;
|
|
1076
1320
|
addWorkspaceByPicker: () => Promise<string | null>;
|
|
@@ -1080,6 +1324,9 @@ function Hero({
|
|
|
1080
1324
|
heroModelLevels: string[];
|
|
1081
1325
|
heroThinking: string;
|
|
1082
1326
|
onSelectHeroThinking: (level: string) => void;
|
|
1327
|
+
heroMode: WorkflowMode;
|
|
1328
|
+
onSelectHeroMode: (mode: WorkflowMode) => void;
|
|
1329
|
+
onOpenSwarm?: () => void;
|
|
1083
1330
|
}) {
|
|
1084
1331
|
const { t } = useI18n();
|
|
1085
1332
|
const [wsMenu, setWsMenu] = useState(false);
|
|
@@ -1097,81 +1344,106 @@ function Hero({
|
|
|
1097
1344
|
}, [wsMenu]);
|
|
1098
1345
|
|
|
1099
1346
|
const pickWorkspace = async () => {
|
|
1347
|
+
setWsMenu(false);
|
|
1100
1348
|
const p = await addWorkspaceByPicker();
|
|
1101
1349
|
if (p) {
|
|
1102
1350
|
setCwd(p);
|
|
1103
|
-
setWsMenu(false);
|
|
1104
1351
|
}
|
|
1105
1352
|
};
|
|
1106
1353
|
|
|
1107
1354
|
const currentLabel = cwd ? (getWorkspaceName ? getWorkspaceName(cwd) : basename(cwd)) : t.startWith;
|
|
1108
1355
|
|
|
1109
1356
|
return (
|
|
1110
|
-
<div className="flex h-full min-h-0 flex-col items-center justify-center px-6">
|
|
1111
|
-
{/*
|
|
1112
|
-
<div className="
|
|
1113
|
-
|
|
1114
|
-
|
|
1357
|
+
<div className="pw-hero flex h-full min-h-0 flex-col items-center justify-center px-6 py-8 overflow-y-auto relative">
|
|
1358
|
+
{/* Ambient orbs */}
|
|
1359
|
+
<div className="absolute -top-10 left-12 w-64 h-64 bg-emerald-200/40 dark:bg-emerald-950/20 rounded-full filter blur-3xl opacity-60 pointer-events-none" />
|
|
1360
|
+
<div className="absolute top-1/3 -right-16 w-80 h-80 bg-sky-200/40 dark:bg-sky-950/20 rounded-full filter blur-3xl opacity-60 pointer-events-none" />
|
|
1361
|
+
<div className="absolute bottom-6 left-1/4 w-72 h-72 bg-orange-200/30 dark:bg-orange-950/20 rounded-full filter blur-3xl opacity-50 pointer-events-none" />
|
|
1115
1362
|
|
|
1116
|
-
{/* 工作区芯片行 + 输入卡
|
|
1117
|
-
<div className="w-full flex flex-col items-stretch mx-auto" style={{ maxWidth: "var(--dsh-composer-card-max-width)" }}>
|
|
1118
|
-
<div
|
|
1119
|
-
<div className="
|
|
1120
|
-
<
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1363
|
+
{/* 工作区芯片行 + 输入卡 同宽容器 */}
|
|
1364
|
+
<div className="pw-hero-content w-full flex flex-col items-stretch mx-auto my-auto z-10" style={{ maxWidth: "var(--dsh-composer-card-max-width)" }}>
|
|
1365
|
+
<div className="pw-hero-intro mb-4 text-center">
|
|
1366
|
+
<div className="flex items-center justify-center gap-4 px-4 py-2 rounded-full clay-card mb-3 mx-auto w-fit animate-bounce duration-1000 border border-slate-200 dark:border-slate-800">
|
|
1367
|
+
<div className="w-8 h-8 rounded-full bg-emerald-600 flex items-center justify-center shadow-[inset_1px_1px_2px_rgba(255,255,255,0.7)] text-white font-mono font-bold text-sm">
|
|
1368
|
+
π
|
|
1369
|
+
</div>
|
|
1370
|
+
<div className="flex items-center gap-2">
|
|
1371
|
+
<span className="font-extrabold tracking-wider text-slate-900 dark:text-slate-100 text-xs sm:text-sm">PIWEB</span>
|
|
1372
|
+
<span className="text-slate-400">/</span>
|
|
1373
|
+
<span className="text-[10px] tracking-widest text-slate-600 dark:text-slate-400 font-bold uppercase">WORKBENCH</span>
|
|
1374
|
+
</div>
|
|
1375
|
+
<span className="w-1.5 h-1.5 rounded-full bg-slate-300 dark:bg-slate-600" />
|
|
1376
|
+
<div className="flex items-center gap-1.5 px-2.5 py-0.5 rounded-full clay-inset border border-slate-200 dark:border-slate-800">
|
|
1377
|
+
<span className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" />
|
|
1378
|
+
<span className="font-mono text-[11px] text-emerald-700 dark:text-emerald-300 font-bold tracking-wider">01 / READY</span>
|
|
1379
|
+
</div>
|
|
1380
|
+
</div>
|
|
1381
|
+
<h1 className="text-3xl sm:text-4xl font-extrabold tracking-tight text-slate-900 dark:text-slate-50 mb-2 drop-shadow-sm">
|
|
1382
|
+
让想法落到代码。
|
|
1383
|
+
</h1>
|
|
1384
|
+
<p className="text-sm text-slate-600 dark:text-slate-300 max-w-lg mx-auto leading-relaxed font-medium">
|
|
1385
|
+
规划、执行、验证。一个专注于交付的轻量工作台。
|
|
1386
|
+
</p>
|
|
1387
|
+
|
|
1388
|
+
{/* 工作区选择器 */}
|
|
1389
|
+
<div ref={menuRef} className="pw-hero-workspace mt-4 mb-3 flex items-center justify-center gap-2.5">
|
|
1390
|
+
<div className="relative">
|
|
1391
|
+
<button
|
|
1392
|
+
type="button"
|
|
1393
|
+
className="clay-btn clay-btn-soft flex items-center gap-2 px-3.5 py-1.5 rounded-full text-xs font-bold text-slate-800 dark:text-slate-100 border border-slate-200 dark:border-slate-700 shadow-sm"
|
|
1394
|
+
data-open={wsMenu}
|
|
1395
|
+
onClick={() => setWsMenu((v) => !v)}
|
|
1396
|
+
>
|
|
1397
|
+
<IconFolderClose16 className="text-amber-500 flex-none" size={15} />
|
|
1398
|
+
<span className="max-w-[240px] truncate" suppressHydrationWarning>{currentLabel}</span>
|
|
1399
|
+
<IconChevronDown14 size={13} className="text-slate-500 dark:text-slate-400" />
|
|
1400
|
+
</button>
|
|
1401
|
+
{wsMenu && (
|
|
1402
|
+
<div className="clay-card popover absolute top-full left-1/2 -translate-x-1/2 z-50 w-64 mt-2 p-1.5 rounded-2xl shadow-xl border border-slate-200 dark:border-slate-800">
|
|
1403
|
+
<div className="px-3 py-1.5 text-[11px] font-bold text-slate-500 dark:text-slate-400">选择工作区</div>
|
|
1404
|
+
{knownCwds.map((c) => {
|
|
1405
|
+
const itemLabel = getWorkspaceName ? getWorkspaceName(c) : basename(c);
|
|
1406
|
+
return (
|
|
1407
|
+
<button
|
|
1408
|
+
key={c}
|
|
1409
|
+
className="flex w-full items-center gap-2.5 px-3 py-2 text-left rounded-xl transition-colors text-xs font-medium"
|
|
1410
|
+
style={{
|
|
1411
|
+
background: cwd === c ? "var(--dsw-accent-soft)" : "transparent",
|
|
1412
|
+
color: cwd === c ? "var(--dsw-accent)" : "inherit",
|
|
1413
|
+
}}
|
|
1414
|
+
onClick={() => {
|
|
1415
|
+
setCwd(c);
|
|
1416
|
+
setWsMenu(false);
|
|
1417
|
+
}}
|
|
1418
|
+
>
|
|
1419
|
+
<IconFolderClose16 size={14} style={{ flex: "none", color: cwd === c ? "var(--dsw-accent)" : "var(--dsw-label-tertiary)" }} />
|
|
1420
|
+
<span className="min-w-0 flex-1 truncate font-medium">{itemLabel}</span>
|
|
1421
|
+
{cwd === c && <IconCheckOutline14 size={13} style={{ flex: "none" }} />}
|
|
1422
|
+
</button>
|
|
1423
|
+
);
|
|
1424
|
+
})}
|
|
1425
|
+
<div className="my-1 border-t border-slate-200/50 dark:border-slate-800/50" />
|
|
1426
|
+
<button
|
|
1427
|
+
type="button"
|
|
1428
|
+
className="flex w-full items-center gap-2.5 px-3 py-2 text-left rounded-xl transition-colors text-xs text-slate-700 dark:text-slate-200 hover:bg-slate-100 dark:hover:bg-slate-800 font-semibold"
|
|
1429
|
+
onClick={pickWorkspace}
|
|
1430
|
+
>
|
|
1431
|
+
<IconProjectAddOutline16 size={14} style={{ flex: "none", color: "var(--dsw-label-tertiary)" }} />
|
|
1432
|
+
{t.addWorkspace}
|
|
1433
|
+
</button>
|
|
1434
|
+
</div>
|
|
1435
|
+
)}
|
|
1436
|
+
</div>
|
|
1437
|
+
<div className="px-3 py-1.5 rounded-full clay-inset text-slate-700 dark:text-slate-300 text-xs font-mono font-semibold flex items-center gap-1.5 border border-slate-200 dark:border-slate-800">
|
|
1438
|
+
<span className="text-amber-500">⚡</span>
|
|
1439
|
+
<span>在当前工作区执行</span>
|
|
1440
|
+
</div>
|
|
1170
1441
|
</div>
|
|
1171
1442
|
</div>
|
|
1172
1443
|
|
|
1173
1444
|
{/* 输入卡 */}
|
|
1174
|
-
<
|
|
1445
|
+
<div className="w-full mb-4">
|
|
1446
|
+
<ChatInput
|
|
1175
1447
|
draft={draft}
|
|
1176
1448
|
onDraftChange={onDraftChange}
|
|
1177
1449
|
onUploadError={onUploadError}
|
|
@@ -1180,26 +1452,60 @@ function Hero({
|
|
|
1180
1452
|
pendingUploadCount={pendingUploadCount}
|
|
1181
1453
|
pendingSend={pendingSend}
|
|
1182
1454
|
onSendPendingChange={onSendPendingChange}
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1455
|
+
commands={commands}
|
|
1456
|
+
onCommand={onCommand}
|
|
1457
|
+
isStreaming={false}
|
|
1458
|
+
contextPercent={null}
|
|
1459
|
+
contextTokens={null}
|
|
1460
|
+
contextWindow={null}
|
|
1461
|
+
contextVisible={false}
|
|
1462
|
+
model={heroModel ? models.find((m) => m.provider === heroModel.provider && m.id === heroModel.id) ?? { provider: heroModel.provider, id: heroModel.id, name: heroModel.id, reasoning: false, contextWindow: 0 } : defaultModel}
|
|
1463
|
+
thinkingLevel={heroThinking || undefined}
|
|
1464
|
+
thinkingLevels={heroModelLevels}
|
|
1465
|
+
models={models}
|
|
1466
|
+
modelLoading={modelLoading}
|
|
1467
|
+
modelLoadError={modelLoadError}
|
|
1468
|
+
onRetryModels={onRetryModels}
|
|
1469
|
+
providerNames={providerNames}
|
|
1470
|
+
authByProvider={authByProvider}
|
|
1471
|
+
queue={{ steering: [], followUp: [] }}
|
|
1472
|
+
workflow={{ mode: heroMode, planStatus: "idle", goal: "" }}
|
|
1473
|
+
onWorkflowModeChange={onSelectHeroMode}
|
|
1474
|
+
onSend={onSend}
|
|
1475
|
+
onSteer={() => ({ success: false })}
|
|
1476
|
+
onAbort={() => {}}
|
|
1477
|
+
onSelectModel={onSelectHeroModel}
|
|
1478
|
+
onSelectLevel={onSelectHeroThinking}
|
|
1479
|
+
/>
|
|
1480
|
+
<div className="flex items-center justify-between px-2 pt-2 text-slate-500 dark:text-slate-400 font-mono text-xs font-medium">
|
|
1481
|
+
<div className="flex items-center gap-3">
|
|
1482
|
+
<span className="flex items-center gap-1"><kbd className="px-1.5 py-0.5 rounded clay-inset font-bold text-slate-700 dark:text-slate-200 border border-slate-200 dark:border-slate-700">⏎</kbd> 发送</span>
|
|
1483
|
+
<span className="flex items-center gap-1"><kbd className="px-1.5 py-0.5 rounded clay-inset font-bold text-slate-700 dark:text-slate-200 border border-slate-200 dark:border-slate-700">⇧ ⏎</kbd> 换行</span>
|
|
1484
|
+
</div>
|
|
1485
|
+
<div className="flex items-center gap-1.5 text-emerald-700 dark:text-emerald-400 font-semibold">
|
|
1486
|
+
<IconShieldOutline16 size={13} />
|
|
1487
|
+
<span>主会话无系统级沙盒隔离</span>
|
|
1488
|
+
</div>
|
|
1489
|
+
</div>
|
|
1490
|
+
</div>
|
|
1491
|
+
|
|
1492
|
+
{/* Telemetry bottom bar */}
|
|
1493
|
+
<div className="flex items-center justify-center gap-4 text-slate-600 dark:text-slate-300 text-xs font-semibold tracking-wider">
|
|
1494
|
+
<div className="flex items-center gap-1.5">
|
|
1495
|
+
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500" />
|
|
1496
|
+
<span>极速热加载</span>
|
|
1497
|
+
</div>
|
|
1498
|
+
<span>•</span>
|
|
1499
|
+
<div className="flex items-center gap-1.5">
|
|
1500
|
+
<span className="w-1.5 h-1.5 rounded-full bg-sky-500" />
|
|
1501
|
+
<span>MCP 扩展全兼容</span>
|
|
1502
|
+
</div>
|
|
1503
|
+
<span>•</span>
|
|
1504
|
+
<div className="flex items-center gap-1.5">
|
|
1505
|
+
<span className="w-1.5 h-1.5 rounded-full bg-orange-500" />
|
|
1506
|
+
<span>端侧安全加固</span>
|
|
1507
|
+
</div>
|
|
1508
|
+
</div>
|
|
1203
1509
|
</div>
|
|
1204
1510
|
</div>
|
|
1205
1511
|
);
|