@rexvane/piweb 0.3.18 → 0.3.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +8 -6
- package/.next/build-manifest.json +3 -3
- package/.next/prerender-manifest.json +4 -4
- package/.next/react-loadable-manifest.json +23 -9
- package/.next/required-server-files.js +2 -1
- package/.next/required-server-files.json +2 -1
- package/.next/routes-manifest.json +12 -0
- package/.next/server/app/_global-error/page.js +2 -2
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page.js +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +2 -2
- package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/api/agent/[id]/events/route.js +3 -3
- package/.next/server/app/api/agent/[id]/events/route.js.nft.json +1 -1
- package/.next/server/app/api/agent/[id]/route.js +1 -1
- package/.next/server/app/api/agent/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/agent/[id]/tree/route.js +1 -1
- package/.next/server/app/api/agent/[id]/tree/route.js.nft.json +1 -1
- package/.next/server/app/api/agent/new/route.js +1 -1
- package/.next/server/app/api/agent/new/route.js.nft.json +1 -1
- package/.next/server/app/api/config/route.js +1 -1
- package/.next/server/app/api/config/route.js.nft.json +1 -1
- package/.next/server/app/api/files/route.js +1 -1
- package/.next/server/app/api/files/route.js.nft.json +1 -1
- package/.next/server/app/api/fs/route.js +1 -0
- package/.next/server/app/api/fs/route.js.nft.json +1 -0
- package/.next/server/app/api/fs/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/git/route.js +2 -2
- package/.next/server/app/api/git/route.js.nft.json +1 -1
- package/.next/server/app/api/growth/route.js +1 -1
- package/.next/server/app/api/growth/route.js.nft.json +1 -1
- package/.next/server/app/api/health/route.js +1 -1
- package/.next/server/app/api/models/route.js +1 -1
- package/.next/server/app/api/models/route.js.nft.json +1 -1
- package/.next/server/app/api/pi-settings/route.js +1 -1
- package/.next/server/app/api/pi-settings/route.js.nft.json +1 -1
- package/.next/server/app/api/plugins/route.js +1 -1
- package/.next/server/app/api/plugins/route.js.nft.json +1 -1
- package/.next/server/app/api/prompts/route.js +1 -1
- package/.next/server/app/api/prompts/route.js.nft.json +1 -1
- package/.next/server/app/api/remote/route.js +8 -0
- package/.next/server/app/api/remote/route.js.nft.json +1 -0
- package/.next/server/app/api/remote/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/security/route.js +1 -1
- package/.next/server/app/api/security/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/export/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/export/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/[id]/route.js +1 -1
- package/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/import/route.js +2 -2
- package/.next/server/app/api/sessions/import/route.js.nft.json +1 -1
- package/.next/server/app/api/sessions/route.js +1 -1
- package/.next/server/app/api/sessions/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/route.js +1 -1
- package/.next/server/app/api/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/update/route.js +2 -2
- package/.next/server/app/api/update/route.js.nft.json +1 -1
- package/.next/server/app/api/version/route.js +1 -1
- package/.next/server/app/api/version/route.js.nft.json +1 -1
- package/.next/server/app/api/web-auth/route.js +3 -1
- package/.next/server/app/api/workspaces/route.js +1 -1
- package/.next/server/app/api/workspaces/route.js.nft.json +1 -1
- package/.next/server/app/icon.svg/route.js +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +4 -4
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +4 -4
- package/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login/page.js +1 -1
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/login.html +1 -1
- package/.next/server/app/login.rsc +3 -3
- package/.next/server/app/login.segments/_full.segment.rsc +3 -3
- package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/page.js +1 -1
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +8 -6
- package/.next/server/chunks/229.js +1 -1
- package/.next/server/chunks/243.js +1 -0
- package/.next/server/chunks/393.js +1 -0
- package/.next/server/chunks/496.js +1 -1
- package/.next/server/chunks/538.js +1 -0
- package/.next/server/chunks/686.js +1 -4
- package/.next/server/chunks/802.js +2 -0
- package/.next/server/chunks/807.js +1 -0
- package/.next/server/chunks/920.js +4 -0
- package/.next/server/chunks/924.js +6 -0
- package/.next/server/instrumentation.js +1 -0
- package/.next/server/instrumentation.js.nft.json +1 -0
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/.next/server/middleware.js +4 -3
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/JDGSR-TIbk9qZWcUNYIX3/_buildManifest.js +1 -0
- package/.next/static/chunks/2716.7ad073038e94a802.js +1 -0
- package/.next/static/chunks/316-5ea443ee6b512aa9.js +1 -0
- package/.next/static/chunks/4673.8a5a9e6855350e34.js +1 -0
- package/.next/static/chunks/4686-e3825f3b6a384aa1.js +1 -0
- package/.next/static/chunks/4778.e4c9ed093a413bc4.js +1 -0
- package/.next/static/chunks/5173.91ddb803785009f8.js +1 -0
- package/.next/static/chunks/839.7403c6bdace4c56c.js +1 -0
- package/.next/static/chunks/8552.6bfe3e101250b20f.js +1 -0
- package/.next/static/chunks/app/_global-error/page-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/events/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/tree/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/agent/new/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/config/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/files/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/fs/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/git/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/growth/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/health/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/models/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/pi-settings/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/plugins/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/prompts/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/remote/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/security/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/export/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/sessions/import/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/sessions/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/skills/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/update/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/version/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/web-auth/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/api/workspaces/route-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/app/login/page-5e276a1eeb8867df.js +1 -0
- package/.next/static/chunks/app/page-2e425ef83fba0782.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-b179d9f7f6f2daf9.js +1 -0
- package/.next/static/chunks/webpack-bd2b9202ecbcdcaa.js +1 -0
- package/.next/static/css/7391c42e1ae617f9.css +1 -0
- package/.next/types/app/api/fs/route.ts +353 -0
- package/.next/types/app/api/remote/route.ts +353 -0
- package/.next/types/routes.d.ts +3 -1
- package/.next/types/validator.ts +18 -0
- package/README.md +56 -10
- package/next.config.ts +3 -2
- package/package.json +3 -3
- package/scripts/launcher.mjs +194 -13
- package/scripts/release.mjs +0 -2
- package/src/app/api/agent/[id]/events/route.ts +11 -6
- package/src/app/api/fs/route.ts +119 -0
- package/src/app/api/growth/route.ts +11 -6
- package/src/app/api/models/route.ts +21 -9
- package/src/app/api/remote/route.ts +67 -0
- package/src/app/api/sessions/[id]/export/route.ts +11 -1
- package/src/app/api/sessions/route.ts +3 -1
- package/src/app/api/web-auth/route.ts +45 -49
- package/src/app/api/workspaces/route.ts +16 -0
- package/src/app/globals.css +6 -0
- package/src/app/login/page.tsx +8 -60
- package/src/components/AppShell.tsx +202 -155
- package/src/components/ChatInput.tsx +61 -6
- package/src/components/ChatWindow.tsx +274 -132
- package/src/components/DirectoryPicker.tsx +189 -0
- package/src/components/DrawerPanel.tsx +164 -0
- package/src/components/FileViewer.tsx +2 -2
- package/src/components/GrowthTree.tsx +6 -5
- package/src/components/MarkdownBlock.tsx +134 -0
- package/src/components/OutlineRail.tsx +22 -1
- package/src/components/ProjectPanel.tsx +197 -14
- package/src/components/RemoteSection.tsx +232 -0
- package/src/components/SessionImportSection.tsx +60 -38
- package/src/components/SettingsPanel.tsx +104 -57
- package/src/components/icons.tsx +23 -0
- package/src/hooks/useGrowth.ts +47 -25
- package/src/hooks/usePiWeb.ts +100 -14
- package/src/i18n.tsx +115 -28
- package/src/instrumentation-node.ts +35 -0
- package/src/instrumentation.ts +18 -0
- package/src/lib/agent-config.ts +134 -0
- package/src/lib/agent-manager.ts +243 -61
- package/src/lib/auth-throttle.ts +102 -0
- package/src/lib/auth.ts +35 -27
- package/src/lib/files-service.ts +61 -1
- package/src/lib/font.ts +84 -0
- package/src/lib/git-service.ts +5 -3
- package/src/lib/growth-service.ts +78 -30
- package/src/lib/growth-tracker.ts +91 -9
- package/src/lib/models-service.ts +36 -11
- package/src/lib/pi-settings.ts +10 -12
- package/src/lib/pi.ts +145 -18
- package/src/lib/plugins-service.ts +67 -35
- package/src/lib/remote/assets.json +4 -0
- package/src/lib/remote/mobile-proxy.ts +149 -0
- package/src/lib/remote/supervisor.ts +482 -0
- package/src/lib/security-service.ts +6 -11
- package/src/lib/session-import/claude.ts +45 -21
- package/src/lib/session-reader.ts +106 -3
- package/src/lib/skills-service.ts +34 -12
- package/src/lib/text-sanitize.ts +12 -0
- package/src/lib/theme.ts +3 -0
- package/src/lib/types.ts +16 -2
- package/src/lib/update-service.ts +0 -1
- package/src/lib/web-auth-shared.ts +5 -1
- package/src/lib/web-auth.ts +139 -22
- package/src/lib/web-launch-token.ts +55 -0
- package/src/lib/web-secret.ts +182 -0
- package/src/lib/workspace-store.ts +91 -7
- package/src/proxy.ts +46 -12
- package/.next/server/chunks/532.js +0 -1
- package/.next/static/chunks/2155.2f27fe7098987699.js +0 -1
- package/.next/static/chunks/2608.ec8ff1f5767c9056.js +0 -1
- package/.next/static/chunks/2716.93be8ad5c970c516.js +0 -1
- package/.next/static/chunks/4632.112d2e893d48d188.js +0 -1
- package/.next/static/chunks/4673.c1fb1580efc6ace7.js +0 -1
- package/.next/static/chunks/4686-f4a037d609cea3f2.js +0 -1
- package/.next/static/chunks/692-7992db0ae9cc3021.js +0 -1
- package/.next/static/chunks/app/_global-error/page-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/events/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/tree/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/agent/new/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/config/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/files/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/git/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/growth/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/health/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/models/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/pi-settings/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/plugins/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/prompts/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/security/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/export/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/sessions/import/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/sessions/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/skills/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/update/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/version/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/web-auth/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/api/workspaces/route-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/app/login/page-52ef4841cbd199a8.js +0 -1
- package/.next/static/chunks/app/page-b51087831582ee37.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f77e9ede9808b18e.js +0 -1
- package/.next/static/chunks/webpack-c31263fd83d75ce6.js +0 -1
- package/.next/static/css/0cd968b9b62035b5.css +0 -1
- package/.next/static/en03hnPiuro0rnf8QkFEe/_buildManifest.js +0 -1
- /package/.next/static/{en03hnPiuro0rnf8QkFEe → JDGSR-TIbk9qZWcUNYIX3}/_ssgManifest.js +0 -0
|
@@ -11,7 +11,8 @@ import { ChatWindow, SessionStatsBar, type RoundBadge } from "@/components/ChatW
|
|
|
11
11
|
import { ExtensionDialogHost, ExtensionNotices } from "@/components/ExtensionUI";
|
|
12
12
|
import dynamic from "next/dynamic";
|
|
13
13
|
import { SessionSidebar } from "@/components/SessionSidebar";
|
|
14
|
-
import {
|
|
14
|
+
import type { DrawerSection } from "@/components/DrawerPanel";
|
|
15
|
+
import { DirectoryPicker } from "@/components/DirectoryPicker";
|
|
15
16
|
|
|
16
17
|
// 首屏不需要的重组件按需加载(设置面板含供应商配置与代码高亮,轨迹/文件只在打开时才用)
|
|
17
18
|
const SettingsPanel = dynamic(() => import("@/components/SettingsPanel").then((m) => m.SettingsPanel), { ssr: false });
|
|
@@ -20,16 +21,16 @@ const TrajInspector = dynamic(() => import("@/components/TrajectoryView").then((
|
|
|
20
21
|
/** 会话/工作区路径比较(Windows 大小写与分隔符差异不该影响判断) */
|
|
21
22
|
const samePath = (a: string, b: string): boolean =>
|
|
22
23
|
a.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase() === b.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
23
|
-
const
|
|
24
|
+
const DrawerPanel = dynamic(() => import("@/components/DrawerPanel").then((m) => m.DrawerPanel), { ssr: false });
|
|
25
|
+
const GrowthConsentDialog = dynamic(() => import("@/components/ProjectPanel").then((m) => m.GrowthConsentDialog), { ssr: false });
|
|
24
26
|
const FileViewer = dynamic(() => import("@/components/FileViewer").then((m) => m.FileViewer), { ssr: false });
|
|
25
|
-
|
|
27
|
+
// Git 面板不再由 AppShell 直接渲染:它现在是抽屉里的一个入口(DrawerPanel 内部按需加载)
|
|
26
28
|
import {
|
|
27
|
-
IconAgentPresetOutline16,
|
|
28
29
|
IconCheckOutline14,
|
|
29
30
|
IconChevronDown14,
|
|
30
31
|
IconFolderClose16,
|
|
31
|
-
IconFolderOpenOutline16,
|
|
32
32
|
IconPanelLeftOutline16,
|
|
33
|
+
IconPanelRightOutline16,
|
|
33
34
|
IconProjectAddOutline16,
|
|
34
35
|
} from "@/components/icons";
|
|
35
36
|
import { useI18n } from "@/i18n";
|
|
@@ -46,12 +47,11 @@ const SIDEBAR_MIN = 264;
|
|
|
46
47
|
const SIDEBAR_MAX = 420;
|
|
47
48
|
const SIDEBAR_DEFAULT = 280;
|
|
48
49
|
const SIDEBAR_COLLAPSED = 56;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
const PROJECT_DEFAULT = 380;
|
|
50
|
+
// 最右一列(网格列,不是浮层):轨迹详情 ⇄ 抽屉共用同一列,所以宽度也只有一套。
|
|
51
|
+
// 下界取原抽屉(顶部入口列表 + 内容),上界取原详情栏(diff 与轨迹详情需要更宽)。
|
|
52
|
+
const RIGHT_MIN = 280;
|
|
53
|
+
const RIGHT_MAX = 760;
|
|
54
|
+
const RIGHT_DEFAULT = 360;
|
|
55
55
|
function persist(key: string, value: number) {
|
|
56
56
|
localStorage.setItem(key, String(value));
|
|
57
57
|
}
|
|
@@ -59,6 +59,19 @@ function restore(key: string, fallback: number): number {
|
|
|
59
59
|
const v = parseInt(localStorage.getItem(key) ?? "", 10);
|
|
60
60
|
return Number.isNaN(v) ? fallback : v;
|
|
61
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* 最右一列的宽度:合并前有两个键(piweb.drawerW 抽屉 / piweb.detailsW 详情栏),
|
|
64
|
+
* 现在同一列只写 piweb.drawerW(抽屉是这一列的常驻内容,详情栏已不再单独存在)。
|
|
65
|
+
* 读取时先抽屉键、再回落到旧的详情栏键,最后夹到统一后的上下界——
|
|
66
|
+
* 老用户拖过的那一列宽度不会被重置成默认值,两个键都写过时以较新的抽屉键为准。
|
|
67
|
+
*/
|
|
68
|
+
function restoreRightWidth(): number {
|
|
69
|
+
for (const key of ["piweb.drawerW", "piweb.detailsW"]) {
|
|
70
|
+
const v = parseInt(localStorage.getItem(key) ?? "", 10);
|
|
71
|
+
if (!Number.isNaN(v)) return Math.max(RIGHT_MIN, Math.min(RIGHT_MAX, v));
|
|
72
|
+
}
|
|
73
|
+
return RIGHT_DEFAULT;
|
|
74
|
+
}
|
|
62
75
|
|
|
63
76
|
export function AppShell() {
|
|
64
77
|
const { t } = useI18n();
|
|
@@ -86,6 +99,9 @@ export function AppShell() {
|
|
|
86
99
|
setGroupBy,
|
|
87
100
|
setOrderBy,
|
|
88
101
|
addWorkspaceByPicker,
|
|
102
|
+
dirPickerOpen,
|
|
103
|
+
confirmDirPicker,
|
|
104
|
+
cancelDirPicker,
|
|
89
105
|
removeWorkspace,
|
|
90
106
|
refreshModels,
|
|
91
107
|
sendCommand,
|
|
@@ -97,15 +113,14 @@ export function AppShell() {
|
|
|
97
113
|
clearCompaction,
|
|
98
114
|
setError,
|
|
99
115
|
answerExtensionDialog,
|
|
116
|
+
answerGrowthConsent,
|
|
100
117
|
dismissExtensionNotice,
|
|
101
118
|
} = usePiWeb();
|
|
102
119
|
|
|
103
120
|
const [sidebarWidth, setSidebarWidth] = useState(SIDEBAR_DEFAULT);
|
|
104
121
|
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const [projectOpen, setProjectOpen] = useState(false);
|
|
108
|
-
const [projectWidth, setProjectWidth] = useState(PROJECT_DEFAULT);
|
|
122
|
+
/** 最右一列(轨迹详情 / 抽屉共用)的宽度 */
|
|
123
|
+
const [rightWidth, setRightWidth] = useState(RIGHT_DEFAULT);
|
|
109
124
|
// Drafts are parent-owned data, never a replayable last insertion/upload event.
|
|
110
125
|
// A hero draft keeps its identity when newSession switches the rendered composer.
|
|
111
126
|
const composerDrafts = useRef(new Map<string, ChatDraft>());
|
|
@@ -135,12 +150,17 @@ export function AppShell() {
|
|
|
135
150
|
}, []);
|
|
136
151
|
const saveSessionDraft = useCallback((update: ChatDraftUpdate) => updateDraft(draftKey, update), [draftKey, updateDraft]);
|
|
137
152
|
const saveHeroDraft = useCallback((update: ChatDraftUpdate) => updateDraft(heroDraftKey, update), [heroDraftKey, updateDraft]);
|
|
138
|
-
const [dragging, setDragging] = useState<"sidebar" | "details" | "project" | null>(null);
|
|
139
153
|
const [settingsOpen, setSettingsOpen] = useState(false);
|
|
140
154
|
const [selected, setSelected] = useState<TrajEntry | null>(null);
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const [
|
|
155
|
+
/** 抽屉当前入口(由这里持有:轨迹详情关掉后要回到同一个入口) */
|
|
156
|
+
// 默认进「项目」:项目栏降级成抽屉入口后,它是最常用的那一个
|
|
157
|
+
const [drawerSection, setDrawerSection] = useState<DrawerSection>("project");
|
|
158
|
+
/** 右侧抽屉(最右一列):入口列表 + 内容 */
|
|
159
|
+
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
160
|
+
/** 打开抽屉时「落到最新轮」只跳过这一次(点 badge 指定轮次时用) */
|
|
161
|
+
const skipLatestOnce = useRef(false);
|
|
162
|
+
/** useGrowth 的"落到最新轮":它在 AppShell 里、不随抽屉卸载,所以必须显式调用 */
|
|
163
|
+
const growthSelectLatest = useRef<() => void>(() => {});
|
|
144
164
|
const [heroCwd, setHeroCwd] = useState("");
|
|
145
165
|
const [heroModel, setHeroModel] = useState<{ provider: string; id: string } | null>(null);
|
|
146
166
|
const [heroThinking, setHeroThinking] = useState("");
|
|
@@ -148,29 +168,40 @@ export function AppShell() {
|
|
|
148
168
|
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
|
|
149
169
|
useEffect(() => {
|
|
150
170
|
setSidebarWidth(restore("piweb.sidebarW", SIDEBAR_DEFAULT));
|
|
151
|
-
|
|
152
|
-
setProjectWidth(restore("piweb.projectW", PROJECT_DEFAULT));
|
|
171
|
+
setRightWidth(restoreRightWidth());
|
|
153
172
|
setSidebarCollapsed(localStorage.getItem("piweb.sidebarCollapsed") === "1");
|
|
154
|
-
|
|
173
|
+
setDrawerOpen(localStorage.getItem("piweb.drawerOpen") === "1");
|
|
155
174
|
}, []);
|
|
156
|
-
|
|
157
|
-
|
|
175
|
+
/** 右侧抽屉开关(项目栏降级成抽屉入口后,它是"最右一列是否打开"唯一的开关) */
|
|
176
|
+
const toggleDrawer = useCallback((next?: boolean) => {
|
|
177
|
+
setDrawerOpen((cur) => {
|
|
158
178
|
const v = next ?? !cur;
|
|
159
|
-
localStorage.setItem("piweb.
|
|
179
|
+
localStorage.setItem("piweb.drawerOpen", v ? "1" : "0");
|
|
160
180
|
return v;
|
|
161
181
|
});
|
|
162
182
|
}, []);
|
|
163
|
-
|
|
183
|
+
/** 切到某个抽屉入口并打开抽屉(顶栏图标 / 点 badge 都用这条) */
|
|
184
|
+
const openDrawerSection = useCallback(
|
|
185
|
+
(section: DrawerSection) => {
|
|
186
|
+
setDrawerSection(section);
|
|
187
|
+
setSelected(null);
|
|
188
|
+
toggleDrawer(true);
|
|
189
|
+
},
|
|
190
|
+
[toggleDrawer],
|
|
191
|
+
);
|
|
192
|
+
// Ctrl/⌘+Shift+E:打开抽屉并切到「项目」入口;已经停在该入口时再按收起。
|
|
193
|
+
// 轨迹详情占着这一列时,这个快捷键把抽屉叫回来(而不是切到轨迹详情)。
|
|
164
194
|
useEffect(() => {
|
|
165
195
|
const h = (e: KeyboardEvent) => {
|
|
166
196
|
if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key.toLowerCase() === "e") {
|
|
167
197
|
e.preventDefault();
|
|
168
|
-
|
|
198
|
+
if (drawerOpen && !selected && drawerSection === "project") toggleDrawer(false);
|
|
199
|
+
else openDrawerSection("project");
|
|
169
200
|
}
|
|
170
201
|
};
|
|
171
202
|
document.addEventListener("keydown", h);
|
|
172
203
|
return () => document.removeEventListener("keydown", h);
|
|
173
|
-
}, [
|
|
204
|
+
}, [drawerOpen, drawerSection, toggleDrawer, openDrawerSection]);
|
|
174
205
|
|
|
175
206
|
useEffect(() => {
|
|
176
207
|
const query = window.matchMedia("(max-width: 840px)");
|
|
@@ -200,11 +231,16 @@ export function AppShell() {
|
|
|
200
231
|
};
|
|
201
232
|
}, []);
|
|
202
233
|
|
|
203
|
-
//
|
|
234
|
+
// Markdown 分块(react-markdown + highlight.js 常用语言集,实测 472KB)空闲预取:
|
|
235
|
+
// 首屏不为它付出下载成本,等真正打开会话时通常已经就绪,不会看到正文空窗。
|
|
236
|
+
useEffect(() => {
|
|
237
|
+
const timer = setTimeout(() => { void import("@/components/MarkdownBlock"); }, 1200);
|
|
238
|
+
return () => clearTimeout(timer);
|
|
239
|
+
}, []);
|
|
240
|
+
|
|
241
|
+
// 切换会话时清掉轨迹详情的选中态(最右一列回落到抽屉;抽屉入口保持不变)
|
|
204
242
|
useEffect(() => {
|
|
205
243
|
setSelected(null);
|
|
206
|
-
setGitDetailsOpen(false);
|
|
207
|
-
setPromptsOpen(false);
|
|
208
244
|
}, [currentPath]);
|
|
209
245
|
|
|
210
246
|
// 已知工作区列表(给 Hero 建议);启动不预选任何工作区,
|
|
@@ -251,26 +287,32 @@ export function AppShell() {
|
|
|
251
287
|
|
|
252
288
|
|
|
253
289
|
// ---------- 拖拽 ----------
|
|
290
|
+
// pointermove 事件率(125~1000Hz)远高于帧率:事件里只记最新位置,rAF 里每帧
|
|
291
|
+
// 最多提交一次 state,渲染+重排从事件率降到帧率;持久化推迟到松手一次完成。
|
|
254
292
|
const onDrag = useCallback(
|
|
255
|
-
(side: "sidebar" | "
|
|
293
|
+
(side: "sidebar" | "right") => (e: React.PointerEvent) => {
|
|
256
294
|
e.preventDefault();
|
|
257
|
-
setDragging(side);
|
|
258
295
|
const startX = e.clientX;
|
|
259
|
-
const startW = side === "sidebar" ? sidebarWidth :
|
|
296
|
+
const startW = side === "sidebar" ? sidebarWidth : rightWidth;
|
|
297
|
+
const [min, max] = side === "sidebar" ? [SIDEBAR_MIN, SIDEBAR_MAX] : [RIGHT_MIN, RIGHT_MAX];
|
|
298
|
+
// 最右一列:左缘往左拖是变宽(与原来的详情栏、抽屉同向)
|
|
299
|
+
const sign = side === "right" ? -1 : 1;
|
|
300
|
+
const commit = side === "sidebar" ? setSidebarWidth : setRightWidth;
|
|
301
|
+
let frame = 0;
|
|
302
|
+
let latest = startW;
|
|
260
303
|
const move = (ev: PointerEvent) => {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
} else {
|
|
268
|
-
const w = Math.max(DETAILS_MIN, Math.min(DETAILS_MAX, startW - (ev.clientX - startX)));
|
|
269
|
-
setDetailsWidth(w);
|
|
304
|
+
latest = Math.max(min, Math.min(max, startW + sign * (ev.clientX - startX)));
|
|
305
|
+
if (!frame) {
|
|
306
|
+
frame = requestAnimationFrame(() => {
|
|
307
|
+
frame = 0;
|
|
308
|
+
commit(latest);
|
|
309
|
+
});
|
|
270
310
|
}
|
|
271
311
|
};
|
|
272
312
|
const up = () => {
|
|
273
|
-
|
|
313
|
+
if (frame) cancelAnimationFrame(frame);
|
|
314
|
+
commit(latest);
|
|
315
|
+
persist(side === "sidebar" ? "piweb.sidebarW" : "piweb.drawerW", latest);
|
|
274
316
|
window.removeEventListener("pointermove", move);
|
|
275
317
|
window.removeEventListener("pointerup", up);
|
|
276
318
|
window.removeEventListener("pointercancel", up);
|
|
@@ -280,15 +322,9 @@ export function AppShell() {
|
|
|
280
322
|
// 触摸/笔输入被系统手势打断时只触发 pointercancel,不处理会永久泄漏监听器
|
|
281
323
|
window.addEventListener("pointercancel", up);
|
|
282
324
|
},
|
|
283
|
-
[sidebarWidth,
|
|
325
|
+
[sidebarWidth, rightWidth],
|
|
284
326
|
);
|
|
285
327
|
|
|
286
|
-
useEffect(() => {
|
|
287
|
-
if (dragging === "sidebar") persist("piweb.sidebarW", sidebarWidth);
|
|
288
|
-
if (dragging === "details") persist("piweb.detailsW", detailsWidth);
|
|
289
|
-
if (dragging === "project") persist("piweb.projectW", projectWidth);
|
|
290
|
-
}, [dragging, sidebarWidth, detailsWidth, projectWidth]);
|
|
291
|
-
|
|
292
328
|
const toggleSidebar = () => {
|
|
293
329
|
const next = !sidebarCollapsed;
|
|
294
330
|
setSidebarCollapsed(next);
|
|
@@ -304,7 +340,7 @@ export function AppShell() {
|
|
|
304
340
|
const openToolTrajectory = useCallback((toolCallId: string) => {
|
|
305
341
|
const entry = trajectoryRef.current.find((item) => item.toolCallId === toolCallId);
|
|
306
342
|
if (!entry) return;
|
|
307
|
-
|
|
343
|
+
// 最右一列与抽屉互斥:选中轨迹详情即接管这一列,关掉后回落回抽屉的当前入口
|
|
308
344
|
setSelected(entry);
|
|
309
345
|
}, []);
|
|
310
346
|
|
|
@@ -541,16 +577,20 @@ export function AppShell() {
|
|
|
541
577
|
prevStreamingRef.current = isStreaming;
|
|
542
578
|
}, [isStreaming]);
|
|
543
579
|
|
|
580
|
+
/**
|
|
581
|
+
* 「引用到输入框」:引用存成草稿里的独立数组,输入框渲染成 @ 前缀的 chip
|
|
582
|
+
* (不再直接把路径混进正文——正文和引用在视觉上要能分开,发送时才拼成 @路径)。
|
|
583
|
+
*/
|
|
544
584
|
const insertIntoComposer = useCallback((text: string) => {
|
|
545
|
-
|
|
546
|
-
const
|
|
585
|
+
// 存的是"裸路径"(不带 @):发送时统一拼 @路径,避免调用方已经带了 @ 就变成 @@
|
|
586
|
+
const path = text.trim().replace(/^@+/, "");
|
|
587
|
+
if (!path) return;
|
|
547
588
|
updateDraft(draftKey, (previous) => {
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
return { ...previous, text: `${before}${before && !/\s$/.test(before) ? " " : ""}${text}${after}` };
|
|
589
|
+
const references = previous.references ?? [];
|
|
590
|
+
if (references.includes(path)) return previous;
|
|
591
|
+
return { ...previous, references: [...references, path] };
|
|
552
592
|
});
|
|
553
|
-
requestAnimationFrame(() => textarea?.focus());
|
|
593
|
+
requestAnimationFrame(() => document.querySelector<HTMLTextAreaElement>('[data-testid="composer"] textarea')?.focus());
|
|
554
594
|
}, [draftKey, updateDraft]);
|
|
555
595
|
const openInEditor = useCallback(
|
|
556
596
|
async (filePath: string) => {
|
|
@@ -579,11 +619,27 @@ export function AppShell() {
|
|
|
579
619
|
sessionPath: currentPath,
|
|
580
620
|
liveRounds: state.growth.rounds,
|
|
581
621
|
runtimeError: state.growth.error,
|
|
582
|
-
active:
|
|
622
|
+
active: drawerOpen || viewer.state.open,
|
|
583
623
|
watchRounds: Boolean(currentPath),
|
|
584
624
|
connected: state.connected,
|
|
585
625
|
});
|
|
586
626
|
const openFileNode = useCallback((node: TreeNode) => viewer.open(node.path, { from: node.from, lazy: node.lazy }), [viewer]);
|
|
627
|
+
// 打开面板时落在最新轮:useGrowth 活在 AppShell 里、不随抽屉卸载,state 不会自己回到最新,
|
|
628
|
+
// 所以每次从「关」变「开」(或换了工作区)都显式选一次最新轮。
|
|
629
|
+
// 新轮次出现时不再自动跳——用户打开面板后停在哪一轮就是哪一轮。
|
|
630
|
+
growthSelectLatest.current = growth.selectLatest;
|
|
631
|
+
useEffect(() => {
|
|
632
|
+
if (!drawerOpen) {
|
|
633
|
+
// 关掉时清掉标记:否则"抽屉已经开着时点 badge"留下的标记会吞掉下一次打开
|
|
634
|
+
skipLatestOnce.current = false;
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
if (skipLatestOnce.current) {
|
|
638
|
+
skipLatestOnce.current = false;
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
growthSelectLatest.current();
|
|
642
|
+
}, [drawerOpen, panelCwd]);
|
|
587
643
|
const roundBadges = useMemo(() => {
|
|
588
644
|
const map = new Map<string, RoundBadge>();
|
|
589
645
|
for (const round of growth.rounds) {
|
|
@@ -593,11 +649,16 @@ export function AppShell() {
|
|
|
593
649
|
}
|
|
594
650
|
return map;
|
|
595
651
|
}, [growth.rounds]);
|
|
652
|
+
// 点对话里的轮次 badge:打开抽屉、切到「项目」入口,并选中**那一轮**(不是最新轮)。
|
|
653
|
+
// skipLatestOnce 让下面「打开即落最新轮」的 effect 放这一拍过去,不要覆盖掉刚选中的那轮。
|
|
596
654
|
const growthSelect = growth.select;
|
|
597
655
|
const showRound = useCallback((commit: string) => {
|
|
598
|
-
|
|
656
|
+
skipLatestOnce.current = true;
|
|
657
|
+
setDrawerSection("project");
|
|
658
|
+
setSelected(null);
|
|
659
|
+
toggleDrawer(true);
|
|
599
660
|
growthSelect(commit);
|
|
600
|
-
}, [
|
|
661
|
+
}, [toggleDrawer, growthSelect]);
|
|
601
662
|
const jumpToChat = useCallback((messageId: string) => {
|
|
602
663
|
requestAnimationFrame(() => {
|
|
603
664
|
const node = document.querySelector<HTMLElement>(`[data-role="user"][data-message-id="${CSS.escape(messageId)}"]`);
|
|
@@ -612,37 +673,54 @@ export function AppShell() {
|
|
|
612
673
|
document.title = currentId && title && title !== "pi" ? `${title} · pi` : "pi";
|
|
613
674
|
}, [currentId, title]);
|
|
614
675
|
|
|
615
|
-
//
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
const
|
|
676
|
+
// 网格列(宽屏):侧栏 | 对话区 | 最右一列(轨迹详情 ⇄ 抽屉)。项目栏已降级成
|
|
677
|
+
// 抽屉里的一个入口,不再单独占一列,所以是 3 列而不是 4 列。
|
|
678
|
+
// JSX 里各列的渲染顺序必须与这里严格一致,否则网格会把内容摆错列。
|
|
679
|
+
// 最右一列只有一条宽度(rightWidth):详情栏与抽屉合并后不再各占一列。
|
|
680
|
+
const rightColumnVisible = Boolean(selected) || drawerOpen;
|
|
681
|
+
const gridCols = isNarrow ? "minmax(0,1fr)" : `${sidebarCollapsed ? SIDEBAR_COLLAPSED : sidebarWidth}px minmax(0,1fr) ${
|
|
682
|
+
rightColumnVisible ? `${rightWidth}px` : "0px"}`;
|
|
683
|
+
|
|
684
|
+
// 最右一列(宽屏是网格列,窄屏是贴左右的 fixed 覆盖层):轨迹详情 ⇄ 抽屉互斥。
|
|
685
|
+
// selected(点对话里的工具行)优先——它临时接管这一列;关掉后自动回落回抽屉,
|
|
686
|
+
// 并回到抽屉原本选中的入口(入口状态在 drawerSection,不随锁定/解锁重置)。
|
|
687
|
+
const rightColumn = (
|
|
620
688
|
<div
|
|
621
|
-
className="min-h-0 overflow-hidden"
|
|
689
|
+
className="flex min-h-0 flex-col overflow-hidden"
|
|
622
690
|
style={isNarrow ? {
|
|
623
691
|
position: "fixed",
|
|
624
|
-
inset: "0
|
|
625
|
-
width: "min(
|
|
626
|
-
zIndex:
|
|
692
|
+
inset: "0 0 0 auto",
|
|
693
|
+
width: "min(92vw, 640px)",
|
|
694
|
+
zIndex: 90,
|
|
695
|
+
background: "var(--dsw-sidebar-fill)",
|
|
627
696
|
boxShadow: "var(--dsw-elevation-prominent)",
|
|
628
|
-
} :
|
|
697
|
+
} : { background: "var(--dsw-sidebar-fill)", borderLeft: "0.5px solid var(--dsw-border-l2)" }}
|
|
629
698
|
>
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
699
|
+
{selected ? (
|
|
700
|
+
<TrajInspector entry={selected} onClose={() => setSelected(null)} />
|
|
701
|
+
) : (
|
|
702
|
+
<DrawerPanel
|
|
703
|
+
cwd={panelCwd}
|
|
704
|
+
section={drawerSection}
|
|
705
|
+
onSectionChange={setDrawerSection}
|
|
706
|
+
onClose={() => toggleDrawer(false)}
|
|
707
|
+
sessionId={currentId}
|
|
708
|
+
promptRefreshKey={currentId}
|
|
709
|
+
gitRefreshKey={panelRefreshKey}
|
|
710
|
+
growth={growth}
|
|
711
|
+
workspaceName={panelCwd ? getWorkspaceName(panelCwd) : ""}
|
|
712
|
+
hasSession={Boolean(currentPath)}
|
|
713
|
+
running={isStreaming}
|
|
714
|
+
onOpenFileNode={openFileNode}
|
|
715
|
+
onReference={insertIntoComposer}
|
|
716
|
+
onOpenEditor={openInEditor}
|
|
717
|
+
onJumpToChat={jumpToChat}
|
|
718
|
+
onError={setError}
|
|
719
|
+
onOpenContent={(path, content) => viewer.openStatic(path, content)}
|
|
720
|
+
onAskCommit={() => insertIntoComposer(t.gitAskCommitPrompt)}
|
|
721
|
+
onOpenFile={openInEditor}
|
|
722
|
+
/>
|
|
723
|
+
)}
|
|
646
724
|
</div>
|
|
647
725
|
);
|
|
648
726
|
|
|
@@ -651,7 +729,9 @@ export function AppShell() {
|
|
|
651
729
|
className="grid h-screen w-screen overflow-hidden"
|
|
652
730
|
style={{
|
|
653
731
|
gridTemplateColumns: gridCols,
|
|
654
|
-
|
|
732
|
+
// 收起/展开不做过渡:grid-template-columns 是布局属性,动画它会让浏览器逐帧
|
|
733
|
+
// 重排整棵布局树,而聊天列 minmax(0,1fr) 的宽度逐帧变化,正文/代码块每帧
|
|
734
|
+
// 重新折行,长对话必然掉帧。瞬时切换只付一次重排;拖动改宽原本也是 none。
|
|
655
735
|
background: "var(--dsw-bg-base)",
|
|
656
736
|
}}
|
|
657
737
|
>
|
|
@@ -716,8 +796,7 @@ export function AppShell() {
|
|
|
716
796
|
/>
|
|
717
797
|
</div>
|
|
718
798
|
|
|
719
|
-
{/*
|
|
720
|
-
{!isNarrow && (projectColumn || <div aria-hidden="true" />)}
|
|
799
|
+
{/* 三列网格:侧栏 | 会话区 | 最右一列(项目栏已进抽屉,不再有独立的项目列) */}
|
|
721
800
|
|
|
722
801
|
{/* 会话区 */}
|
|
723
802
|
<div className="pi-main flex min-h-0 min-w-0 flex-col">
|
|
@@ -776,33 +855,28 @@ export function AppShell() {
|
|
|
776
855
|
<span className="min-w-0 flex-1 truncate" style={{ fontSize: 14.5, fontWeight: 600 }}>
|
|
777
856
|
{title}
|
|
778
857
|
</span>
|
|
779
|
-
{/*
|
|
858
|
+
{/* 项目与提示词原来是两个平级图标,现已合并进右侧抽屉:
|
|
859
|
+
抽屉顶部那条横向图标栏就是它们的新位置,顶栏只留一个抽屉开关。
|
|
860
|
+
Ctrl/⌘+Shift+E 仍然直达项目入口(见快捷键绑定)。 */}
|
|
861
|
+
{/* 右侧抽屉:项目 / 提示词 / Git 详情 / 会话导入 四个入口都在里面 */}
|
|
780
862
|
<button
|
|
781
863
|
type="button"
|
|
782
864
|
className="icon-btn"
|
|
783
|
-
style={{ width: 30, height: 30, background:
|
|
784
|
-
title={
|
|
785
|
-
aria-label={t.
|
|
786
|
-
data-testid="
|
|
787
|
-
onClick={() => toggleProject()}
|
|
788
|
-
>
|
|
789
|
-
<IconFolderOpenOutline16 size={15} />
|
|
790
|
-
</button>
|
|
791
|
-
{/* 提示词来源:系统提示词 / 记忆 / 技能 / 模板 / 工具提示词集中列出,点开即看 */}
|
|
792
|
-
<button
|
|
793
|
-
type="button"
|
|
794
|
-
className="icon-btn"
|
|
795
|
-
style={{ width: 30, height: 30, background: promptsOpen ? "var(--dsw-active)" : undefined }}
|
|
796
|
-
title={t.promptPanel}
|
|
797
|
-
aria-label={t.promptPanel}
|
|
798
|
-
data-testid="prompt-panel-toggle"
|
|
865
|
+
style={{ width: 30, height: 30, background: drawerOpen && !selected ? "var(--dsw-active)" : undefined }}
|
|
866
|
+
title={t.drawerPanel}
|
|
867
|
+
aria-label={t.drawerPanel}
|
|
868
|
+
data-testid="drawer-toggle"
|
|
799
869
|
onClick={() => {
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
870
|
+
// 轨迹详情占着这一列时,这个按钮是把抽屉叫回来(否则点了像没反应)
|
|
871
|
+
if (selected) {
|
|
872
|
+
setSelected(null);
|
|
873
|
+
toggleDrawer(true);
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
toggleDrawer();
|
|
803
877
|
}}
|
|
804
878
|
>
|
|
805
|
-
<
|
|
879
|
+
<IconPanelRightOutline16 size={15} />
|
|
806
880
|
</button>
|
|
807
881
|
</div>
|
|
808
882
|
{projectTrust?.required && !projectTrust.trusted && (
|
|
@@ -916,36 +990,9 @@ export function AppShell() {
|
|
|
916
990
|
)}
|
|
917
991
|
</div>
|
|
918
992
|
|
|
919
|
-
{/*
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
className="flex min-h-0 flex-col overflow-hidden"
|
|
923
|
-
style={isNarrow ? {
|
|
924
|
-
position: "fixed",
|
|
925
|
-
inset: "0 0 0 auto",
|
|
926
|
-
width: "min(92vw, 640px)",
|
|
927
|
-
zIndex: 90,
|
|
928
|
-
background: "var(--dsw-sidebar-fill)",
|
|
929
|
-
boxShadow: "var(--dsw-elevation-prominent)",
|
|
930
|
-
} : { background: "var(--dsw-sidebar-fill)", borderLeft: "0.5px solid var(--dsw-border-l2)" }}
|
|
931
|
-
>
|
|
932
|
-
<div className="min-h-0 flex-1">
|
|
933
|
-
{promptsOpen ? (
|
|
934
|
-
<PromptPanel
|
|
935
|
-
cwd={panelCwd}
|
|
936
|
-
sessionId={currentId}
|
|
937
|
-
refreshKey={currentId}
|
|
938
|
-
onOpenContent={(path, content) => viewer.openStatic(path, content)}
|
|
939
|
-
onClose={() => setPromptsOpen(false)}
|
|
940
|
-
/>
|
|
941
|
-
) : selected ? (
|
|
942
|
-
<TrajInspector entry={selected} onClose={() => setSelected(null)} />
|
|
943
|
-
) : (
|
|
944
|
-
<GitPanel cwd={panelCwd} refreshKey={panelRefreshKey} onClose={() => setGitDetailsOpen(false)} onAskCommit={() => insertIntoComposer(t.gitAskCommitPrompt)} onOpenFile={openInEditor} />
|
|
945
|
-
)}
|
|
946
|
-
</div>
|
|
947
|
-
</div>
|
|
948
|
-
)}
|
|
993
|
+
{/* 宽屏:最右一列(轨迹详情优先,否则抽屉);关闭时补占位列。
|
|
994
|
+
网格列数与顺序是固定的三列,少一个 item 会让后面的元素落错列。 */}
|
|
995
|
+
{!isNarrow && (rightColumnVisible ? rightColumn : <div aria-hidden="true" />)}
|
|
949
996
|
|
|
950
997
|
{/* 拖拽手柄 */}
|
|
951
998
|
{!isNarrow && !sidebarCollapsed && (
|
|
@@ -955,24 +1002,18 @@ export function AppShell() {
|
|
|
955
1002
|
onPointerDown={onDrag("sidebar")}
|
|
956
1003
|
/>
|
|
957
1004
|
)}
|
|
958
|
-
{!isNarrow &&
|
|
1005
|
+
{!isNarrow && rightColumnVisible && (
|
|
959
1006
|
<div
|
|
960
1007
|
className="fixed top-0 h-full w-2 cursor-col-resize"
|
|
961
|
-
style={{ left: `calc(100vw - ${
|
|
962
|
-
onPointerDown={onDrag("
|
|
1008
|
+
style={{ left: `calc(100vw - ${rightWidth}px - 4px)`, zIndex: 40 }}
|
|
1009
|
+
onPointerDown={onDrag("right")}
|
|
963
1010
|
/>
|
|
964
1011
|
)}
|
|
965
|
-
{
|
|
966
|
-
|
|
967
|
-
className="fixed top-0 h-full w-2 cursor-col-resize"
|
|
968
|
-
style={{ left: (sidebarCollapsed ? SIDEBAR_COLLAPSED : sidebarWidth) + projectWidth - 4, zIndex: 40 }}
|
|
969
|
-
onPointerDown={onDrag("project")}
|
|
970
|
-
/>
|
|
971
|
-
)}
|
|
972
|
-
{isNarrow && projectOpen && (
|
|
1012
|
+
{/* 窄屏最右一列:贴右边滑出(轨迹详情与抽屉共用,遮罩关闭回到对话) */}
|
|
1013
|
+
{isNarrow && rightColumnVisible && (
|
|
973
1014
|
<>
|
|
974
|
-
<button className="fixed inset-0 z-[85]" style={{ background: "var(--dsw-mask)" }} onClick={() =>
|
|
975
|
-
{
|
|
1015
|
+
<button className="fixed inset-0 z-[85]" style={{ background: "var(--dsw-mask)" }} onClick={() => (selected ? setSelected(null) : toggleDrawer(false))} aria-label={t.close} />
|
|
1016
|
+
{rightColumn}
|
|
976
1017
|
</>
|
|
977
1018
|
)}
|
|
978
1019
|
{viewer.state.open && (
|
|
@@ -990,6 +1031,12 @@ export function AppShell() {
|
|
|
990
1031
|
)}
|
|
991
1032
|
|
|
992
1033
|
<ExtensionDialogHost dialog={state.extensionDialogs[0] ?? null} onAnswer={(id, response) => void answerExtensionDialog(id, response)} />
|
|
1034
|
+
{/* 扩展对话框优先:它在阻塞 pi,建仓库的询问可以等 */}
|
|
1035
|
+
{state.growth.consent && !state.extensionDialogs.length && (
|
|
1036
|
+
<GrowthConsentDialog request={state.growth.consent} onAnswer={(allow) => void answerGrowthConsent(allow)} />
|
|
1037
|
+
)}
|
|
1038
|
+
{/* 只有远程设备(手机走 tailnet)会走到这里:本机仍然用系统原生对话框 */}
|
|
1039
|
+
<DirectoryPicker open={dirPickerOpen} onConfirm={(path) => void confirmDirPicker(path)} onCancel={cancelDirPicker} />
|
|
993
1040
|
<ExtensionNotices notices={state.extensionNotices} onDismiss={dismissExtensionNotice} />
|
|
994
1041
|
<SettingsPanel
|
|
995
1042
|
open={settingsOpen}
|