@rexvane/piweb 0.3.11 → 0.3.13
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 +6 -6
- package/.next/prerender-manifest.json +7 -7
- package/.next/react-loadable-manifest.json +7 -7
- 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 +1 -1
- package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- 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/files/route.js +1 -1
- package/.next/server/app/api/git/route.js +2 -2
- 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 -0
- package/.next/server/app/api/prompts/route.js.nft.json +1 -0
- package/.next/server/app/api/prompts/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 +6 -0
- package/.next/server/app/api/sessions/import/route.js.nft.json +1 -0
- package/.next/server/app/api/sessions/import/route_client-reference-manifest.js +1 -0
- package/.next/server/app/api/sessions/route.js +1 -1
- package/.next/server/app/api/sessions/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/route.js +2 -2
- package/.next/server/app/api/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/update/route.js +2 -2
- package/.next/server/app/api/update/route.js.nft.json +1 -1
- package/.next/server/app/api/version/route.js +1 -1
- package/.next/server/app/api/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 +3 -3
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/index.segments/_full.segment.rsc +3 -3
- package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- 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 +2 -2
- package/.next/server/app/login.segments/_full.segment.rsc +2 -2
- package/.next/server/app/login.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/page.js +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +8 -6
- package/.next/server/chunks/496.js +1 -1
- package/.next/server/chunks/532.js +1 -0
- package/.next/server/chunks/686.js +2 -2
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/2155.2f27fe7098987699.js +1 -0
- package/.next/static/chunks/2608.ec8ff1f5767c9056.js +1 -0
- package/.next/static/chunks/2716.a9e808190a4e2424.js +1 -0
- package/.next/static/chunks/{794-d1a40dcf24d651d9.js → 3794-3679f1c05160977c.js} +1 -1
- package/.next/static/chunks/4632.d574c210ad78d065.js +1 -0
- package/.next/static/chunks/4673.d3d0a7f2599080fb.js +1 -0
- package/.next/static/chunks/4686-0a4155f8488314b8.js +1 -0
- package/.next/static/chunks/4bd1b696-93bf506b236d6248.js +1 -0
- package/.next/static/chunks/app/_global-error/page-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/_not-found/page-2d40d1d7d916666a.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/events/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/agent/[id]/tree/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/agent/new/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/config/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/files/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/git/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/growth/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/health/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/models/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/pi-settings/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/plugins/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/prompts/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/security/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/export/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/sessions/[id]/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/sessions/import/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/sessions/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/skills/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/update/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/version/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/web-auth/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/api/workspaces/route-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/app/layout-50d06e2078d6ce76.js +1 -0
- package/.next/static/chunks/app/login/page-52ef4841cbd199a8.js +1 -0
- package/.next/static/chunks/app/page-9a5440047c5a415c.js +1 -0
- package/.next/static/chunks/framework-c4e651fe89edfa91.js +1 -0
- package/.next/static/chunks/{main-a315d7c58b599962.js → main-6159b22a8cb0b7c5.js} +2 -2
- package/.next/static/chunks/main-app-e9b53f0e1c6591b2.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/global-error-a7e260f8cc2582d7.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-f77e9ede9808b18e.js +1 -0
- package/.next/static/chunks/webpack-b1a572e319353f5d.js +1 -0
- package/.next/static/olV7jxKRN5VhfEK012WhV/_buildManifest.js +1 -0
- package/.next/types/app/api/prompts/route.ts +353 -0
- package/.next/types/app/api/sessions/import/route.ts +353 -0
- package/.next/types/routes.d.ts +3 -1
- package/.next/types/validator.ts +18 -0
- package/README.md +4 -0
- package/package.json +1 -1
- package/src/app/api/prompts/route.ts +51 -0
- package/src/app/api/sessions/import/route.ts +74 -0
- package/src/components/AppShell.tsx +42 -19
- package/src/components/ChatWindow.tsx +20 -35
- package/src/components/PromptPanel.tsx +259 -0
- package/src/components/SessionImportSection.tsx +333 -0
- package/src/components/SettingsPanel.tsx +63 -57
- package/src/i18n.tsx +98 -4
- package/src/lib/agent-manager.ts +35 -8
- package/src/lib/clipboard.ts +27 -0
- package/src/lib/command-validation.ts +1 -0
- package/src/lib/prompt-sources.ts +229 -0
- package/src/lib/session-import/claude.ts +219 -0
- package/src/lib/session-import/codex.ts +206 -0
- package/src/lib/session-import/dsh-zstd.ts +69 -0
- package/src/lib/session-import/dsh.ts +248 -0
- package/src/lib/session-import/files.ts +84 -0
- package/src/lib/session-import/grok.ts +166 -0
- package/src/lib/session-import/index.ts +229 -0
- package/src/lib/session-import/oc-family.ts +275 -0
- package/src/lib/session-import/opencode.ts +7 -0
- package/src/lib/session-import/paths.ts +37 -0
- package/src/lib/session-import/sqlite.ts +89 -0
- package/src/lib/session-import/types.ts +171 -0
- package/src/lib/session-import/writer.ts +259 -0
- package/src/lib/session-import/zcode.ts +8 -0
- package/src/lib/types.ts +1 -0
- package/.next/server/chunks/118.js +0 -1
- package/.next/static/222KLUIvefiGf7M0Ov-Qz/_buildManifest.js +0 -1
- package/.next/static/chunks/127.c31e688e156605e3.js +0 -1
- package/.next/static/chunks/155.a3c40ae481da1358.js +0 -1
- package/.next/static/chunks/4bd1b696-92152b0f5947070d.js +0 -1
- package/.next/static/chunks/608.5b8aae42a9998ee7.js +0 -1
- package/.next/static/chunks/673.93445c8600bc1dd9.js +0 -1
- package/.next/static/chunks/686-9f15e4e370d26815.js +0 -1
- package/.next/static/chunks/716.5266fe10b5578591.js +0 -1
- package/.next/static/chunks/app/_global-error/page-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/_not-found/page-9de30f6f614fce02.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/events/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/agent/[id]/tree/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/agent/new/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/config/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/files/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/git/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/growth/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/health/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/models/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/pi-settings/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/plugins/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/security/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/export/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/sessions/[id]/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/sessions/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/skills/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/update/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/version/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/web-auth/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/api/workspaces/route-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/app/layout-eb62bcd99abe79f9.js +0 -1
- package/.next/static/chunks/app/login/page-d2308b48d013d1a6.js +0 -1
- package/.next/static/chunks/app/page-708cd3ee4cfe8af5.js +0 -1
- package/.next/static/chunks/framework-0f597d6e68ffafed.js +0 -1
- package/.next/static/chunks/main-app-1a92e20ec190a32b.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/app-error-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/forbidden-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/global-error-7bb5c4afcbc1772b.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/not-found-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/next/dist/client/components/builtin/unauthorized-1dac1b3ca8adb975.js +0 -1
- package/.next/static/chunks/webpack-9d84b382d4dec27a.js +0 -1
- /package/.next/static/{222KLUIvefiGf7M0Ov-Qz → olV7jxKRN5VhfEK012WhV}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 设置 → 导入会话:把本机其它 AI 编程工具的对话无损导成 pi 会话。
|
|
5
|
+
*
|
|
6
|
+
* 交互上刻意保守:**默认一条都不勾**(本机 ZCode 421 条、opencode 860 条,一键全选就是事故),
|
|
7
|
+
* 已导过的行标出来并且不能再勾。写入去向由服务端决定(源项目还在就导回原工作区),
|
|
8
|
+
* 这里只负责在源项目已不在本机时给一个兜底工作区——从已知工作区里选,不允许手输路径。
|
|
9
|
+
*/
|
|
10
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
11
|
+
import { useI18n } from "@/i18n";
|
|
12
|
+
import { IconRefreshOutline14, IconDownloadOutline16 } from "@/components/icons";
|
|
13
|
+
|
|
14
|
+
interface Summary {
|
|
15
|
+
source: string;
|
|
16
|
+
externalId: string;
|
|
17
|
+
title?: string;
|
|
18
|
+
projectPath?: string;
|
|
19
|
+
provider?: string;
|
|
20
|
+
model?: string;
|
|
21
|
+
createdAt?: number;
|
|
22
|
+
updatedAt?: number;
|
|
23
|
+
messageCount?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface SourceError {
|
|
27
|
+
source: string;
|
|
28
|
+
message: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface ImportOutcome {
|
|
32
|
+
source: string;
|
|
33
|
+
externalId: string;
|
|
34
|
+
title?: string;
|
|
35
|
+
status: "imported" | "skipped" | "failed";
|
|
36
|
+
reason?: string;
|
|
37
|
+
messageCount?: number;
|
|
38
|
+
workspace?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface ImportReport {
|
|
42
|
+
imported: number;
|
|
43
|
+
skipped: number;
|
|
44
|
+
failed: number;
|
|
45
|
+
results: ImportOutcome[];
|
|
46
|
+
workspaces: string[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 来源中文名/英文名放在 i18n 里,这里只保留稳定的顺序与徽标色 */
|
|
50
|
+
const SOURCE_ORDER = ["claude", "codex", "grok", "zcode", "dsh", "opencode"] as const;
|
|
51
|
+
|
|
52
|
+
/** 与 src/lib/session-import/index.ts 的 SCAN_LIMIT_PER_SOURCE 保持一致(仅用于提示文案) */
|
|
53
|
+
const SCAN_LIMIT = 15;
|
|
54
|
+
|
|
55
|
+
function dayOf(ms?: number): string {
|
|
56
|
+
if (!ms) return "";
|
|
57
|
+
const d = new Date(ms);
|
|
58
|
+
if (Number.isNaN(d.getTime())) return "";
|
|
59
|
+
const pad = (value: number) => String(value).padStart(2, "0");
|
|
60
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function SessionImportSection({ cwd }: { cwd: string }) {
|
|
64
|
+
const { t } = useI18n();
|
|
65
|
+
const [summaries, setSummaries] = useState<Summary[]>([]);
|
|
66
|
+
const [errors, setErrors] = useState<SourceError[]>([]);
|
|
67
|
+
const [importedKeys, setImportedKeys] = useState<Set<string>>(new Set());
|
|
68
|
+
const [selected, setSelected] = useState<Set<string>>(new Set());
|
|
69
|
+
const [scanning, setScanning] = useState(false);
|
|
70
|
+
const [importing, setImporting] = useState(false);
|
|
71
|
+
const [report, setReport] = useState<ImportReport | null>(null);
|
|
72
|
+
const [failure, setFailure] = useState<string | null>(null);
|
|
73
|
+
const [workspaces, setWorkspaces] = useState<string[]>([]);
|
|
74
|
+
const [fallback, setFallback] = useState(cwd);
|
|
75
|
+
|
|
76
|
+
const scan = useCallback(async () => {
|
|
77
|
+
setScanning(true);
|
|
78
|
+
setFailure(null);
|
|
79
|
+
try {
|
|
80
|
+
const response = await fetch("/api/sessions/import");
|
|
81
|
+
const json = await response.json();
|
|
82
|
+
if (!json.success) {
|
|
83
|
+
setFailure(json.error || t.toastError);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
setSummaries(json.data.sessions ?? []);
|
|
87
|
+
setErrors(json.data.errors ?? []);
|
|
88
|
+
setImportedKeys(new Set<string>(json.data.imported ?? []));
|
|
89
|
+
} catch (error) {
|
|
90
|
+
setFailure(error instanceof Error ? error.message : String(error));
|
|
91
|
+
} finally {
|
|
92
|
+
setScanning(false);
|
|
93
|
+
}
|
|
94
|
+
}, [t]);
|
|
95
|
+
|
|
96
|
+
// 兜底工作区候选:已知工作区列表(只选不填),默认当前打开的工作区
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
void (async () => {
|
|
99
|
+
try {
|
|
100
|
+
const json = await (await fetch("/api/workspaces")).json();
|
|
101
|
+
if (json.success) setWorkspaces(json.data.workspaces ?? []);
|
|
102
|
+
} catch {
|
|
103
|
+
// 拿不到候选就把当前工作区当唯一条目,不影响导入
|
|
104
|
+
}
|
|
105
|
+
})();
|
|
106
|
+
}, []);
|
|
107
|
+
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
void scan();
|
|
110
|
+
}, [scan]);
|
|
111
|
+
|
|
112
|
+
const key = (summary: Summary) => `${summary.source}:${summary.externalId}`;
|
|
113
|
+
|
|
114
|
+
const groups = useMemo(() => {
|
|
115
|
+
const bySource = new Map<string, Summary[]>();
|
|
116
|
+
for (const summary of summaries) {
|
|
117
|
+
const list = bySource.get(summary.source);
|
|
118
|
+
if (list) list.push(summary);
|
|
119
|
+
else bySource.set(summary.source, [summary]);
|
|
120
|
+
}
|
|
121
|
+
return SOURCE_ORDER.filter((source) => bySource.has(source)).map((source) => {
|
|
122
|
+
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(key(row))).length };
|
|
124
|
+
});
|
|
125
|
+
}, [summaries, importedKeys]);
|
|
126
|
+
|
|
127
|
+
const toggle = (summary: Summary) => {
|
|
128
|
+
if (importedKeys.has(key(summary))) return;
|
|
129
|
+
setSelected((previous) => {
|
|
130
|
+
const next = new Set(previous);
|
|
131
|
+
if (next.has(key(summary))) next.delete(key(summary));
|
|
132
|
+
else next.add(key(summary));
|
|
133
|
+
return next;
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const selectGroup = (rows: Summary[], checked: boolean) => {
|
|
138
|
+
setSelected((previous) => {
|
|
139
|
+
const next = new Set(previous);
|
|
140
|
+
for (const row of rows) {
|
|
141
|
+
if (importedKeys.has(key(row))) continue;
|
|
142
|
+
if (checked) next.add(key(row));
|
|
143
|
+
else next.delete(key(row));
|
|
144
|
+
}
|
|
145
|
+
return next;
|
|
146
|
+
});
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const run = async () => {
|
|
150
|
+
if (!selected.size) return;
|
|
151
|
+
const items = summaries.filter((summary) => selected.has(key(summary))).map((summary) => ({ source: summary.source, externalId: summary.externalId }));
|
|
152
|
+
setImporting(true);
|
|
153
|
+
setFailure(null);
|
|
154
|
+
setReport(null);
|
|
155
|
+
try {
|
|
156
|
+
const response = await fetch("/api/sessions/import", {
|
|
157
|
+
method: "POST",
|
|
158
|
+
headers: { "Content-Type": "application/json" },
|
|
159
|
+
body: JSON.stringify({ sessions: items, fallbackCwd: fallback || undefined }),
|
|
160
|
+
});
|
|
161
|
+
const json = await response.json();
|
|
162
|
+
if (!json.success) {
|
|
163
|
+
setFailure(json.error || t.toastError);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
setReport(json.data as ImportReport);
|
|
167
|
+
setSelected(new Set());
|
|
168
|
+
await scan(); // 重新扫描:已经导过的行马上变成"已导入"
|
|
169
|
+
} catch (error) {
|
|
170
|
+
setFailure(error instanceof Error ? error.message : String(error));
|
|
171
|
+
} finally {
|
|
172
|
+
setImporting(false);
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const sourceLabel = (source: string) => {
|
|
177
|
+
const labels: Record<string, string> = {
|
|
178
|
+
claude: t.importSourceClaude,
|
|
179
|
+
codex: t.importSourceCodex,
|
|
180
|
+
grok: t.importSourceGrok,
|
|
181
|
+
zcode: t.importSourceZcode,
|
|
182
|
+
dsh: t.importSourceDsh,
|
|
183
|
+
opencode: t.importSourceOpencode,
|
|
184
|
+
};
|
|
185
|
+
return labels[source] ?? source;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const failures = report?.results.filter((result) => result.status === "failed") ?? [];
|
|
189
|
+
const skipped = report?.results.filter((result) => result.status === "skipped") ?? [];
|
|
190
|
+
|
|
191
|
+
return (
|
|
192
|
+
<div className="flex flex-col gap-4" data-testid="session-import">
|
|
193
|
+
<div style={{ fontSize: 12, color: "var(--dsw-label-caption)" }}>{t.importDesc}</div>
|
|
194
|
+
<div style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }} title={t.importLimitHintTooltip} data-testid="import-limit-hint">
|
|
195
|
+
{t.importLimitHint.replace("{n}", String(SCAN_LIMIT))}
|
|
196
|
+
</div>
|
|
197
|
+
|
|
198
|
+
{/* 工具条:扫描 / 选择 / 兜底工作区 / 导入 */}
|
|
199
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
200
|
+
<button
|
|
201
|
+
className="flex items-center gap-1.5 rounded-lg px-2.5 py-1.5"
|
|
202
|
+
style={{ fontSize: 12, border: "0.5px solid var(--dsw-border-l2)" }}
|
|
203
|
+
onClick={() => void scan()}
|
|
204
|
+
disabled={scanning}
|
|
205
|
+
data-testid="import-rescan"
|
|
206
|
+
>
|
|
207
|
+
<IconRefreshOutline14 size={13} /> {scanning ? t.importScanning : t.importRescan}
|
|
208
|
+
</button>
|
|
209
|
+
<span style={{ fontSize: 12, color: "var(--dsw-label-caption)" }}>{t.importFallbackWorkspace}</span>
|
|
210
|
+
<select
|
|
211
|
+
className="rounded-lg px-2 py-1"
|
|
212
|
+
style={{ fontSize: 12, border: "0.5px solid var(--dsw-border-l2)", background: "transparent", maxWidth: 260 }}
|
|
213
|
+
value={fallback}
|
|
214
|
+
onChange={(event) => setFallback(event.target.value)}
|
|
215
|
+
aria-label={t.importFallbackWorkspace}
|
|
216
|
+
data-testid="import-fallback"
|
|
217
|
+
>
|
|
218
|
+
{(workspaces.includes(fallback) || !fallback ? workspaces : [fallback, ...workspaces]).map((workspace) => (
|
|
219
|
+
<option key={workspace} value={workspace}>
|
|
220
|
+
{workspace}
|
|
221
|
+
</option>
|
|
222
|
+
))}
|
|
223
|
+
</select>
|
|
224
|
+
<div className="flex-1" />
|
|
225
|
+
<button
|
|
226
|
+
className="rounded-lg px-2.5 py-1.5"
|
|
227
|
+
style={{ fontSize: 12, color: "var(--dsw-label-tertiary)" }}
|
|
228
|
+
onClick={() => setSelected(new Set())}
|
|
229
|
+
disabled={!selected.size}
|
|
230
|
+
>
|
|
231
|
+
{t.importClearSelection}
|
|
232
|
+
</button>
|
|
233
|
+
<button
|
|
234
|
+
className="flex items-center gap-1.5 rounded-lg px-3 py-1.5"
|
|
235
|
+
style={{ fontSize: 12, background: selected.size && !importing ? "var(--dsw-accent)" : "var(--dsw-hover)", color: selected.size && !importing ? "#fff" : "var(--dsw-label-caption)" }}
|
|
236
|
+
onClick={() => void run()}
|
|
237
|
+
disabled={!selected.size || importing}
|
|
238
|
+
data-testid="import-run"
|
|
239
|
+
>
|
|
240
|
+
<IconDownloadOutline16 size={13} /> {importing ? t.importRunning : t.importRun.replace("{n}", String(selected.size))}
|
|
241
|
+
</button>
|
|
242
|
+
</div>
|
|
243
|
+
|
|
244
|
+
{failure && (
|
|
245
|
+
<div role="alert" className="rounded-lg px-3 py-2" style={{ fontSize: 12.5, background: "var(--dsw-hover)", color: "var(--dsw-danger)" }}>
|
|
246
|
+
{failure}
|
|
247
|
+
</div>
|
|
248
|
+
)}
|
|
249
|
+
|
|
250
|
+
{errors.map((error) => (
|
|
251
|
+
<div key={error.source} className="rounded-lg px-3 py-2" style={{ fontSize: 12, background: "var(--dsw-hover)", color: "var(--dsw-label-tertiary)" }}>
|
|
252
|
+
{t.importSourceError.replace("{source}", sourceLabel(error.source)).replace("{message}", error.message)}
|
|
253
|
+
</div>
|
|
254
|
+
))}
|
|
255
|
+
|
|
256
|
+
{report && (
|
|
257
|
+
<div className="rounded-2xl px-4 py-3" style={{ border: "0.5px solid var(--dsw-border-l2)" }} data-testid="import-report">
|
|
258
|
+
<div style={{ fontSize: 13 }}>
|
|
259
|
+
{t.importReportSummary
|
|
260
|
+
.replace("{imported}", String(report.imported))
|
|
261
|
+
.replace("{skipped}", String(report.skipped))
|
|
262
|
+
.replace("{failed}", String(report.failed))}
|
|
263
|
+
</div>
|
|
264
|
+
{skipped.map((result) => (
|
|
265
|
+
<div key={`skip-${result.source}-${result.externalId}`} className="mt-1" style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }}>
|
|
266
|
+
{sourceLabel(result.source)} · {result.title || result.externalId} — {result.reason}
|
|
267
|
+
</div>
|
|
268
|
+
))}
|
|
269
|
+
{failures.map((result) => (
|
|
270
|
+
<div key={`fail-${result.source}-${result.externalId}`} className="mt-1" style={{ fontSize: 11.5, color: "var(--dsw-danger)" }}>
|
|
271
|
+
{sourceLabel(result.source)} · {result.title || result.externalId} — {result.reason}
|
|
272
|
+
</div>
|
|
273
|
+
))}
|
|
274
|
+
</div>
|
|
275
|
+
)}
|
|
276
|
+
|
|
277
|
+
{!scanning && !summaries.length && !errors.length && <div style={{ fontSize: 13, color: "var(--dsw-label-caption)" }}>{t.importEmpty}</div>}
|
|
278
|
+
|
|
279
|
+
{groups.map((group) => (
|
|
280
|
+
<div key={group.source} className="flex flex-col gap-2" data-testid={`import-group-${group.source}`}>
|
|
281
|
+
<div className="flex items-center gap-2">
|
|
282
|
+
<span className="rounded-full px-2 py-0.5" style={{ fontSize: 10.5, background: "var(--dsw-hover)", color: "var(--dsw-label-tertiary)" }}>
|
|
283
|
+
{sourceLabel(group.source)}
|
|
284
|
+
</span>
|
|
285
|
+
<span style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }}>
|
|
286
|
+
{t.importGroupCount.replace("{n}", String(group.rows.length)).replace("{imported}", String(group.imported))}
|
|
287
|
+
</span>
|
|
288
|
+
<div className="flex-1" />
|
|
289
|
+
<button className="rounded-lg px-2 py-0.5" style={{ fontSize: 11.5, color: "var(--dsw-label-tertiary)" }} onClick={() => selectGroup(group.rows, true)}>
|
|
290
|
+
{t.importSelectAll}
|
|
291
|
+
</button>
|
|
292
|
+
<button className="rounded-lg px-2 py-0.5" style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }} onClick={() => selectGroup(group.rows, false)}>
|
|
293
|
+
{t.importSelectNone}
|
|
294
|
+
</button>
|
|
295
|
+
</div>
|
|
296
|
+
<div className="flex flex-col gap-1.5">
|
|
297
|
+
{group.rows.map((row) => {
|
|
298
|
+
const rowKey = key(row);
|
|
299
|
+
const done = importedKeys.has(rowKey);
|
|
300
|
+
const checked = selected.has(rowKey);
|
|
301
|
+
return (
|
|
302
|
+
<label
|
|
303
|
+
key={rowKey}
|
|
304
|
+
className="flex cursor-pointer items-center gap-3 rounded-xl px-3 py-2"
|
|
305
|
+
style={{ border: `0.5px solid ${checked ? "var(--dsw-accent)" : "var(--dsw-border-l2)"}`, opacity: done ? 0.55 : 1 }}
|
|
306
|
+
>
|
|
307
|
+
<input type="checkbox" checked={checked} disabled={done} onChange={() => toggle(row)} />
|
|
308
|
+
<div className="min-w-0 flex-1">
|
|
309
|
+
<div className="truncate" style={{ fontSize: 12.5 }} title={row.title}>
|
|
310
|
+
{row.title || t.importUntitled}
|
|
311
|
+
</div>
|
|
312
|
+
<div className="truncate" style={{ fontSize: 11, color: "var(--dsw-label-caption)" }} title={row.projectPath}>
|
|
313
|
+
{row.projectPath || t.importNoProject}
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
316
|
+
{done && (
|
|
317
|
+
<span className="rounded-full px-2 py-0.5" style={{ fontSize: 10, background: "var(--dsw-hover)", color: "var(--dsw-label-caption)" }}>
|
|
318
|
+
{t.importAlready}
|
|
319
|
+
</span>
|
|
320
|
+
)}
|
|
321
|
+
<span style={{ fontSize: 11, color: "var(--dsw-label-caption)", whiteSpace: "nowrap" }}>
|
|
322
|
+
{typeof row.messageCount === "number" ? t.importMessages.replace("{n}", String(row.messageCount)) : "—"}
|
|
323
|
+
</span>
|
|
324
|
+
<span style={{ fontSize: 11, color: "var(--dsw-label-caption)", whiteSpace: "nowrap" }}>{dayOf(row.updatedAt ?? row.createdAt)}</span>
|
|
325
|
+
</label>
|
|
326
|
+
);
|
|
327
|
+
})}
|
|
328
|
+
</div>
|
|
329
|
+
</div>
|
|
330
|
+
))}
|
|
331
|
+
</div>
|
|
332
|
+
);
|
|
333
|
+
}
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
type BuiltinProviderSetup,
|
|
32
32
|
type CustomProviderSetup,
|
|
33
33
|
} from "@/components/ProviderSetupModal";
|
|
34
|
+
import { SessionImportSection } from "@/components/SessionImportSection";
|
|
34
35
|
import { useI18n } from "@/i18n";
|
|
35
36
|
import { applyPebrelTheme, loadPebrelTheme, loadThemeMode, type PebrelTheme, type ThemeMode } from "@/lib/theme";
|
|
36
37
|
import { modelDraftFromConfig, serializeProviderDraft, validateModelDrafts, type ModelDraft } from "@/lib/model-draft";
|
|
@@ -39,7 +40,7 @@ import { quotaWindowKind, supportsUsageProbe, type BalanceAmount, type BalanceDe
|
|
|
39
40
|
import type { ProviderView } from "@/lib/models-service";
|
|
40
41
|
import type { ToolPreset } from "@/lib/types";
|
|
41
42
|
|
|
42
|
-
type Section = "general" | "models" | "tools" | "skills" | "plugins";
|
|
43
|
+
type Section = "general" | "models" | "tools" | "skills" | "plugins" | "import";
|
|
43
44
|
|
|
44
45
|
export function SettingsPanel({
|
|
45
46
|
open,
|
|
@@ -83,6 +84,7 @@ export function SettingsPanel({
|
|
|
83
84
|
{ id: "tools", label: t.toolsSection, icon: <IconAgentPresetOutline16 size={15} /> },
|
|
84
85
|
{ id: "plugins", label: t.setPlugins, icon: <IconPluginOutline16 size={15} /> },
|
|
85
86
|
{ id: "skills", label: t.setSkills, icon: <IconSkillOutline16 size={15} /> },
|
|
87
|
+
{ id: "import", label: t.importSessions, icon: <IconDownloadOutline16 size={15} /> },
|
|
86
88
|
];
|
|
87
89
|
|
|
88
90
|
return (
|
|
@@ -149,6 +151,7 @@ export function SettingsPanel({
|
|
|
149
151
|
{section === "skills" && <SkillsSection cwd={cwd} onOpenFileContent={onOpenFileContent} />}
|
|
150
152
|
{section === "tools" && <ToolsSection toolPreset={toolPreset} onToolPresetChange={onToolPresetChange} tools={tools} onSetTools={onSetTools} />}
|
|
151
153
|
{section === "plugins" && <PluginsSection cwd={cwd} />}
|
|
154
|
+
{section === "import" && <SessionImportSection cwd={cwd} />}
|
|
152
155
|
</div>
|
|
153
156
|
</div>
|
|
154
157
|
</div>
|
|
@@ -978,12 +981,12 @@ function ModelsSection() {
|
|
|
978
981
|
return (
|
|
979
982
|
<>
|
|
980
983
|
<div className="flex items-center gap-2">
|
|
981
|
-
{!usage && <span>{failed ? t.usageProbeFailed : busy ? t.queryingQuota :
|
|
984
|
+
{!usage && <span>{failed ? t.usageProbeFailed : busy ? t.queryingQuota : t.usageReadOnlyDisclosure}</span>}
|
|
982
985
|
{usage?.kind === "quota" && usage.planType && <span>{t.usagePlan} {titleCase(usage.planType)}</span>}
|
|
983
986
|
{usage?.kind === "balance" && usage.primary && (
|
|
984
987
|
<span>{usage.primaryKey === "used" ? t.usageBalanceUsed : t.usageQuotaRemaining} <span style={{ color: "var(--dsw-label-secondary)" }}>{fmtAmount(usage.primary)}</span></span>
|
|
985
988
|
)}
|
|
986
|
-
<button type="button" className="pw-chip" disabled={busy} title={
|
|
989
|
+
<button type="button" className="pw-chip" disabled={busy} title={t.usageReadOnlyDisclosure} onClick={() => void fetchProviderUsage(providerId, true)}>
|
|
987
990
|
{busy ? t.usageProbing : usage ? t.usageRefresh : t.usageProbe}
|
|
988
991
|
</button>
|
|
989
992
|
</div>
|
|
@@ -1230,69 +1233,71 @@ function ModelsSection() {
|
|
|
1230
1233
|
const editingThis = editing === p.id;
|
|
1231
1234
|
return (
|
|
1232
1235
|
<div key={p.id}>
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
<div className="
|
|
1236
|
-
<
|
|
1237
|
-
<div className="
|
|
1238
|
-
{
|
|
1236
|
+
{/* 卡片:身份/操作行在上,额度显示放在同一个框内的第二段(分隔线区分) */}
|
|
1237
|
+
<div className="flex flex-col rounded-2xl px-4 py-3" style={{ border: "0.5px solid var(--dsw-border-l2)" }}>
|
|
1238
|
+
<div className="flex items-center gap-3">
|
|
1239
|
+
<ProviderBrand id={p.id} name={providerName(p.id)} size={34} />
|
|
1240
|
+
<div className="min-w-0 flex-1">
|
|
1241
|
+
<div className="truncate" style={{ fontSize: 14, fontWeight: 500 }}>{providerName(p.id)}</div>
|
|
1242
|
+
<div className="truncate" style={{ fontSize: 11.5, color: "var(--dsw-label-caption)" }}>
|
|
1243
|
+
{p.id}{p.baseUrl ? ` · ${p.baseUrl}` : ""}
|
|
1244
|
+
</div>
|
|
1239
1245
|
</div>
|
|
1240
|
-
|
|
1241
|
-
|
|
1246
|
+
{isCustom && (
|
|
1247
|
+
<span
|
|
1248
|
+
className="rounded-md px-1.5 py-0.5"
|
|
1249
|
+
style={{ fontSize: 10.5, border: "0.5px solid var(--dsw-border-l3)", color: "var(--dsw-label-tertiary)" }}
|
|
1250
|
+
>
|
|
1251
|
+
{t.customBadge}
|
|
1252
|
+
</span>
|
|
1253
|
+
)}
|
|
1254
|
+
<span
|
|
1255
|
+
className="inline-block h-2 w-2 rounded-full"
|
|
1256
|
+
style={{ background: p.authReady ? "var(--dsw-success)" : "var(--dsw-danger)" }}
|
|
1257
|
+
title={p.authError || (p.authReady ? t.providerReady : t.authUnavailable)}
|
|
1258
|
+
/>
|
|
1242
1259
|
<span
|
|
1243
1260
|
className="rounded-md px-1.5 py-0.5"
|
|
1244
1261
|
style={{ fontSize: 10.5, border: "0.5px solid var(--dsw-border-l3)", color: "var(--dsw-label-tertiary)" }}
|
|
1245
1262
|
>
|
|
1246
|
-
{t.
|
|
1263
|
+
{t.credentialSource.replace("{source}", credentialSource(p))}
|
|
1247
1264
|
</span>
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
className="inline-block h-2 w-2 rounded-full"
|
|
1251
|
-
style={{ background: p.authReady ? "var(--dsw-success)" : "var(--dsw-danger)" }}
|
|
1252
|
-
title={p.authError || (p.authReady ? t.providerReady : t.authUnavailable)}
|
|
1253
|
-
/>
|
|
1254
|
-
<span
|
|
1255
|
-
className="rounded-md px-1.5 py-0.5"
|
|
1256
|
-
style={{ fontSize: 10.5, border: "0.5px solid var(--dsw-border-l3)", color: "var(--dsw-label-tertiary)" }}
|
|
1257
|
-
>
|
|
1258
|
-
{t.credentialSource.replace("{source}", credentialSource(p))}
|
|
1259
|
-
</span>
|
|
1260
|
-
<button className="btn-outline" style={{ height: 28, padding: "0 12px", fontSize: 12 }} onClick={() => startEdit(p.id)}>
|
|
1261
|
-
{t.edit}
|
|
1262
|
-
</button>
|
|
1263
|
-
{p.storedAuthType === "oauth" && (
|
|
1264
|
-
<button
|
|
1265
|
-
style={{ height: 28, padding: "0 12px", fontSize: 12, color: "var(--dsw-danger)" }}
|
|
1266
|
-
onClick={async () => {
|
|
1267
|
-
if (!window.confirm(t.confirmRemoveOAuth.replace("{name}", providerName(p.id)))) return;
|
|
1268
|
-
await call({ action: "removeKey", providerId: p.id });
|
|
1269
|
-
}}
|
|
1270
|
-
>
|
|
1271
|
-
{t.removeOAuth}
|
|
1265
|
+
<button className="btn-outline" style={{ height: 28, padding: "0 12px", fontSize: 12 }} onClick={() => startEdit(p.id)}>
|
|
1266
|
+
{t.edit}
|
|
1272
1267
|
</button>
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1268
|
+
{p.storedAuthType === "oauth" && (
|
|
1269
|
+
<button
|
|
1270
|
+
style={{ height: 28, padding: "0 12px", fontSize: 12, color: "var(--dsw-danger)" }}
|
|
1271
|
+
onClick={async () => {
|
|
1272
|
+
if (!window.confirm(t.confirmRemoveOAuth.replace("{name}", providerName(p.id)))) return;
|
|
1273
|
+
await call({ action: "removeKey", providerId: p.id });
|
|
1274
|
+
}}
|
|
1275
|
+
>
|
|
1276
|
+
{t.removeOAuth}
|
|
1277
|
+
</button>
|
|
1278
|
+
)}
|
|
1279
|
+
{p.authTypes?.includes("oauth") && (
|
|
1280
|
+
<button
|
|
1281
|
+
className="btn-outline"
|
|
1282
|
+
style={{ height: 28, padding: "0 12px", fontSize: 12 }}
|
|
1283
|
+
onClick={() => {
|
|
1284
|
+
setOauthDone(null);
|
|
1285
|
+
void call({ action: "loginStart", providerId: p.id });
|
|
1286
|
+
setOauthProvider(p.id);
|
|
1287
|
+
}}
|
|
1288
|
+
>
|
|
1289
|
+
{t.oauthLogin}
|
|
1290
|
+
</button>
|
|
1291
|
+
)}
|
|
1292
|
+
{customIds.has(p.id) && (
|
|
1293
|
+
<button style={{ fontSize: 12.5, color: "var(--dsw-danger)" }} onClick={() => deleteCustom(p.id)}>
|
|
1294
|
+
{t.delete}
|
|
1295
|
+
</button>
|
|
1296
|
+
)}
|
|
1292
1297
|
</div>
|
|
1293
1298
|
|
|
1294
1299
|
{p.id === "xai" && p.authReady && (
|
|
1295
|
-
<div className="mt-
|
|
1300
|
+
<div className="mt-3 flex items-center gap-2 pt-3" style={{ borderTop: "0.5px solid var(--dsw-border-l2)", fontSize: 11.5, color: "var(--dsw-label-tertiary)" }}>
|
|
1296
1301
|
{xaiUsage ? (
|
|
1297
1302
|
<span>{t.usageQuotaLabel} · {xaiUsage.model} · {t.usageRequests} {xaiUsage.requestRemaining.toLocaleString()}/{xaiUsage.requestLimit.toLocaleString()} · Tokens {fmtQuotaTokens(xaiUsage.tokenRemaining)}/{fmtQuotaTokens(xaiUsage.tokenLimit)}</span>
|
|
1298
1303
|
) : <span>{usageError ? t.usageProbeFailed : t.usageProbeDisclosure}</span>}
|
|
@@ -1303,10 +1308,11 @@ function ModelsSection() {
|
|
|
1303
1308
|
)}
|
|
1304
1309
|
|
|
1305
1310
|
{supportsUsageProbe(p.id) && p.id !== "xai" && p.authReady && (
|
|
1306
|
-
<div className="mt-
|
|
1311
|
+
<div className="mt-3 flex flex-col gap-1.5 pt-3" style={{ borderTop: "0.5px solid var(--dsw-border-l2)", fontSize: 11.5, color: "var(--dsw-label-tertiary)" }}>
|
|
1307
1312
|
{renderProviderUsage(p.id)}
|
|
1308
1313
|
</div>
|
|
1309
1314
|
)}
|
|
1315
|
+
</div>
|
|
1310
1316
|
|
|
1311
1317
|
{editingThis && (
|
|
1312
1318
|
<div className="mt-2 rounded-2xl p-4" style={{ border: "0.5px solid var(--dsw-border-l2)" }}>
|
package/src/i18n.tsx
CHANGED
|
@@ -21,7 +21,6 @@ const zh = {
|
|
|
21
21
|
copyFailed: "复制失败",
|
|
22
22
|
editMessage: "编辑这条消息",
|
|
23
23
|
sendEdit: "重新发送",
|
|
24
|
-
editWhileRunning: "会话正在运行,先停止再编辑",
|
|
25
24
|
editMessageFailed: "编辑重发失败",
|
|
26
25
|
export: "导出",
|
|
27
26
|
settings: "设置",
|
|
@@ -214,7 +213,6 @@ const zh = {
|
|
|
214
213
|
usageQuotaRemaining: "额度剩余",
|
|
215
214
|
queryingQuota: "正在查询额度…",
|
|
216
215
|
usageReadOnlyDisclosure: "读取提供商的只读用量端点,不消耗额度。",
|
|
217
|
-
usageXaiDisclosure: "会向 xAI 发出一次 1 token 的请求以读取限流头,会计入用量。",
|
|
218
216
|
usageReset: "刷新时间",
|
|
219
217
|
usageWindowWeekly: "周限",
|
|
220
218
|
usageWindowMonthly: "月限",
|
|
@@ -286,6 +284,28 @@ const zh = {
|
|
|
286
284
|
contextSystem: "系统提示词",
|
|
287
285
|
contextMessages: "对话消息",
|
|
288
286
|
contextTools: "工具",
|
|
287
|
+
promptPanel: "提示词来源",
|
|
288
|
+
promptRefresh: "刷新",
|
|
289
|
+
promptLoading: "加载中…",
|
|
290
|
+
promptColdHint: "会话尚未启动:先列出磁盘上的提示词文件;打开会话后才会出现「组装后的系统提示词」与工具定义。",
|
|
291
|
+
promptOriginProject: "项目",
|
|
292
|
+
promptOriginAgent: "个人",
|
|
293
|
+
promptOriginPackage: "包",
|
|
294
|
+
promptOriginExtension: "扩展",
|
|
295
|
+
promptKindSystem: "系统提示词文件",
|
|
296
|
+
promptKindAppend: "追加提示词",
|
|
297
|
+
promptKindAgents: "项目记忆(AGENTS.md)",
|
|
298
|
+
promptKindSkill: "技能提示词",
|
|
299
|
+
promptKindTemplate: "提示模板",
|
|
300
|
+
promptKindTool: "工具提示词",
|
|
301
|
+
promptKindAssembled: "系统提示词",
|
|
302
|
+
promptKindCompacted: "压缩摘要",
|
|
303
|
+
promptCompactedKey: "进入上下文的压缩摘要",
|
|
304
|
+
promptGuidelines: "提示词指南",
|
|
305
|
+
promptParameters: "参数定义",
|
|
306
|
+
promptAssembledKey: "当前生效的系统提示词",
|
|
307
|
+
promptToolBuiltin: "内置",
|
|
308
|
+
promptToolExtension: "扩展",
|
|
289
309
|
contextSystemPrompt: "System Prompt",
|
|
290
310
|
contextSystemTools: "System Tools",
|
|
291
311
|
contextCustomTools: "Custom Tools",
|
|
@@ -354,6 +374,33 @@ const zh = {
|
|
|
354
374
|
enterQueue: "排队:本轮结束后发送",
|
|
355
375
|
enterSteer: "插话:当前工具跑完就送达",
|
|
356
376
|
setSkills: "技能",
|
|
377
|
+
// 导入会话(其它 AI 编程工具的本地对话 → pi 会话)
|
|
378
|
+
importSessions: "导入会话",
|
|
379
|
+
importDesc: "把本机 Codex / Claude Code / Grok / ZCode / dsh / opencode 的对话无损导入成 pi 会话:文本、思考、工具调用与结果、时间戳、用量都保留。源项目目录还在本机就导回原工作区,否则落到下面选的兜底工作区。只读源数据,不修改源工具的任何文件。",
|
|
380
|
+
importRescan: "重新扫描",
|
|
381
|
+
importScanning: "扫描中…",
|
|
382
|
+
importSourceClaude: "Claude Code",
|
|
383
|
+
importSourceCodex: "Codex",
|
|
384
|
+
importSourceGrok: "Grok",
|
|
385
|
+
importSourceZcode: "ZCode",
|
|
386
|
+
importSourceDsh: "dsh",
|
|
387
|
+
importSourceOpencode: "opencode",
|
|
388
|
+
importSourceError: "{source} 扫描失败:{message}",
|
|
389
|
+
importGroupCount: "{n} 条 · 已导入 {imported}",
|
|
390
|
+
importLimitHint: "每个来源只列最近 {n} 条对话(更早的会话不扫描,省时省资源)",
|
|
391
|
+
importLimitHintTooltip: "看更早的会话:把 src/lib/session-import/index.ts 里的 SCAN_LIMIT_PER_SOURCE 调大",
|
|
392
|
+
importSelectAll: "全选本组",
|
|
393
|
+
importSelectNone: "清空本组",
|
|
394
|
+
importClearSelection: "清空选择",
|
|
395
|
+
importRun: "导入所选 ({n})",
|
|
396
|
+
importRunning: "导入中…",
|
|
397
|
+
importFallbackWorkspace: "兜底工作区",
|
|
398
|
+
importAlready: "已导入",
|
|
399
|
+
importUntitled: "(无标题)",
|
|
400
|
+
importNoProject: "(无项目目录)",
|
|
401
|
+
importMessages: "{n} 条消息",
|
|
402
|
+
importEmpty: "没有找到可导入的本地会话",
|
|
403
|
+
importReportSummary: "成功 {imported} · 跳过 {skipped} · 失败 {failed}",
|
|
357
404
|
setPlugins: "插件",
|
|
358
405
|
appearance: "外观",
|
|
359
406
|
appearanceMode: "亮暗模式",
|
|
@@ -579,7 +626,6 @@ const en: Dict = {
|
|
|
579
626
|
copyFailed: "Copy failed",
|
|
580
627
|
editMessage: "Edit this message",
|
|
581
628
|
sendEdit: "Send again",
|
|
582
|
-
editWhileRunning: "This session is running; stop it before editing",
|
|
583
629
|
editMessageFailed: "Edit and resend failed",
|
|
584
630
|
export: "Export",
|
|
585
631
|
settings: "Settings",
|
|
@@ -771,7 +817,6 @@ const en: Dict = {
|
|
|
771
817
|
usageQuotaRemaining: "Remaining quota",
|
|
772
818
|
queryingQuota: "Querying quota…",
|
|
773
819
|
usageReadOnlyDisclosure: "Reads the provider's read-only usage endpoint; consumes no quota.",
|
|
774
|
-
usageXaiDisclosure: "Sends one 1-token request to xAI to read the rate-limit headers; counts against your usage.",
|
|
775
820
|
usageReset: "Resets",
|
|
776
821
|
usageWindowWeekly: "Weekly",
|
|
777
822
|
usageWindowMonthly: "Monthly",
|
|
@@ -841,6 +886,28 @@ const en: Dict = {
|
|
|
841
886
|
contextSystem: "System prompt",
|
|
842
887
|
contextMessages: "Messages",
|
|
843
888
|
contextTools: "Tools",
|
|
889
|
+
promptPanel: "Prompt sources",
|
|
890
|
+
promptRefresh: "Refresh",
|
|
891
|
+
promptLoading: "Loading…",
|
|
892
|
+
promptColdHint: "Session not started yet: disk files are listed now; the assembled system prompt and tool definitions appear once the session is running.",
|
|
893
|
+
promptOriginProject: "project",
|
|
894
|
+
promptOriginAgent: "user",
|
|
895
|
+
promptOriginPackage: "package",
|
|
896
|
+
promptOriginExtension: "extension",
|
|
897
|
+
promptKindSystem: "System prompt file",
|
|
898
|
+
promptKindAppend: "Appended prompt",
|
|
899
|
+
promptKindAgents: "Project memory (AGENTS.md)",
|
|
900
|
+
promptKindSkill: "Skill prompts",
|
|
901
|
+
promptKindTemplate: "Prompt templates",
|
|
902
|
+
promptKindTool: "Tool prompts",
|
|
903
|
+
promptKindAssembled: "System prompt",
|
|
904
|
+
promptKindCompacted: "Compaction summary",
|
|
905
|
+
promptCompactedKey: "Compaction summary in context",
|
|
906
|
+
promptGuidelines: "Prompt guidelines",
|
|
907
|
+
promptParameters: "Parameter schema",
|
|
908
|
+
promptAssembledKey: "Effective system prompt",
|
|
909
|
+
promptToolBuiltin: "builtin",
|
|
910
|
+
promptToolExtension: "extension",
|
|
844
911
|
contextSystemPrompt: "System Prompt",
|
|
845
912
|
contextSystemTools: "System Tools",
|
|
846
913
|
contextCustomTools: "Custom Tools",
|
|
@@ -905,6 +972,33 @@ const en: Dict = {
|
|
|
905
972
|
enterQueue: "Queue: sent after this turn ends",
|
|
906
973
|
enterSteer: "Steer: delivered after the current tool call",
|
|
907
974
|
setSkills: "Skills",
|
|
975
|
+
// Session import (other AI coding tools' local conversations → pi sessions)
|
|
976
|
+
importSessions: "Import sessions",
|
|
977
|
+
importDesc: "Losslessly import conversations from Codex / Claude Code / Grok / ZCode / dsh / opencode on this machine into pi sessions: text, thinking, tool calls and results, timestamps and usage are all kept. If the original project directory still exists locally the session lands in that workspace; otherwise it falls back to the workspace selected below. Source data is only read, never modified.",
|
|
978
|
+
importRescan: "Rescan",
|
|
979
|
+
importScanning: "Scanning…",
|
|
980
|
+
importSourceClaude: "Claude Code",
|
|
981
|
+
importSourceCodex: "Codex",
|
|
982
|
+
importSourceGrok: "Grok",
|
|
983
|
+
importSourceZcode: "ZCode",
|
|
984
|
+
importSourceDsh: "dsh",
|
|
985
|
+
importSourceOpencode: "opencode",
|
|
986
|
+
importSourceError: "{source} scan failed: {message}",
|
|
987
|
+
importGroupCount: "{n} sessions · {imported} imported",
|
|
988
|
+
importLimitHint: "Only the {n} most recent conversations per source are scanned",
|
|
989
|
+
importLimitHintTooltip: "To see older sessions, raise SCAN_LIMIT_PER_SOURCE in src/lib/session-import/index.ts",
|
|
990
|
+
importSelectAll: "Select group",
|
|
991
|
+
importSelectNone: "Clear group",
|
|
992
|
+
importClearSelection: "Clear",
|
|
993
|
+
importRun: "Import selected ({n})",
|
|
994
|
+
importRunning: "Importing…",
|
|
995
|
+
importFallbackWorkspace: "Fallback workspace",
|
|
996
|
+
importAlready: "Imported",
|
|
997
|
+
importUntitled: "(untitled)",
|
|
998
|
+
importNoProject: "(no project directory)",
|
|
999
|
+
importMessages: "{n} messages",
|
|
1000
|
+
importEmpty: "No importable local sessions found",
|
|
1001
|
+
importReportSummary: "Imported {imported} · skipped {skipped} · failed {failed}",
|
|
908
1002
|
setPlugins: "Plugins",
|
|
909
1003
|
appearance: "Appearance",
|
|
910
1004
|
appearanceMode: "Light / Dark",
|