@rexvane/piweb 0.3.14 → 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 +12 -9
- package/.next/build-manifest.json +3 -3
- package/.next/prerender-manifest.json +6 -6
- package/.next/react-loadable-manifest.json +16 -4
- package/.next/required-server-files.js +3 -1
- package/.next/required-server-files.json +3 -1
- 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 +12 -9
- 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 +61 -7
- package/assets/archive.png +0 -0
- package/assets/growth-1.png +0 -0
- package/assets/growth-2.png +0 -0
- package/assets/import.png +0 -0
- package/assets/main.png +0 -0
- package/assets/outline.png +0 -0
- package/assets/prompts-1.png +0 -0
- package/assets/prompts-2.png +0 -0
- package/assets/providers-1.png +0 -0
- package/assets/providers-2.png +0 -0
- package/assets/providers-3.png +0 -0
- package/assets/trace-1.png +0 -0
- package/assets/trace-2.png +0 -0
- package/assets/trace-3.png +0 -0
- package/assets/trace-4.png +0 -0
- package/assets/viewer-1.png +0 -0
- package/assets/viewer-2.png +0 -0
- package/next.config.ts +6 -2
- package/package.json +10 -5
- package/scripts/launcher.mjs +1 -1
- package/scripts/managed-niubash-assets.mjs +146 -0
- package/scripts/pick-folder.ps1 +11 -2
- package/scripts/prepare-niubash-packages.mjs +182 -0
- package/scripts/release.mjs +11 -31
- package/scripts/run-test-file.mjs +34 -0
- package/scripts/smoke-managed-niubash.mjs +62 -0
- package/scripts/verify-package.mjs +53 -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 +53 -37
- 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 +182 -11
- package/src/lib/command-validation.ts +9 -1
- package/src/lib/delegation.ts +93 -0
- package/src/lib/managed-niubash.ts +231 -0
- package/src/lib/pi.ts +3 -2
- package/src/lib/runtime-activity.ts +27 -0
- package/src/lib/session-import/claude.ts +13 -2
- package/src/lib/session-import/codex.ts +44 -8
- package/src/lib/session-import/files.ts +14 -0
- package/src/lib/session-import/grok.ts +54 -24
- package/src/lib/session-import/index.ts +5 -0
- package/src/lib/session-import/lock.ts +25 -0
- package/src/lib/session-import/types.ts +12 -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/LOFl9bqqMV2goBV8erESO/_buildManifest.js +0 -1
- package/.next/static/chunks/2608.ec8ff1f5767c9056.js +0 -1
- package/.next/static/chunks/4632.112d2e893d48d188.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-d28e132e1b8c100c.js +0 -1
- package/.next/static/css/b2d35abc828d4713.css +0 -1
- package/assets/showcase.png +0 -0
- package/bin/pi.js +0 -50
- /package/.next/static/{LOFl9bqqMV2goBV8erESO → 0t1jBUAuVaTFTXxRyIo3q}/_ssgManifest.js +0 -0
|
@@ -34,46 +34,13 @@ export function PiMark({
|
|
|
34
34
|
/** 品牌簇:对齐 dsh 品牌模板 [Logo] [Name] [BADGE] */
|
|
35
35
|
export function BrandPi({ compact = false }: { compact?: boolean }) {
|
|
36
36
|
if (compact) {
|
|
37
|
-
return <PiMark size={18} />;
|
|
37
|
+
return <PiMark size={18} style={{ color: "var(--dsw-accent)" }} />;
|
|
38
38
|
}
|
|
39
39
|
return (
|
|
40
|
-
<div
|
|
41
|
-
className="
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
gap: 6,
|
|
45
|
-
}}
|
|
46
|
-
>
|
|
47
|
-
<PiMark size={17} />
|
|
48
|
-
<span
|
|
49
|
-
className="inline-flex items-center"
|
|
50
|
-
style={{
|
|
51
|
-
fontSize: 16.5,
|
|
52
|
-
fontWeight: 600,
|
|
53
|
-
letterSpacing: "-0.02em",
|
|
54
|
-
color: "var(--dsw-label-primary)",
|
|
55
|
-
lineHeight: 1,
|
|
56
|
-
}}
|
|
57
|
-
>
|
|
58
|
-
pi
|
|
59
|
-
</span>
|
|
60
|
-
<span
|
|
61
|
-
className="inline-flex items-center justify-center font-bold"
|
|
62
|
-
style={{
|
|
63
|
-
fontSize: 9.5,
|
|
64
|
-
letterSpacing: "0.06em",
|
|
65
|
-
height: 15,
|
|
66
|
-
paddingLeft: 4.5,
|
|
67
|
-
paddingRight: 4.5,
|
|
68
|
-
borderRadius: 4,
|
|
69
|
-
background: "var(--dsw-label-primary)",
|
|
70
|
-
color: "var(--dsw-bg-base)",
|
|
71
|
-
lineHeight: 1,
|
|
72
|
-
textTransform: "uppercase",
|
|
73
|
-
}}
|
|
74
|
-
>
|
|
75
|
-
HARNESS
|
|
76
|
-
</span>
|
|
40
|
+
<div className="pw-brand select-none">
|
|
41
|
+
<span className="pw-brand-symbol"><PiMark size={18} style={{ color: "var(--dsw-accent)" }} /></span>
|
|
42
|
+
<span className="pw-brand-name">piweb</span>
|
|
43
|
+
<span className="pw-brand-meta">CODE WORKBENCH</span>
|
|
77
44
|
</div>
|
|
78
45
|
);
|
|
79
46
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* 技能 / 提示模板,含扩展与 npm 包提供的那些);热会话额外给出"组装后的系统提示词"
|
|
7
7
|
* 与每个工具的定义,它们不是文件、没有路径,直接在本面板内展开查看。
|
|
8
8
|
*/
|
|
9
|
-
import { useCallback, useEffect, useState } from "react";
|
|
9
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
10
10
|
import { useI18n } from "@/i18n";
|
|
11
11
|
import { IconRefreshOutline14, IconCloseOutline14 } from "@/components/icons";
|
|
12
12
|
|
|
@@ -67,30 +67,46 @@ export function PromptPanel({
|
|
|
67
67
|
onClose: () => void;
|
|
68
68
|
}) {
|
|
69
69
|
const { t } = useI18n();
|
|
70
|
-
const [
|
|
70
|
+
const owner = JSON.stringify([cwd, sessionId]);
|
|
71
|
+
const [catalog, setCatalog] = useState<{ owner: string; data: PromptSourcesData } | null>(null);
|
|
72
|
+
const data = catalog?.owner === owner ? catalog.data : null;
|
|
71
73
|
const [error, setError] = useState<string | null>(null);
|
|
72
74
|
const [busy, setBusy] = useState(false);
|
|
73
75
|
const [opening, setOpening] = useState<string | null>(null);
|
|
76
|
+
const listRequest = useRef<AbortController | null>(null);
|
|
77
|
+
const readRequest = useRef<AbortController | null>(null);
|
|
74
78
|
|
|
75
79
|
const load = useCallback(async () => {
|
|
80
|
+
listRequest.current?.abort();
|
|
81
|
+
readRequest.current?.abort();
|
|
82
|
+
const controller = new AbortController();
|
|
83
|
+
listRequest.current = controller;
|
|
84
|
+
const current = () => listRequest.current === controller && !controller.signal.aborted;
|
|
85
|
+
setCatalog(null);
|
|
86
|
+
setOpening(null);
|
|
76
87
|
setBusy(true);
|
|
77
88
|
setError(null);
|
|
78
89
|
try {
|
|
79
90
|
const query = new URLSearchParams({ cwd });
|
|
80
91
|
if (sessionId) query.set("session", sessionId);
|
|
81
|
-
const response = await fetch(`/api/prompts?${query.toString()}
|
|
92
|
+
const response = await fetch(`/api/prompts?${query.toString()}`, { signal: controller.signal });
|
|
82
93
|
const body = await response.json();
|
|
94
|
+
if (!current()) return;
|
|
83
95
|
if (!response.ok || !body.success) throw new Error(body.error || `request failed (${response.status})`);
|
|
84
|
-
|
|
96
|
+
setCatalog({ owner, data: body.data as PromptSourcesData });
|
|
85
97
|
} catch (err) {
|
|
86
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
98
|
+
if (current()) setError(err instanceof Error ? err.message : String(err));
|
|
87
99
|
} finally {
|
|
88
|
-
setBusy(false);
|
|
100
|
+
if (current()) setBusy(false);
|
|
89
101
|
}
|
|
90
|
-
}, [cwd, sessionId]);
|
|
102
|
+
}, [cwd, sessionId, owner]);
|
|
91
103
|
|
|
92
104
|
useEffect(() => {
|
|
93
105
|
void load();
|
|
106
|
+
return () => {
|
|
107
|
+
listRequest.current?.abort();
|
|
108
|
+
readRequest.current?.abort();
|
|
109
|
+
};
|
|
94
110
|
}, [load, refreshKey]);
|
|
95
111
|
|
|
96
112
|
const originLabel = (origin: PromptSourceOrigin): string => {
|
|
@@ -115,21 +131,28 @@ export function PromptPanel({
|
|
|
115
131
|
/** 文件类:按需读内容,交给外部查看器 */
|
|
116
132
|
const openFile = async (source: PromptSource) => {
|
|
117
133
|
if (!source.path || !onOpenContent) return;
|
|
134
|
+
readRequest.current?.abort();
|
|
135
|
+
const controller = new AbortController();
|
|
136
|
+
const catalogRequest = listRequest.current;
|
|
137
|
+
readRequest.current = controller;
|
|
138
|
+
const current = () => readRequest.current === controller && !controller.signal.aborted && listRequest.current === catalogRequest;
|
|
118
139
|
setOpening(source.path);
|
|
119
140
|
setError(null);
|
|
120
141
|
try {
|
|
121
142
|
const response = await fetch("/api/prompts", {
|
|
122
143
|
method: "POST",
|
|
144
|
+
signal: controller.signal,
|
|
123
145
|
headers: { "Content-Type": "application/json" },
|
|
124
146
|
body: JSON.stringify({ action: "read", filePath: source.path, cwd }),
|
|
125
147
|
});
|
|
126
148
|
const body = await response.json();
|
|
149
|
+
if (!current()) return;
|
|
127
150
|
if (!response.ok || !body.success) throw new Error(body.error || "read failed");
|
|
128
151
|
onOpenContent(source.path, body.data.content as string);
|
|
129
152
|
} catch (err) {
|
|
130
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
153
|
+
if (current()) setError(err instanceof Error ? err.message : String(err));
|
|
131
154
|
} finally {
|
|
132
|
-
setOpening(null);
|
|
155
|
+
if (current()) setOpening(null);
|
|
133
156
|
}
|
|
134
157
|
};
|
|
135
158
|
|
|
@@ -138,50 +161,94 @@ export function PromptPanel({
|
|
|
138
161
|
);
|
|
139
162
|
const hasTools = (data?.tools?.length ?? 0) > 0;
|
|
140
163
|
|
|
164
|
+
const fileExtBadge = (name: string): string => {
|
|
165
|
+
const ext = name.split(".").pop()?.toUpperCase() ?? "DOC";
|
|
166
|
+
return ext.length <= 4 ? ext : "DOC";
|
|
167
|
+
};
|
|
168
|
+
|
|
141
169
|
return (
|
|
142
|
-
<div className="flex min-h-0 flex-1 flex-col">
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
<
|
|
146
|
-
<
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
170
|
+
<div className="flex min-h-0 flex-1 flex-col p-2 select-none">
|
|
171
|
+
{/* Right Header (Screen 2 Claymorphism) */}
|
|
172
|
+
<div className="flex items-center justify-between pb-3 mb-3 border-b border-slate-200 dark:border-slate-800 px-2">
|
|
173
|
+
<div className="flex items-center gap-2">
|
|
174
|
+
<div className="w-2.5 h-2.5 rounded-full bg-sky-500 animate-pulse" />
|
|
175
|
+
<h2 className="text-sm font-bold text-slate-900 dark:text-slate-100 tracking-tight">{t.promptPanel}</h2>
|
|
176
|
+
</div>
|
|
177
|
+
<div className="flex items-center gap-1.5 text-slate-500">
|
|
178
|
+
<button
|
|
179
|
+
type="button"
|
|
180
|
+
className="w-7 h-7 clay-btn bg-white dark:bg-slate-800 text-slate-700 hover:text-slate-950 dark:text-slate-300 dark:hover:text-white transition-colors shadow-sm"
|
|
181
|
+
title={t.promptRefresh}
|
|
182
|
+
aria-label={t.promptRefresh}
|
|
183
|
+
onClick={() => void load()}
|
|
184
|
+
>
|
|
185
|
+
<span className={busy ? "piweb-spin" : undefined} style={{ display: "inline-flex" }}>
|
|
186
|
+
<IconRefreshOutline14 size={13} />
|
|
187
|
+
</span>
|
|
188
|
+
</button>
|
|
189
|
+
<button
|
|
190
|
+
type="button"
|
|
191
|
+
className="w-7 h-7 clay-btn bg-white dark:bg-slate-800 text-slate-700 hover:text-rose-600 dark:text-slate-300 transition-colors shadow-sm"
|
|
192
|
+
title={t.close}
|
|
193
|
+
aria-label={t.close}
|
|
194
|
+
onClick={onClose}
|
|
195
|
+
>
|
|
196
|
+
<IconCloseOutline14 size={13} />
|
|
197
|
+
</button>
|
|
198
|
+
</div>
|
|
153
199
|
</div>
|
|
154
|
-
|
|
200
|
+
|
|
201
|
+
<div className="min-h-0 flex-1 overflow-y-auto px-1 space-y-4 text-xs">
|
|
155
202
|
{error && (
|
|
156
|
-
<div className="
|
|
203
|
+
<div className="rounded-xl px-3 py-2 bg-rose-50 text-rose-700 dark:bg-rose-950/40 dark:text-rose-300 font-semibold" role="alert">
|
|
157
204
|
{error}
|
|
158
205
|
</div>
|
|
159
206
|
)}
|
|
160
|
-
|
|
207
|
+
|
|
208
|
+
{!data && busy && <div className="text-slate-500 py-4 text-center">{t.promptLoading}</div>}
|
|
209
|
+
|
|
161
210
|
{data && !data.sessionReady && (
|
|
162
|
-
<div className="
|
|
211
|
+
<div className="rounded-xl px-3 py-2.5 clay-inset text-slate-600 dark:text-slate-300 leading-relaxed text-xs" role="status">
|
|
163
212
|
{t.promptColdHint}
|
|
164
213
|
</div>
|
|
165
214
|
)}
|
|
166
215
|
|
|
167
216
|
{groups.map((group) => (
|
|
168
|
-
<section key={group.kind} className="
|
|
169
|
-
<div className="
|
|
170
|
-
|
|
217
|
+
<section key={group.kind} className="space-y-1.5">
|
|
218
|
+
<div className="flex items-center justify-between text-xs font-bold text-slate-800 dark:text-slate-200 px-1 mb-1">
|
|
219
|
+
<span>{kindLabel(group.kind)}</span>
|
|
220
|
+
<span className="text-[10px] text-slate-500 dark:text-slate-400 font-mono font-semibold">{group.items.length} 项</span>
|
|
221
|
+
</div>
|
|
222
|
+
<div className="flex flex-col gap-1.5">
|
|
171
223
|
{group.items.map((source) => (
|
|
172
224
|
<button
|
|
173
225
|
key={`${source.kind}:${source.path ?? source.name}`}
|
|
174
226
|
type="button"
|
|
175
|
-
className=
|
|
176
|
-
|
|
227
|
+
className={`clay-card p-3 flex items-center justify-between hover:scale-[1.01] transition-all cursor-pointer rounded-2xl text-left bg-white dark:bg-slate-900 shadow-sm border border-slate-200 dark:border-slate-800 ${
|
|
228
|
+
opening === source.path ? "ring-2 ring-emerald-500" : ""
|
|
229
|
+
}`}
|
|
177
230
|
title={source.path ?? source.name}
|
|
178
231
|
onClick={() => void openFile(source)}
|
|
179
232
|
>
|
|
180
|
-
<
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
233
|
+
<div className="flex items-center gap-2.5 min-w-0 flex-1 mr-2">
|
|
234
|
+
<div className="w-7 h-7 clay-inset bg-emerald-50 text-emerald-700 dark:bg-emerald-950/70 dark:text-emerald-300 flex items-center justify-center rounded-xl font-mono font-bold text-[10px] flex-none">
|
|
235
|
+
{fileExtBadge(source.name)}
|
|
236
|
+
</div>
|
|
237
|
+
<div className="min-w-0 flex-1">
|
|
238
|
+
<div className="text-xs font-bold text-slate-900 dark:text-slate-100 truncate">{source.name}</div>
|
|
239
|
+
<div className="text-[11px] text-slate-600 dark:text-slate-400 truncate">{source.path ?? kindLabel(source.kind)}</div>
|
|
240
|
+
</div>
|
|
241
|
+
</div>
|
|
242
|
+
<div className="text-right flex-none flex flex-col items-end gap-0.5">
|
|
243
|
+
{source.bytes !== undefined && (
|
|
244
|
+
<span className="text-[11px] font-mono font-bold text-slate-800 dark:text-slate-200 block">
|
|
245
|
+
{formatBytes(source.bytes)}
|
|
246
|
+
</span>
|
|
247
|
+
)}
|
|
248
|
+
<span className="text-[10px] px-2 py-0.5 rounded-full bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300 font-semibold border border-slate-200 dark:border-slate-700">
|
|
249
|
+
{originLabel(source.origin)}
|
|
250
|
+
</span>
|
|
251
|
+
</div>
|
|
185
252
|
</button>
|
|
186
253
|
))}
|
|
187
254
|
</div>
|
|
@@ -189,15 +256,17 @@ export function PromptPanel({
|
|
|
189
256
|
))}
|
|
190
257
|
|
|
191
258
|
{hasTools && (
|
|
192
|
-
<section className="
|
|
193
|
-
<div className="
|
|
194
|
-
|
|
259
|
+
<section className="space-y-1.5">
|
|
260
|
+
<div className="flex items-center justify-between text-xs font-bold text-slate-800 dark:text-slate-200 px-1 mb-1">
|
|
261
|
+
<span>{t.promptKindTool}</span>
|
|
262
|
+
<span className="text-[10px] text-slate-500 dark:text-slate-400 font-mono font-semibold">{data!.tools!.length} 个工具</span>
|
|
263
|
+
</div>
|
|
264
|
+
<div className="flex flex-col gap-1.5">
|
|
195
265
|
{data!.tools!.map((tool) => (
|
|
196
266
|
<button
|
|
197
267
|
key={`tool:${tool.name}`}
|
|
198
268
|
type="button"
|
|
199
|
-
className="
|
|
200
|
-
style={{ border: "0.5px solid var(--dsw-border-l2)" }}
|
|
269
|
+
className="clay-card p-3 flex items-center justify-between hover:scale-[1.01] transition-all cursor-pointer rounded-2xl text-left bg-white dark:bg-slate-900 shadow-sm border border-slate-200 dark:border-slate-800"
|
|
201
270
|
title={tool.name}
|
|
202
271
|
onClick={() =>
|
|
203
272
|
onOpenContent?.(
|
|
@@ -214,8 +283,16 @@ export function PromptPanel({
|
|
|
214
283
|
)
|
|
215
284
|
}
|
|
216
285
|
>
|
|
217
|
-
<
|
|
218
|
-
|
|
286
|
+
<div className="flex items-center gap-2.5 min-w-0 flex-1 mr-2">
|
|
287
|
+
<div className="w-7 h-7 clay-inset bg-sky-50 text-sky-700 dark:bg-sky-950/70 dark:text-sky-300 flex items-center justify-center rounded-xl font-mono font-bold text-[10px] flex-none">
|
|
288
|
+
TOOL
|
|
289
|
+
</div>
|
|
290
|
+
<div className="min-w-0 flex-1">
|
|
291
|
+
<div className="text-xs font-bold text-slate-900 dark:text-slate-100 truncate">{tool.name}</div>
|
|
292
|
+
<div className="text-[11px] text-slate-600 dark:text-slate-400 truncate">{tool.description || t.promptKindTool}</div>
|
|
293
|
+
</div>
|
|
294
|
+
</div>
|
|
295
|
+
<span className="text-[10px] px-2 py-0.5 rounded-full bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300 font-semibold border border-slate-200 dark:border-slate-700 flex-none">
|
|
219
296
|
{tool.source === "builtin" ? t.promptToolBuiltin : t.promptToolExtension}
|
|
220
297
|
</span>
|
|
221
298
|
</button>
|
|
@@ -225,31 +302,35 @@ export function PromptPanel({
|
|
|
225
302
|
)}
|
|
226
303
|
|
|
227
304
|
{data?.assembledSystemPrompt && (
|
|
228
|
-
<section className="
|
|
229
|
-
<div className="
|
|
305
|
+
<section className="space-y-1.5">
|
|
306
|
+
<div className="text-xs font-bold text-slate-800 dark:text-slate-200 px-1 mb-1">{t.promptKindAssembled}</div>
|
|
230
307
|
<button
|
|
231
308
|
type="button"
|
|
232
|
-
className="w-full
|
|
233
|
-
style={{ border: "0.5px solid var(--dsw-border-l2)" }}
|
|
309
|
+
className="w-full clay-card p-3 flex items-center gap-2.5 hover:scale-[1.01] transition-all cursor-pointer rounded-2xl text-left bg-white dark:bg-slate-900 shadow-sm border border-slate-200 dark:border-slate-800"
|
|
234
310
|
title={t.promptAssembledKey}
|
|
235
311
|
onClick={() => onOpenContent?.("system-prompt.md", data.assembledSystemPrompt ?? "")}
|
|
236
312
|
>
|
|
237
|
-
<
|
|
313
|
+
<div className="w-7 h-7 clay-inset bg-emerald-50 text-emerald-700 dark:bg-emerald-950/70 dark:text-emerald-300 flex items-center justify-center rounded-xl font-bold text-sm flex-none">
|
|
314
|
+
π
|
|
315
|
+
</div>
|
|
316
|
+
<span className="truncate font-bold text-slate-900 dark:text-slate-100 text-xs">{t.promptAssembledKey}</span>
|
|
238
317
|
</button>
|
|
239
318
|
</section>
|
|
240
319
|
)}
|
|
241
320
|
|
|
242
321
|
{data?.compactedSummary && (
|
|
243
|
-
<section className="
|
|
244
|
-
<div className="
|
|
322
|
+
<section className="space-y-1.5">
|
|
323
|
+
<div className="text-xs font-bold text-slate-800 dark:text-slate-200 px-1 mb-1">{t.promptKindCompacted}</div>
|
|
245
324
|
<button
|
|
246
325
|
type="button"
|
|
247
|
-
className="w-full
|
|
248
|
-
style={{ border: "0.5px solid var(--dsw-border-l2)" }}
|
|
326
|
+
className="w-full clay-card p-3 flex items-center gap-2.5 hover:scale-[1.01] transition-all cursor-pointer rounded-2xl text-left bg-white dark:bg-slate-900 shadow-sm border border-slate-200 dark:border-slate-800"
|
|
249
327
|
title={t.promptCompactedKey}
|
|
250
328
|
onClick={() => onOpenContent?.("compacted-summary.md", data.compactedSummary ?? "")}
|
|
251
329
|
>
|
|
252
|
-
<
|
|
330
|
+
<div className="w-7 h-7 clay-inset bg-amber-50 text-amber-700 dark:bg-amber-950/70 dark:text-amber-300 flex items-center justify-center rounded-xl font-bold text-xs flex-none">
|
|
331
|
+
SUM
|
|
332
|
+
</div>
|
|
333
|
+
<span className="truncate font-bold text-slate-900 dark:text-slate-100 text-xs">{t.promptCompactedKey}</span>
|
|
253
334
|
</button>
|
|
254
335
|
</section>
|
|
255
336
|
)}
|
|
@@ -36,6 +36,8 @@ interface ImportOutcome {
|
|
|
36
36
|
reason?: string;
|
|
37
37
|
messageCount?: number;
|
|
38
38
|
workspace?: string;
|
|
39
|
+
/** 已成功导入,但源里的运行时注入/加密内容等无法作为对话还原 */
|
|
40
|
+
skipped?: string[];
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
interface ImportReport {
|
|
@@ -60,6 +62,8 @@ function dayOf(ms?: number): string {
|
|
|
60
62
|
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
61
63
|
}
|
|
62
64
|
|
|
65
|
+
const summaryKey = (summary: Pick<Summary, "source" | "externalId">) => `${summary.source}:${summary.externalId}`;
|
|
66
|
+
|
|
63
67
|
export function SessionImportSection({ cwd }: { cwd: string }) {
|
|
64
68
|
const { t } = useI18n();
|
|
65
69
|
const [summaries, setSummaries] = useState<Summary[]>([]);
|
|
@@ -83,9 +87,14 @@ export function SessionImportSection({ cwd }: { cwd: string }) {
|
|
|
83
87
|
setFailure(json.error || t.toastError);
|
|
84
88
|
return;
|
|
85
89
|
}
|
|
86
|
-
|
|
90
|
+
const nextSummaries = (json.data.sessions ?? []) as Summary[];
|
|
91
|
+
const nextImported = new Set<string>(json.data.imported ?? []);
|
|
92
|
+
const selectable = new Set(nextSummaries.map(summaryKey));
|
|
93
|
+
setSummaries(nextSummaries);
|
|
87
94
|
setErrors(json.data.errors ?? []);
|
|
88
|
-
setImportedKeys(
|
|
95
|
+
setImportedKeys(nextImported);
|
|
96
|
+
// 重新扫描后,源会话可能已经消失或被另一个请求导入;不要留下幽灵选择
|
|
97
|
+
setSelected((previous) => new Set([...previous].filter((item) => selectable.has(item) && !nextImported.has(item))));
|
|
89
98
|
} catch (error) {
|
|
90
99
|
setFailure(error instanceof Error ? error.message : String(error));
|
|
91
100
|
} finally {
|
|
@@ -109,8 +118,6 @@ export function SessionImportSection({ cwd }: { cwd: string }) {
|
|
|
109
118
|
void scan();
|
|
110
119
|
}, [scan]);
|
|
111
120
|
|
|
112
|
-
const key = (summary: Summary) => `${summary.source}:${summary.externalId}`;
|
|
113
|
-
|
|
114
121
|
const groups = useMemo(() => {
|
|
115
122
|
const bySource = new Map<string, Summary[]>();
|
|
116
123
|
for (const summary of summaries) {
|
|
@@ -120,27 +127,30 @@ export function SessionImportSection({ cwd }: { cwd: string }) {
|
|
|
120
127
|
}
|
|
121
128
|
return SOURCE_ORDER.filter((source) => bySource.has(source)).map((source) => {
|
|
122
129
|
const rows = [...(bySource.get(source) as Summary[])].sort((left, right) => (right.updatedAt ?? right.createdAt ?? 0) - (left.updatedAt ?? left.createdAt ?? 0));
|
|
123
|
-
return { source, rows, imported: rows.filter((row) => importedKeys.has(
|
|
130
|
+
return { source, rows, imported: rows.filter((row) => importedKeys.has(summaryKey(row))).length };
|
|
124
131
|
});
|
|
125
132
|
}, [summaries, importedKeys]);
|
|
126
133
|
|
|
127
134
|
/**
|
|
128
135
|
* 一次只显示一个来源:六个工具各列 15 条就是 90 行,全铺出来既难找也没必要。
|
|
129
|
-
*
|
|
136
|
+
* 首次打开默认落到“最近活动会话”所在的来源;用户切换后保持选择。
|
|
137
|
+
* 直接派生有效来源,不用 effect 回填,避免初始化 effect 覆盖用户刚完成的点击。
|
|
130
138
|
*/
|
|
131
139
|
const [activeSource, setActiveSource] = useState("");
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
140
|
+
const defaultSource = groups.reduce<{ source: string; at: number } | null>((latest, group) => {
|
|
141
|
+
const first = group.rows[0];
|
|
142
|
+
const at = first?.updatedAt ?? first?.createdAt ?? 0;
|
|
143
|
+
return !latest || at > latest.at ? { source: group.source, at } : latest;
|
|
144
|
+
}, null)?.source ?? "";
|
|
145
|
+
const visibleSource = activeSource && groups.some((group) => group.source === activeSource) ? activeSource : defaultSource;
|
|
146
|
+
const activeGroup = groups.find((group) => group.source === visibleSource);
|
|
137
147
|
|
|
138
148
|
const toggle = (summary: Summary) => {
|
|
139
|
-
if (importedKeys.has(
|
|
149
|
+
if (importedKeys.has(summaryKey(summary))) return;
|
|
140
150
|
setSelected((previous) => {
|
|
141
151
|
const next = new Set(previous);
|
|
142
|
-
if (next.has(
|
|
143
|
-
else next.add(
|
|
152
|
+
if (next.has(summaryKey(summary))) next.delete(summaryKey(summary));
|
|
153
|
+
else next.add(summaryKey(summary));
|
|
144
154
|
return next;
|
|
145
155
|
});
|
|
146
156
|
};
|
|
@@ -149,9 +159,9 @@ export function SessionImportSection({ cwd }: { cwd: string }) {
|
|
|
149
159
|
setSelected((previous) => {
|
|
150
160
|
const next = new Set(previous);
|
|
151
161
|
for (const row of rows) {
|
|
152
|
-
if (importedKeys.has(
|
|
153
|
-
if (checked) next.add(
|
|
154
|
-
else next.delete(
|
|
162
|
+
if (importedKeys.has(summaryKey(row))) continue;
|
|
163
|
+
if (checked) next.add(summaryKey(row));
|
|
164
|
+
else next.delete(summaryKey(row));
|
|
155
165
|
}
|
|
156
166
|
return next;
|
|
157
167
|
});
|
|
@@ -159,7 +169,7 @@ export function SessionImportSection({ cwd }: { cwd: string }) {
|
|
|
159
169
|
|
|
160
170
|
const run = async () => {
|
|
161
171
|
if (!selected.size) return;
|
|
162
|
-
const items = summaries.filter((summary) => selected.has(
|
|
172
|
+
const items = summaries.filter((summary) => selected.has(summaryKey(summary))).map((summary) => ({ source: summary.source, externalId: summary.externalId }));
|
|
163
173
|
setImporting(true);
|
|
164
174
|
setFailure(null);
|
|
165
175
|
setReport(null);
|
|
@@ -198,6 +208,7 @@ export function SessionImportSection({ cwd }: { cwd: string }) {
|
|
|
198
208
|
|
|
199
209
|
const failures = report?.results.filter((result) => result.status === "failed") ?? [];
|
|
200
210
|
const skipped = report?.results.filter((result) => result.status === "skipped") ?? [];
|
|
211
|
+
const importedWithNotes = report?.results.filter((result) => result.status === "imported" && result.skipped?.length) ?? [];
|
|
201
212
|
|
|
202
213
|
return (
|
|
203
214
|
<div className="flex flex-col gap-4" data-testid="session-import">
|
|
@@ -277,6 +288,11 @@ export function SessionImportSection({ cwd }: { cwd: string }) {
|
|
|
277
288
|
{sourceLabel(result.source)} · {result.title || result.externalId} — {result.reason}
|
|
278
289
|
</div>
|
|
279
290
|
))}
|
|
291
|
+
{importedWithNotes.map((result) => (
|
|
292
|
+
<div key={`note-${result.source}-${result.externalId}`} className="mt-1" style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }}>
|
|
293
|
+
{sourceLabel(result.source)} · {result.title || result.externalId} — {result.skipped?.join(";")}
|
|
294
|
+
</div>
|
|
295
|
+
))}
|
|
280
296
|
{failures.map((result) => (
|
|
281
297
|
<div key={`fail-${result.source}-${result.externalId}`} className="mt-1" style={{ fontSize: 11.5, color: "var(--dsw-danger)" }}>
|
|
282
298
|
{sourceLabel(result.source)} · {result.title || result.externalId} — {result.reason}
|
|
@@ -289,27 +305,25 @@ export function SessionImportSection({ cwd }: { cwd: string }) {
|
|
|
289
305
|
|
|
290
306
|
{/* 来源选择器:选中哪个就只列哪个工具的会话 */}
|
|
291
307
|
{groups.length > 0 && (
|
|
292
|
-
<div className="flex flex-wrap items-center gap-1.5" role="tablist" aria-label={t.importPickSource} data-testid="import-source-tabs">
|
|
308
|
+
<div className="flex flex-wrap items-center gap-2 p-1.5 rounded-2xl bg-slate-100/80 dark:bg-slate-900/60" role="tablist" aria-label={t.importPickSource} data-testid="import-source-tabs">
|
|
293
309
|
{groups.map((group) => {
|
|
294
|
-
const active = group.source ===
|
|
310
|
+
const active = group.source === visibleSource;
|
|
295
311
|
return (
|
|
296
312
|
<button
|
|
297
313
|
key={group.source}
|
|
298
314
|
type="button"
|
|
299
315
|
role="tab"
|
|
300
316
|
aria-selected={active}
|
|
301
|
-
className=
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
color: active ? "#fff" : "var(--dsw-label-tertiary)",
|
|
307
|
-
}}
|
|
317
|
+
className={`clay-btn rounded-xl px-3.5 py-1.5 transition-all text-xs font-medium flex items-center gap-1.5 ${
|
|
318
|
+
active
|
|
319
|
+
? "clay-btn-peach text-white shadow-sm font-semibold"
|
|
320
|
+
: "clay-btn-soft text-slate-600 dark:text-slate-300"
|
|
321
|
+
}`}
|
|
308
322
|
onClick={() => setActiveSource(group.source)}
|
|
309
323
|
data-testid={`import-source-${group.source}`}
|
|
310
324
|
>
|
|
311
|
-
{sourceLabel(group.source)}
|
|
312
|
-
<span
|
|
325
|
+
<span>{sourceLabel(group.source)}</span>
|
|
326
|
+
<span className={`text-[10px] px-1.5 py-0.2 rounded-full ${active ? "bg-white/30 text-white" : "bg-slate-200 dark:bg-slate-700 text-slate-500"}`}>{group.rows.length}</span>
|
|
313
327
|
</button>
|
|
314
328
|
);
|
|
315
329
|
})}
|
|
@@ -323,35 +337,37 @@ export function SessionImportSection({ cwd }: { cwd: string }) {
|
|
|
323
337
|
{t.importGroupCount.replace("{n}", String(activeGroup.rows.length)).replace("{imported}", String(activeGroup.imported))}
|
|
324
338
|
</span>
|
|
325
339
|
<div className="flex-1" />
|
|
326
|
-
<button className="rounded-lg px-2 py-
|
|
340
|
+
<button className="clay-btn clay-btn-soft rounded-lg px-2.5 py-1 text-xs" onClick={() => selectGroup(activeGroup.rows, true)}>
|
|
327
341
|
{t.importSelectAll}
|
|
328
342
|
</button>
|
|
329
|
-
<button className="rounded-lg px-2 py-
|
|
343
|
+
<button className="clay-btn clay-btn-soft rounded-lg px-2.5 py-1 text-xs text-slate-400" onClick={() => selectGroup(activeGroup.rows, false)}>
|
|
330
344
|
{t.importSelectNone}
|
|
331
345
|
</button>
|
|
332
346
|
</div>
|
|
333
347
|
<div className="flex flex-col gap-1.5">
|
|
334
348
|
{activeGroup.rows.map((row) => {
|
|
335
|
-
const rowKey =
|
|
349
|
+
const rowKey = summaryKey(row);
|
|
336
350
|
const done = importedKeys.has(rowKey);
|
|
337
351
|
const checked = selected.has(rowKey);
|
|
338
352
|
return (
|
|
339
353
|
<label
|
|
340
354
|
key={rowKey}
|
|
341
|
-
className=
|
|
342
|
-
|
|
355
|
+
className={`clay-card flex cursor-pointer items-center gap-3 rounded-2xl px-3.5 py-2.5 transition-all hover:translate-x-0.5 ${
|
|
356
|
+
checked ? "ring-2 ring-emerald-500 bg-white dark:bg-slate-800" : "bg-white/70 dark:bg-slate-900/60"
|
|
357
|
+
}`}
|
|
358
|
+
style={{ opacity: done ? 0.55 : 1 }}
|
|
343
359
|
>
|
|
344
360
|
<input type="checkbox" checked={checked} disabled={done} onChange={() => toggle(row)} />
|
|
345
361
|
<div className="min-w-0 flex-1">
|
|
346
|
-
<div className="truncate
|
|
362
|
+
<div className="truncate text-xs font-medium text-slate-800 dark:text-slate-100" title={row.title}>
|
|
347
363
|
{row.title || t.importUntitled}
|
|
348
364
|
</div>
|
|
349
|
-
<div className="truncate
|
|
365
|
+
<div className="truncate text-[11px] text-slate-400" title={row.projectPath}>
|
|
350
366
|
{row.projectPath || t.importNoProject}
|
|
351
367
|
</div>
|
|
352
368
|
</div>
|
|
353
369
|
{done && (
|
|
354
|
-
<span className="rounded-full px-2 py-0.5
|
|
370
|
+
<span className="clay-badge rounded-full px-2 py-0.5 text-[10px] bg-slate-100 text-slate-400">
|
|
355
371
|
{t.importAlready}
|
|
356
372
|
</span>
|
|
357
373
|
)}
|