@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
package/src/hooks/usePiWeb.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
7
7
|
import { createEventBatcher, type EventBatcher } from "@/lib/event-batcher";
|
|
8
8
|
import type {
|
|
9
|
+
GrowthConsentRequest,
|
|
9
10
|
GrowthRound,
|
|
10
11
|
SessionSummary,
|
|
11
12
|
TrajEntry,
|
|
@@ -61,7 +62,7 @@ export interface PiWebState {
|
|
|
61
62
|
/** 扩展覆盖的「工作中」文案(setWorkingMessage) */
|
|
62
63
|
workingMessage: string | null;
|
|
63
64
|
/** 项目生长:本次连接期间收到的 commit(完整时间轴由 useGrowth 拉取后按 commit 去重合并) */
|
|
64
|
-
growth: { rounds: GrowthRound[]; error: string | null };
|
|
65
|
+
growth: { rounds: GrowthRound[]; error: string | null; consent: GrowthConsentRequest | null };
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
export interface SessionListItem extends SessionSummary {
|
|
@@ -76,8 +77,10 @@ export const foldPiWebEvent = (state: PiWebState, evt: WebEvent): PiWebState =>
|
|
|
76
77
|
case "delta": {
|
|
77
78
|
// 新服务端按稳定流 ID 定位;旧协议仍按最近 pending 消息兼容。
|
|
78
79
|
const msgs = [...s.messages];
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
// 从尾部找:增量几乎总是落在最后一条消息上,长会话里从头扫每个 token 都是一次全表遍历
|
|
81
|
+
let i = msgs.length - 1;
|
|
82
|
+
if (evt.messageId) while (i >= 0 && msgs[i].streamId !== evt.messageId && msgs[i].id !== evt.messageId) i -= 1;
|
|
83
|
+
else while (i >= 0 && msgs[i].role !== "assistant" && msgs[i].role !== "user") i -= 1;
|
|
81
84
|
if (evt.messageId && i >= 0 && msgs[i].stopReason && msgs[i].stopReason !== "pending") return s;
|
|
82
85
|
if (i < 0 || msgs[i].role !== "assistant" || (msgs[i].stopReason ?? "pending") !== "pending") {
|
|
83
86
|
msgs.push({ role: "assistant", content: [], streamId: evt.messageId, stopReason: "pending", timestamp: evt.ts });
|
|
@@ -234,12 +237,15 @@ export const foldPiWebEvent = (state: PiWebState, evt: WebEvent): PiWebState =>
|
|
|
234
237
|
return s;
|
|
235
238
|
case "growth": {
|
|
236
239
|
const rounds = s.growth.rounds.some((x) => x.commit === evt.round.commit) ? s.growth.rounds : [...s.growth.rounds, evt.round];
|
|
237
|
-
s.growth = { rounds, error: null };
|
|
240
|
+
s.growth = { ...s.growth, rounds, error: null };
|
|
238
241
|
return s;
|
|
239
242
|
}
|
|
240
243
|
case "growth_error":
|
|
241
244
|
s.growth = { ...s.growth, error: evt.message };
|
|
242
245
|
return s;
|
|
246
|
+
case "growth_consent":
|
|
247
|
+
s.growth = { ...s.growth, consent: evt.request };
|
|
248
|
+
return s;
|
|
243
249
|
case "retry":
|
|
244
250
|
// 只保留最后一次尝试:重试过程在界面上是「重试 3/5」这一条,而不是五行报错
|
|
245
251
|
s.retryNotice = evt.maxAttempts
|
|
@@ -315,7 +321,7 @@ const emptyState = (toolPreset: ToolPreset = "standard"): PiWebState => ({
|
|
|
315
321
|
extensionNotices: [],
|
|
316
322
|
extensionStatuses: {},
|
|
317
323
|
workingMessage: null,
|
|
318
|
-
growth: { rounds: [], error: null },
|
|
324
|
+
growth: { rounds: [], error: null, consent: null },
|
|
319
325
|
});
|
|
320
326
|
|
|
321
327
|
function pathKey(value: string): string {
|
|
@@ -337,6 +343,9 @@ export function usePiWeb() {
|
|
|
337
343
|
const [models, setModels] = useState<{ providers: any[]; models: any[] } | null>(null);
|
|
338
344
|
const [addedWorkspaces, setAddedWorkspaces] = useState<string[]>([]);
|
|
339
345
|
const [removedWorkspaces, setRemovedWorkspaces] = useState<string[]>([]);
|
|
346
|
+
/** 应用内目录浏览器(远程设备专用):是否打开,以及等待用户选择的那次调用 */
|
|
347
|
+
const [dirPickerOpen, setDirPickerOpen] = useState(false);
|
|
348
|
+
const dirPickResolve = useRef<((path: string | null) => void) | null>(null);
|
|
340
349
|
const [groupBy, setGroupByState] = useState<"workspace" | "flat">("workspace");
|
|
341
350
|
const [orderBy, setOrderByState] = useState<"updated" | "manual">("updated");
|
|
342
351
|
const [resyncNonce, setResyncNonce] = useState(0);
|
|
@@ -400,10 +409,15 @@ export function usePiWeb() {
|
|
|
400
409
|
} catch (error) {
|
|
401
410
|
const message = error instanceof Error ? error.message : "failed to load workspaces";
|
|
402
411
|
setState((current) => ({ ...current, error: message }));
|
|
412
|
+
throw error;
|
|
403
413
|
}
|
|
404
414
|
}, []);
|
|
405
|
-
|
|
406
|
-
|
|
415
|
+
// 挂载时不再单独拉一次 /api/workspaces:首个 /api/sessions 响应里带着同一份 registry,
|
|
416
|
+
// 少一次请求也少一次服务端读盘;只有会话列表拉不到时才用它兜底(见轮询的 catch)。
|
|
417
|
+
const workspaceRegistryLoaded = useRef(false);
|
|
418
|
+
const refreshWorkspacesFallback = useCallback(async () => {
|
|
419
|
+
if (workspaceRegistryLoaded.current) return;
|
|
420
|
+
await refreshWorkspaces().catch(() => undefined);
|
|
407
421
|
}, [refreshWorkspaces]);
|
|
408
422
|
|
|
409
423
|
/** 会话改名成功后先把列表里的名字改掉,不等下一次轮询(轮询 3 秒 + 服务端 1.2 秒列表缓存) */
|
|
@@ -489,6 +503,14 @@ export function usePiWeb() {
|
|
|
489
503
|
});
|
|
490
504
|
const j = await r.json();
|
|
491
505
|
if (!r.ok || !j.success) throw new Error(j.error || `request failed (${r.status})`);
|
|
506
|
+
// 远程设备(手机走 tailnet):原生对话框会弹在主机屏幕上,等于没反应。
|
|
507
|
+
// 服务端据此回 remote:true,这里改用应用内目录浏览器,等选完再返回路径。
|
|
508
|
+
if (j.data?.remote) {
|
|
509
|
+
setDirPickerOpen(true);
|
|
510
|
+
return await new Promise<string | null>((resolve) => {
|
|
511
|
+
dirPickResolve.current = resolve;
|
|
512
|
+
});
|
|
513
|
+
}
|
|
492
514
|
if (j.success && j.data?.path) {
|
|
493
515
|
setAddedWorkspaces(j.data.workspaces ?? []);
|
|
494
516
|
await refreshWorkspaces();
|
|
@@ -502,6 +524,37 @@ export function usePiWeb() {
|
|
|
502
524
|
}
|
|
503
525
|
}, [refreshWorkspaces]);
|
|
504
526
|
|
|
527
|
+
/** 应用内目录浏览器:用户选定了某个文件夹 */
|
|
528
|
+
const confirmDirPicker = useCallback(async (path: string) => {
|
|
529
|
+
setDirPickerOpen(false);
|
|
530
|
+
const settle = dirPickResolve.current;
|
|
531
|
+
dirPickResolve.current = null;
|
|
532
|
+
try {
|
|
533
|
+
const r = await fetch("/api/workspaces", {
|
|
534
|
+
method: "POST",
|
|
535
|
+
headers: { "Content-Type": "application/json" },
|
|
536
|
+
body: JSON.stringify({ action: "add", path }),
|
|
537
|
+
});
|
|
538
|
+
const j = await r.json();
|
|
539
|
+
if (!r.ok || !j.success) throw new Error(j.error || `request failed (${r.status})`);
|
|
540
|
+
setAddedWorkspaces(j.data?.workspaces ?? []);
|
|
541
|
+
await refreshWorkspaces();
|
|
542
|
+
settle?.(path);
|
|
543
|
+
} catch (error) {
|
|
544
|
+
const message = error instanceof Error ? error.message : "failed to add workspace";
|
|
545
|
+
setState((current) => ({ ...current, error: message }));
|
|
546
|
+
settle?.(null);
|
|
547
|
+
}
|
|
548
|
+
}, [refreshWorkspaces]);
|
|
549
|
+
|
|
550
|
+
/** 应用内目录浏览器:取消 */
|
|
551
|
+
const cancelDirPicker = useCallback(() => {
|
|
552
|
+
setDirPickerOpen(false);
|
|
553
|
+
const settle = dirPickResolve.current;
|
|
554
|
+
dirPickResolve.current = null;
|
|
555
|
+
settle?.(null);
|
|
556
|
+
}, []);
|
|
557
|
+
|
|
505
558
|
|
|
506
559
|
const removeWorkspace = useCallback(
|
|
507
560
|
async (dir: string) => {
|
|
@@ -556,6 +609,7 @@ export function usePiWeb() {
|
|
|
556
609
|
const running: Record<string, { streaming: boolean; lastStep?: string }> = j.data.running ?? {};
|
|
557
610
|
const registry = j.data.workspaceRegistry;
|
|
558
611
|
if (registry) {
|
|
612
|
+
workspaceRegistryLoaded.current = true;
|
|
559
613
|
if (Array.isArray(registry.workspaces)) setAddedWorkspaces(registry.workspaces);
|
|
560
614
|
if (Array.isArray(registry.removedWorkspaces)) setRemovedWorkspaces(registry.removedWorkspaces);
|
|
561
615
|
if (registry.aliases && typeof registry.aliases === "object") setWorkspaceAliases(registry.aliases);
|
|
@@ -565,8 +619,11 @@ export function usePiWeb() {
|
|
|
565
619
|
(j.data.sessions as SessionSummary[]).map((s) => ({ ...s, streaming: running[s.path]?.streaming === true, lastStep: running[s.path]?.lastStep })),
|
|
566
620
|
);
|
|
567
621
|
} catch (error) {
|
|
568
|
-
if (alive && !(error instanceof DOMException && error.name === "AbortError"))
|
|
622
|
+
if (alive && !(error instanceof DOMException && error.name === "AbortError")) {
|
|
569
623
|
setSessionListError(error instanceof Error ? error.message : "session list request failed");
|
|
624
|
+
// 会话列表没拿到(也就没拿到 registry):单独兜底拉一次工作区,侧栏不至于空着
|
|
625
|
+
void refreshWorkspacesFallback();
|
|
626
|
+
}
|
|
570
627
|
} finally {
|
|
571
628
|
controller = undefined;
|
|
572
629
|
schedule();
|
|
@@ -586,7 +643,7 @@ export function usePiWeb() {
|
|
|
586
643
|
controller?.abort();
|
|
587
644
|
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
588
645
|
};
|
|
589
|
-
}, [sessionListRetryNonce]);
|
|
646
|
+
}, [sessionListRetryNonce, refreshWorkspacesFallback]);
|
|
590
647
|
|
|
591
648
|
// 模型目录
|
|
592
649
|
const refreshModels = useCallback(async () => {
|
|
@@ -621,14 +678,17 @@ export function usePiWeb() {
|
|
|
621
678
|
const es = new EventSource(`/api/agent/${currentId}/events`);
|
|
622
679
|
esRef.current = es;
|
|
623
680
|
es.onopen = () => {
|
|
681
|
+
retryDelayMs = 2000;
|
|
624
682
|
setState((s) => ({ ...s, connected: true }));
|
|
625
683
|
};
|
|
626
684
|
es.onerror = () => {
|
|
627
685
|
setState((s) => ({ ...s, connected: false }));
|
|
628
|
-
// 浏览器对非 200 响应会永久放弃 EventSource 自动重连
|
|
629
|
-
// (网络断连才会重试),这里手动兜底:关掉后延迟重建,
|
|
630
|
-
// 重建时会拿到全新快照,不依赖 Last-Event-ID 回放。
|
|
631
686
|
if (esRef.current !== es) return;
|
|
687
|
+
// 网络断开时浏览器自己会重连,并带上 Last-Event-ID 让服务端从环形缓冲续放
|
|
688
|
+
// (只补漏掉的几条事件)。这时手动重建就等于丢掉续传、每次都拉一份全量快照。
|
|
689
|
+
if (es.readyState !== EventSource.CLOSED) return;
|
|
690
|
+
// 浏览器对非 200 响应会永久放弃自动重连:这里手动兜底,关掉后延迟重建,
|
|
691
|
+
// 重建时会拿到全新快照。
|
|
632
692
|
es.close();
|
|
633
693
|
esRef.current = null;
|
|
634
694
|
const delay = retryDelayMs;
|
|
@@ -668,10 +728,10 @@ export function usePiWeb() {
|
|
|
668
728
|
// 服务器当前 pending 集合是权威来源,刷新可恢复、已答请求不能复活。
|
|
669
729
|
extensionDialogs: extensionDialogsFromSnapshot(snap, prev.extensionDialogs),
|
|
670
730
|
extensionNotices: prev.extensionNotices,
|
|
671
|
-
extensionStatuses: {},
|
|
731
|
+
extensionStatuses: snap.extensionStatuses ?? {},
|
|
672
732
|
workingMessage: null,
|
|
673
733
|
// 重连的快照不带生长账本;连接期间已收到的步保留,useGrowth 会按 seq 与拉取结果去重
|
|
674
|
-
growth: { ...prev.growth, error: snap.growthError ?? null },
|
|
734
|
+
growth: { ...prev.growth, error: snap.growthError ?? null, consent: snap.growthConsent ?? null },
|
|
675
735
|
}));
|
|
676
736
|
} else {
|
|
677
737
|
batcher.push(parsed as WebEvent);
|
|
@@ -836,6 +896,28 @@ export function usePiWeb() {
|
|
|
836
896
|
},
|
|
837
897
|
[currentId, resync, sendCommand],
|
|
838
898
|
);
|
|
899
|
+
/** 回答「要不要在工作区建 git 仓库给生长记录用」:决定按工作区记住;失败就重连拿回服务端的待答状态 */
|
|
900
|
+
const answerGrowthConsent = useCallback(
|
|
901
|
+
async (allow: boolean) => {
|
|
902
|
+
const target = currentId;
|
|
903
|
+
if (!target) return;
|
|
904
|
+
setState((s) => ({ ...s, growth: { ...s.growth, consent: null } }));
|
|
905
|
+
try {
|
|
906
|
+
const r = await fetch("/api/growth", {
|
|
907
|
+
method: "POST",
|
|
908
|
+
headers: { "Content-Type": "application/json" },
|
|
909
|
+
body: JSON.stringify({ action: "consent", session: target, allow }),
|
|
910
|
+
});
|
|
911
|
+
const result = await r.json();
|
|
912
|
+
if (!r.ok || !result.success) throw new Error(result.error || `request failed (${r.status})`);
|
|
913
|
+
} catch (error) {
|
|
914
|
+
if (activeSessionRef.current !== target) return;
|
|
915
|
+
setState((s) => ({ ...s, error: error instanceof Error ? error.message : String(error) }));
|
|
916
|
+
resync();
|
|
917
|
+
}
|
|
918
|
+
},
|
|
919
|
+
[currentId, resync],
|
|
920
|
+
);
|
|
839
921
|
const dismissExtensionNotice = useCallback((id: string) => setState((s) => ({ ...s, extensionNotices: s.extensionNotices.filter((n) => n.id !== id) })), []);
|
|
840
922
|
const clearError = useCallback(() => setState((s) => ({ ...s, error: null })), []);
|
|
841
923
|
/** 供 AppShell 直接展示行内提示(如未选工作区就发送) */
|
|
@@ -898,6 +980,9 @@ export function usePiWeb() {
|
|
|
898
980
|
setGroupBy,
|
|
899
981
|
setOrderBy,
|
|
900
982
|
addWorkspaceByPicker,
|
|
983
|
+
dirPickerOpen,
|
|
984
|
+
confirmDirPicker,
|
|
985
|
+
cancelDirPicker,
|
|
901
986
|
removeWorkspace,
|
|
902
987
|
refreshModels,
|
|
903
988
|
sendCommand,
|
|
@@ -909,6 +994,7 @@ export function usePiWeb() {
|
|
|
909
994
|
clearCompaction,
|
|
910
995
|
setError,
|
|
911
996
|
answerExtensionDialog,
|
|
997
|
+
answerGrowthConsent,
|
|
912
998
|
dismissExtensionNotice,
|
|
913
999
|
};
|
|
914
1000
|
}
|
package/src/i18n.tsx
CHANGED
|
@@ -19,6 +19,7 @@ const zh = {
|
|
|
19
19
|
error: "出错",
|
|
20
20
|
copy: "复制",
|
|
21
21
|
copyFailed: "复制失败",
|
|
22
|
+
externalImage: "外部图片,点击加载",
|
|
22
23
|
chatWidthHandle: "拖动调整对话宽度",
|
|
23
24
|
editMessage: "编辑这条消息",
|
|
24
25
|
sendEdit: "重新发送",
|
|
@@ -145,8 +146,15 @@ const zh = {
|
|
|
145
146
|
commandLines: "{n} 行脚本",
|
|
146
147
|
thinkLabel: "思考",
|
|
147
148
|
expandOutput: "展开全部输出",
|
|
149
|
+
expandDiff: "展开改动",
|
|
148
150
|
collapseOutput: "收起",
|
|
149
151
|
workspaces: "工作区",
|
|
152
|
+
pickFolderTitle: "选择工作区文件夹",
|
|
153
|
+
pickFolderUp: "上一级",
|
|
154
|
+
pickFolderConfirm: "选择此文件夹",
|
|
155
|
+
pickFolderEmpty: "此目录下没有子文件夹",
|
|
156
|
+
pickFolderTruncated: "只列出前 {n} 个文件夹",
|
|
157
|
+
pickFolderLoading: "读取中…",
|
|
150
158
|
searchSessions: "搜索会话...",
|
|
151
159
|
addWorkspace: "添加工作区",
|
|
152
160
|
groupBy: "分组方式",
|
|
@@ -188,15 +196,10 @@ const zh = {
|
|
|
188
196
|
noBranches: "暂无可跳转的分支节点",
|
|
189
197
|
oauthLogin: "OAuth 登录",
|
|
190
198
|
authMethod: "认证方式",
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
loginSubmitting: "登录中…",
|
|
196
|
-
loginFailed: "密码错误",
|
|
197
|
-
webAuth: "密码认证",
|
|
198
|
-
webAuthOn: "已启用(PI_WEB_PASSWORD)",
|
|
199
|
-
webAuthOff: "未启用(仅本机可访问)",
|
|
199
|
+
webAuth: "访问认证",
|
|
200
|
+
webAuthTokenOn: "已启用(启动令牌)",
|
|
201
|
+
loginTokenTitle: "这个实例用启动令牌登录",
|
|
202
|
+
loginTokenDesc: "请打开启动 PiWeb 时终端里打印的登录链接(形如 …/?token=…)。令牌每次重启都会更换;打开一次后浏览器会记住 30 天。",
|
|
200
203
|
logout: "退出登录",
|
|
201
204
|
authMethodApiKey: "API 密钥",
|
|
202
205
|
oauthDevicePrompt: "请在浏览器中完成授权",
|
|
@@ -375,7 +378,6 @@ const zh = {
|
|
|
375
378
|
enterQueue: "排队:本轮结束后发送",
|
|
376
379
|
enterSteer: "插话:当前工具跑完就送达",
|
|
377
380
|
setSkills: "技能",
|
|
378
|
-
// 导入会话(其它 AI 编程工具的本地对话 → pi 会话)
|
|
379
381
|
importSessions: "导入会话",
|
|
380
382
|
importDesc: "把本机 Codex / Claude Code / Grok / ZCode / dsh / opencode 的对话无损导入成 pi 会话:文本、思考、工具调用与结果、时间戳、用量都保留。源项目目录还在本机就导回原工作区,否则落到下面选的兜底工作区。只读源数据,不修改源工具的任何文件。",
|
|
381
383
|
importRescan: "重新扫描",
|
|
@@ -397,13 +399,41 @@ const zh = {
|
|
|
397
399
|
importRun: "导入所选 ({n})",
|
|
398
400
|
importRunning: "导入中…",
|
|
399
401
|
importFallbackWorkspace: "兜底工作区",
|
|
402
|
+
importFallbackHint: "原项目目录已不在本机时落到这里",
|
|
403
|
+
importWhereGoes: "导入去向",
|
|
404
|
+
importGoesOriginal: "回到原工作区",
|
|
405
|
+
importGoesFallback: "原目录已不在,用兜底工作区",
|
|
400
406
|
importAlready: "已导入",
|
|
401
407
|
importUntitled: "(无标题)",
|
|
402
408
|
importNoProject: "(无项目目录)",
|
|
403
409
|
importMessages: "{n} 条消息",
|
|
404
410
|
importEmpty: "没有找到可导入的本地会话",
|
|
405
411
|
importReportSummary: "成功 {imported} · 跳过 {skipped} · 失败 {failed}",
|
|
412
|
+
// 右侧抽屉(入口列表 + 内容)
|
|
413
|
+
drawerPanel: "右侧抽屉",
|
|
414
|
+
drawerSections: "抽屉入口",
|
|
415
|
+
drawerPrompts: "提示词",
|
|
406
416
|
setPlugins: "插件",
|
|
417
|
+
setRemote: "远程连接",
|
|
418
|
+
remoteBuiltin: "内置插件",
|
|
419
|
+
remoteLanTitle: "局域网入口",
|
|
420
|
+
remoteLanHint: "同一个 WiFi 下的设备直接打开下面地址即可,手机排版已适配。默认开启。",
|
|
421
|
+
remotePublicTitle: "公网入口",
|
|
422
|
+
remotePublicHint: "开启后任何浏览器都能打开,无需安装客户端。地址由 Cloudflare 随机分配,每次启动都会变。",
|
|
423
|
+
remoteStart: "开启远程连接",
|
|
424
|
+
remoteStop: "关闭",
|
|
425
|
+
remoteWorking: "启动中…",
|
|
426
|
+
remoteRunning: "运行中",
|
|
427
|
+
remoteStopped: "未开启",
|
|
428
|
+
remotePending: "地址已生成,生效中…",
|
|
429
|
+
remoteEngineMissing: "找不到 cloudflared。请安装后重试,或用 PIWEB_CLOUDFLARED 指定它的路径。",
|
|
430
|
+
remoteFailed: "操作失败",
|
|
431
|
+
remoteBlockedAuth: "认证没开,远程连接不可用:开出去就是一个免密入口。请用 piweb 启动器启动,或设 PI_WEB_AUTH=1。",
|
|
432
|
+
remoteBlockedDev: "开发模式只服务本机,远程连接不可用。请先构建,再用生产模式启动。",
|
|
433
|
+
remoteSessionsTitle: "已登录的设备",
|
|
434
|
+
remoteSessionsHint: "手机、别的电脑登录后一直有效(30 天)。设备丢了或链接被别人拿到时,可以让所有设备(包括这台)都要重新用启动令牌登录。",
|
|
435
|
+
remoteSignOutAll: "让所有设备重新登录",
|
|
436
|
+
remoteSignOutAllConfirm: "再点一次确认",
|
|
407
437
|
appearance: "外观",
|
|
408
438
|
appearanceMode: "亮暗模式",
|
|
409
439
|
themeLight: "浅色",
|
|
@@ -420,6 +450,12 @@ const zh = {
|
|
|
420
450
|
fontUiSizeDesc: "侧栏、按钮、标签等界面文字",
|
|
421
451
|
fontChatSize: "对话字体",
|
|
422
452
|
fontChatSizeDesc: "消息正文、用户气泡与输入框",
|
|
453
|
+
fontUiFamily: "界面字体族",
|
|
454
|
+
fontUiFamilyDesc: "侧栏、按钮、标签等界面文字使用的字体",
|
|
455
|
+
fontMonoFamily: "代码字体族",
|
|
456
|
+
fontMonoFamilyDesc: "代码块、diff、行内代码使用的等宽字体",
|
|
457
|
+
fontCustom: "自定义…",
|
|
458
|
+
fontCustomPlaceholder: "输入字体栈,如 Fira Code, monospace",
|
|
423
459
|
language: "界面语言",
|
|
424
460
|
openConfigFile: "打开配置文件",
|
|
425
461
|
providerAuthOk: "已配置",
|
|
@@ -433,7 +469,7 @@ const zh = {
|
|
|
433
469
|
apiKey: "API Key",
|
|
434
470
|
apiKeyStored: "已保存;留空保持不变",
|
|
435
471
|
providerIdImmutable: "Provider ID 创建后不可修改;如需更名,请删除后重建。",
|
|
436
|
-
modelConfigReopen: "
|
|
472
|
+
modelConfigReopen: "模型配置已保存并立即生效;正在回答的会话在本轮结束后生效。",
|
|
437
473
|
customModelIdentityNotice: "自定义模型 ID 只是发往该 API 地址的路由名;PiWeb 不会自动下载、接入或识别本机 Ollama 模型。",
|
|
438
474
|
customApiKeyHint: "可留空改用 auth.json;指向本机/私网的无鉴权服务(Ollama 等)保存时自动填占位值。",
|
|
439
475
|
modelsTitle: "模型",
|
|
@@ -557,8 +593,10 @@ const zh = {
|
|
|
557
593
|
growthScopeRound: "本轮",
|
|
558
594
|
growthScopeSession: "本会话累计",
|
|
559
595
|
growthOnlyChanges: "只看改动",
|
|
560
|
-
|
|
561
|
-
|
|
596
|
+
// 轮数选择器:计数可点开轮次列表(不再有「跟随」开关)
|
|
597
|
+
growthPickRound: "选择轮次",
|
|
598
|
+
growthRoundJump: "输入轮号...",
|
|
599
|
+
growthRoundJumpHint: "输入轮号后按回车跳到该轮",
|
|
562
600
|
growthPrev: "上一轮",
|
|
563
601
|
growthNext: "下一轮",
|
|
564
602
|
growthRoundPosition: "第 {current}/{total} 轮",
|
|
@@ -570,6 +608,12 @@ const zh = {
|
|
|
570
608
|
growthNoSession: "打开或新建一个会话后,这里会显示项目的生长过程。",
|
|
571
609
|
growthUnavailable: "机器上找不到 git,项目生长功能不可用",
|
|
572
610
|
growthUnavailableFallback: "生长记录不可用(找不到可用的 git),已显示当前目录结构",
|
|
611
|
+
growthConsentTitle: "要为这个工作区开启生长记录吗?",
|
|
612
|
+
growthConsentNotGit: "生长记录会把 pi 每一轮的改动存成 git 提交,方便回看和对比。这个目录还不是 git 仓库,开启后会在这里执行 git init(新建 .git 目录)。",
|
|
613
|
+
growthConsentNested: "生长记录会把 pi 每一轮的改动存成 git 提交,方便回看和对比。这个目录是另一个仓库({parent})的子目录,开启后会在这里再建一个嵌套的 git 仓库,外层仓库会把它当成一个子仓库看待。",
|
|
614
|
+
growthConsentNote: "记录挂在专用引用 refs/piweb/rounds/… 上,不动分支和暂存区。选择会按工作区记住。",
|
|
615
|
+
growthConsentAllow: "开启并建仓库",
|
|
616
|
+
growthConsentDecline: "不记录",
|
|
573
617
|
growthRoundLabel: "第 {n} 轮",
|
|
574
618
|
growthKindUser: "你的修改",
|
|
575
619
|
growthRoundFiles: "{n} 个文件",
|
|
@@ -625,6 +669,7 @@ const en: Dict = {
|
|
|
625
669
|
error: "Error",
|
|
626
670
|
copy: "Copy",
|
|
627
671
|
copyFailed: "Copy failed",
|
|
672
|
+
externalImage: "External image — click to load",
|
|
628
673
|
chatWidthHandle: "Drag to resize the conversation",
|
|
629
674
|
editMessage: "Edit this message",
|
|
630
675
|
sendEdit: "Send again",
|
|
@@ -750,8 +795,15 @@ const en: Dict = {
|
|
|
750
795
|
commandLines: "{n}-line script",
|
|
751
796
|
thinkLabel: "Thought",
|
|
752
797
|
expandOutput: "Show full output",
|
|
798
|
+
expandDiff: "Show changes",
|
|
753
799
|
collapseOutput: "Collapse",
|
|
754
800
|
workspaces: "Workspaces",
|
|
801
|
+
pickFolderTitle: "Choose a workspace folder",
|
|
802
|
+
pickFolderUp: "Parent folder",
|
|
803
|
+
pickFolderConfirm: "Use this folder",
|
|
804
|
+
pickFolderEmpty: "No subfolders here",
|
|
805
|
+
pickFolderTruncated: "Showing the first {n} folders",
|
|
806
|
+
pickFolderLoading: "Loading…",
|
|
755
807
|
searchSessions: "Search sessions...",
|
|
756
808
|
addWorkspace: "Add workspace",
|
|
757
809
|
groupBy: "Group by",
|
|
@@ -793,15 +845,10 @@ const en: Dict = {
|
|
|
793
845
|
noBranches: "No branch points available",
|
|
794
846
|
oauthLogin: "OAuth login",
|
|
795
847
|
authMethod: "Authentication",
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
loginSubmitting: "Signing in…",
|
|
801
|
-
loginFailed: "Incorrect password",
|
|
802
|
-
webAuth: "Password auth",
|
|
803
|
-
webAuthOn: "Enabled (PI_WEB_PASSWORD)",
|
|
804
|
-
webAuthOff: "Disabled (local access only)",
|
|
848
|
+
webAuth: "Access authentication",
|
|
849
|
+
webAuthTokenOn: "Enabled (launch token)",
|
|
850
|
+
loginTokenTitle: "This instance signs in with a launch token",
|
|
851
|
+
loginTokenDesc: "Open the login link printed in the terminal when PiWeb started (…/?token=…). The token changes on every restart; once opened, the browser remembers it for 30 days.",
|
|
805
852
|
logout: "Sign out",
|
|
806
853
|
authMethodApiKey: "API key",
|
|
807
854
|
oauthDevicePrompt: "Finish authorization in your browser",
|
|
@@ -974,11 +1021,10 @@ const en: Dict = {
|
|
|
974
1021
|
enterQueue: "Queue: sent after this turn ends",
|
|
975
1022
|
enterSteer: "Steer: delivered after the current tool call",
|
|
976
1023
|
setSkills: "Skills",
|
|
977
|
-
// Session import (other AI coding tools' local conversations → pi sessions)
|
|
978
1024
|
importSessions: "Import sessions",
|
|
979
1025
|
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.",
|
|
980
1026
|
importRescan: "Rescan",
|
|
981
|
-
importScanning: "Scanning
|
|
1027
|
+
importScanning: "Scanning...",
|
|
982
1028
|
importSourceClaude: "Claude Code",
|
|
983
1029
|
importSourceCodex: "Codex",
|
|
984
1030
|
importSourceGrok: "Grok",
|
|
@@ -994,15 +1040,42 @@ const en: Dict = {
|
|
|
994
1040
|
importSelectNone: "Clear group",
|
|
995
1041
|
importClearSelection: "Clear",
|
|
996
1042
|
importRun: "Import selected ({n})",
|
|
997
|
-
importRunning: "Importing
|
|
1043
|
+
importRunning: "Importing...",
|
|
998
1044
|
importFallbackWorkspace: "Fallback workspace",
|
|
1045
|
+
importFallbackHint: "Used when the original project folder is gone",
|
|
1046
|
+
importWhereGoes: "Destination",
|
|
1047
|
+
importGoesOriginal: "Back to its original workspace",
|
|
1048
|
+
importGoesFallback: "Original folder is gone — uses the fallback workspace",
|
|
999
1049
|
importAlready: "Imported",
|
|
1000
1050
|
importUntitled: "(untitled)",
|
|
1001
1051
|
importNoProject: "(no project directory)",
|
|
1002
1052
|
importMessages: "{n} messages",
|
|
1003
1053
|
importEmpty: "No importable local sessions found",
|
|
1004
1054
|
importReportSummary: "Imported {imported} · skipped {skipped} · failed {failed}",
|
|
1055
|
+
drawerPanel: "Drawer",
|
|
1056
|
+
drawerSections: "Drawer sections",
|
|
1057
|
+
drawerPrompts: "Prompts",
|
|
1005
1058
|
setPlugins: "Plugins",
|
|
1059
|
+
setRemote: "Remote access",
|
|
1060
|
+
remoteBuiltin: "Built-in plugin",
|
|
1061
|
+
remoteLanTitle: "Local network",
|
|
1062
|
+
remoteLanHint: "Devices on the same Wi-Fi can open the address below; the mobile layout is already applied. On by default.",
|
|
1063
|
+
remotePublicTitle: "Public access",
|
|
1064
|
+
remotePublicHint: "Once on, any browser can open it with nothing to install. Cloudflare assigns a random address that changes on every start.",
|
|
1065
|
+
remoteStart: "Turn on",
|
|
1066
|
+
remoteStop: "Turn off",
|
|
1067
|
+
remoteWorking: "Starting…",
|
|
1068
|
+
remoteRunning: "Running",
|
|
1069
|
+
remoteStopped: "Off",
|
|
1070
|
+
remotePending: "Address created, taking effect…",
|
|
1071
|
+
remoteEngineMissing: "cloudflared not found. Install it and retry, or point PIWEB_CLOUDFLARED at it.",
|
|
1072
|
+
remoteFailed: "Action failed",
|
|
1073
|
+
remoteBlockedAuth: "Remote access is unavailable because authentication is off — it would be an open, password-free entry. Start with the piweb launcher, or set PI_WEB_AUTH=1.",
|
|
1074
|
+
remoteBlockedDev: "Development mode serves this machine only, so remote access is unavailable. Build first, then start in production mode.",
|
|
1075
|
+
remoteSessionsTitle: "Signed-in devices",
|
|
1076
|
+
remoteSessionsHint: "Phones and other computers stay signed in for 30 days. If a device is lost or a link leaked, make every device (including this one) sign in again with the launch token.",
|
|
1077
|
+
remoteSignOutAll: "Sign out all devices",
|
|
1078
|
+
remoteSignOutAllConfirm: "Click again to confirm",
|
|
1006
1079
|
appearance: "Appearance",
|
|
1007
1080
|
appearanceMode: "Light / Dark",
|
|
1008
1081
|
themeLight: "Light",
|
|
@@ -1019,6 +1092,12 @@ const en: Dict = {
|
|
|
1019
1092
|
fontUiSizeDesc: "Sidebar, buttons, labels and other UI text",
|
|
1020
1093
|
fontChatSize: "Chat font size",
|
|
1021
1094
|
fontChatSizeDesc: "Message body, user bubbles and the composer",
|
|
1095
|
+
fontUiFamily: "UI font family",
|
|
1096
|
+
fontUiFamilyDesc: "Font used by the sidebar, buttons, labels and other UI text",
|
|
1097
|
+
fontMonoFamily: "Code font family",
|
|
1098
|
+
fontMonoFamilyDesc: "Monospace font used by code blocks, diffs and inline code",
|
|
1099
|
+
fontCustom: "Custom…",
|
|
1100
|
+
fontCustomPlaceholder: "Font stack, e.g. Fira Code, monospace",
|
|
1022
1101
|
language: "Language",
|
|
1023
1102
|
openConfigFile: "Open config file",
|
|
1024
1103
|
providerAuthOk: "Configured",
|
|
@@ -1032,7 +1111,7 @@ const en: Dict = {
|
|
|
1032
1111
|
apiKey: "API Key",
|
|
1033
1112
|
apiKeyStored: "Saved; leave blank to keep it",
|
|
1034
1113
|
providerIdImmutable: "A Provider ID cannot be changed after creation. Delete and recreate it to rename it.",
|
|
1035
|
-
modelConfigReopen: "Model configuration saved
|
|
1114
|
+
modelConfigReopen: "Model configuration saved and applied. Sessions that are answering pick it up after the current turn.",
|
|
1036
1115
|
customModelIdentityNotice: "A custom model ID is only a route name sent to that API endpoint. PiWeb does not automatically download, attach, or identify local Ollama models.",
|
|
1037
1116
|
customApiKeyHint: "Leave blank to use auth.json. Keyless local/private services (Ollama etc.) get a placeholder key automatically on save.",
|
|
1038
1117
|
modelsTitle: "Models",
|
|
@@ -1155,8 +1234,10 @@ const en: Dict = {
|
|
|
1155
1234
|
growthScopeRound: "This round",
|
|
1156
1235
|
growthScopeSession: "Session total",
|
|
1157
1236
|
growthOnlyChanges: "Changes only",
|
|
1158
|
-
|
|
1159
|
-
|
|
1237
|
+
// Round picker: the counter opens a round list (no "Follow" switch any more)
|
|
1238
|
+
growthPickRound: "Select round",
|
|
1239
|
+
growthRoundJump: "Enter round number...",
|
|
1240
|
+
growthRoundJumpHint: "Type a round number and press Enter to jump",
|
|
1160
1241
|
growthPrev: "Previous round",
|
|
1161
1242
|
growthNext: "Next round",
|
|
1162
1243
|
growthRoundPosition: "Round {current}/{total}",
|
|
@@ -1168,6 +1249,12 @@ const en: Dict = {
|
|
|
1168
1249
|
growthNoSession: "Open or start a session to watch the project grow here.",
|
|
1169
1250
|
growthUnavailable: "git was not found on this machine; project growth is unavailable",
|
|
1170
1251
|
growthUnavailableFallback: "Growth history is unavailable (no usable git); showing the current directory structure",
|
|
1252
|
+
growthConsentTitle: "Turn on growth history for this workspace?",
|
|
1253
|
+
growthConsentNotGit: "Growth history saves each pi turn's changes as git commits so you can review and compare them. This folder is not a git repository yet; turning it on runs git init here (creates a .git folder).",
|
|
1254
|
+
growthConsentNested: "Growth history saves each pi turn's changes as git commits so you can review and compare them. This folder is inside another repository ({parent}); turning it on creates a nested git repository here, which the outer repository will see as a sub-repository.",
|
|
1255
|
+
growthConsentNote: "Commits live on a private ref (refs/piweb/rounds/…) and never touch your branches or index. The choice is remembered per workspace.",
|
|
1256
|
+
growthConsentAllow: "Turn on and create repository",
|
|
1257
|
+
growthConsentDecline: "Don't record",
|
|
1171
1258
|
growthRoundLabel: "Round {n}",
|
|
1172
1259
|
growthKindUser: "Your changes",
|
|
1173
1260
|
growthRoundFiles: "{n} files",
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 服务启动钩子的 Node 部分(只由 instrumentation.ts 在 nodejs 运行时引入)。
|
|
3
|
+
*
|
|
4
|
+
* 这里用到 node:child_process 等模块,edge 运行时编不过;放在单独文件里,
|
|
5
|
+
* edge 那份编译就整段跳过了(见 instrumentation.ts)。
|
|
6
|
+
*/
|
|
7
|
+
export async function registerNode() {
|
|
8
|
+
// 构建期也会实例化服务,别在构建机上开监听
|
|
9
|
+
if (process.env.NEXT_PHASE === "phase-production-build") return;
|
|
10
|
+
|
|
11
|
+
// 再次运行启动器时要能找回本实例的登录链接(见 publishLaunchToken)
|
|
12
|
+
const { authEnabled } = await import("./lib/web-auth");
|
|
13
|
+
if (authEnabled()) (await import("./lib/web-launch-token")).publishLaunchToken();
|
|
14
|
+
|
|
15
|
+
const { remoteBlocked, remoteLanEnabled, startLan } = await import("./lib/remote/supervisor");
|
|
16
|
+
if (!remoteLanEnabled()) return;
|
|
17
|
+
|
|
18
|
+
// 局域网入口会把 PiWeb 暴露给同网段的所有设备:没认证、或是开发模式,都绝不能开。
|
|
19
|
+
const blocked = remoteBlocked();
|
|
20
|
+
if (blocked === "auth") {
|
|
21
|
+
console.warn("[远程连接] 已跳过局域网入口:认证没开。请设 PI_WEB_AUTH=1。");
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (blocked === "dev") {
|
|
25
|
+
console.warn("[远程连接] 已跳过局域网入口:开发模式只服务本机(认证保护不了 Next 开发工具端点)。");
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const state = await startLan();
|
|
30
|
+
if (state.running) {
|
|
31
|
+
console.log(`[远程连接] 局域网入口已就绪:${state.urls.join(" ")}`);
|
|
32
|
+
} else {
|
|
33
|
+
console.warn(`[远程连接] 局域网入口启动失败:${state.error}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 服务启动钩子:「远程连接」内置插件的局域网入口在这里拉起。
|
|
3
|
+
*
|
|
4
|
+
* 为什么必须在这里:局域网入口要「默认就在跑」(手机连同一个 WiFi 就能用),
|
|
5
|
+
* 而 Next 的请求处理里没有「服务刚起来」这个时机 —— instrumentation 的 register()
|
|
6
|
+
* 是官方给的入口,而且它返回前服务不会开始处理请求。
|
|
7
|
+
*
|
|
8
|
+
* Next 会把本文件按 nodejs 和 edge 各编译一份。Node 专用的代码只能写在
|
|
9
|
+
* `=== "nodejs"` 的分支里再 import:edge 那份里条件是常量 false,webpack 整段跳过。
|
|
10
|
+
* 写成「不是 nodejs 就 return,后面再 import」不行 —— webpack 照样解析后面的 import,
|
|
11
|
+
* edge 编译碰到 node:child_process 就报错,dev 下所有页面跟着 500。
|
|
12
|
+
*/
|
|
13
|
+
export async function register() {
|
|
14
|
+
if (process.env.NEXT_RUNTIME === "nodejs") {
|
|
15
|
+
const { registerNode } = await import("./instrumentation-node");
|
|
16
|
+
await registerNode();
|
|
17
|
+
}
|
|
18
|
+
}
|