@oversk7/pi-web-dsh-style 1.0.0 → 1.0.1
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/README.md +39 -3
- package/frp-process.ts +135 -0
- package/index.ts +35 -3
- package/local-session.ts +10 -2
- package/network-access.ts +171 -0
- package/network-settings.ts +146 -0
- package/package.json +5 -2
- package/server.ts +204 -32
- package/web/app.css +79 -2
- package/web/app.js +437 -44
- package/web/index.html +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oversk7/pi-web-dsh-style",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "An unofficial DSH-styled
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "An unofficial DSH-styled Windows browser UI for pi coding agent with configurable LAN and native frp relay access",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "oversk7",
|
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
"file-preview.ts",
|
|
26
26
|
"index.ts",
|
|
27
27
|
"local-session.ts",
|
|
28
|
+
"network-access.ts",
|
|
29
|
+
"network-settings.ts",
|
|
30
|
+
"frp-process.ts",
|
|
28
31
|
"pi-rpc.ts",
|
|
29
32
|
"sse-channel.ts",
|
|
30
33
|
"server.ts",
|
package/server.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { createServer } from "node:http";
|
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
5
|
import { existsSync } from "node:fs";
|
|
6
|
-
import { mkdir, readFile, readdir, stat, unlink } from "node:fs/promises";
|
|
6
|
+
import { chmod, mkdir, mkdtemp, readFile, readdir, rm, stat, unlink } from "node:fs/promises";
|
|
7
7
|
import { homedir, platform, tmpdir } from "node:os";
|
|
8
8
|
import { basename, dirname, extname, join, normalize, resolve, sep } from "node:path";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
@@ -17,10 +17,13 @@ import {
|
|
|
17
17
|
type RpcQueueSnapshot,
|
|
18
18
|
} from "./abort-session.ts";
|
|
19
19
|
import { ImageAttachmentStore } from "./attachment-store.ts";
|
|
20
|
+
import { isLocalRequest, WebAccess } from "./network-access.ts";
|
|
21
|
+
import { FrpProcess } from "./frp-process.ts";
|
|
22
|
+
import { frpcConfig, loadNetworkSettings, proxyForNetwork, publicNetworkSettings, relayServerFiles, resolveFrpc, saveNetworkSettings, updateNetworkSettings, type NetworkMode, type NetworkSettings } from "./network-settings.ts";
|
|
20
23
|
import { closeSseClient, createSseClient, sendSseEvent, type SseClient } from "./sse-channel.ts";
|
|
21
24
|
import { closeAllPiRpcs, spawnPiRpc, type Json, type PiRpc } from "./pi-rpc.ts";
|
|
22
25
|
import { loadLocalSession } from "./local-session.ts";
|
|
23
|
-
import { loadJsonWithLegacyMigration, writeJsonAtomically } from "./state-store.ts";
|
|
26
|
+
import { loadJsonWithLegacyMigration, writeJsonAtomically, writeTextAtomically } from "./state-store.ts";
|
|
24
27
|
import { buildFilePreview } from "./file-preview.ts";
|
|
25
28
|
import { readClipboardImage, readClipboardText } from "./windows-clipboard.ts";
|
|
26
29
|
import {
|
|
@@ -44,6 +47,8 @@ const DEFAULT_PORT = 18789;
|
|
|
44
47
|
const STATE_DIR = process.env.PI_WEB_STATE_DIR || join(getAgentDir(), "pi-web");
|
|
45
48
|
const STATE_FILE = resolve(process.env.PI_WEB_STATE_FILE || join(STATE_DIR, "state.json"));
|
|
46
49
|
const LEGACY_STATE_FILE = resolve(process.env.PI_WEB_LEGACY_STATE_FILE || join(here, "state.json"));
|
|
50
|
+
const AUTH_FILE = join(dirname(STATE_FILE), "auth.json");
|
|
51
|
+
const NETWORK_FILE = join(dirname(STATE_FILE), "network.json");
|
|
47
52
|
|
|
48
53
|
interface Workspace {
|
|
49
54
|
id: string;
|
|
@@ -205,6 +210,13 @@ interface MaintenanceCommandResult {
|
|
|
205
210
|
let server: ReturnType<typeof createServer> | undefined;
|
|
206
211
|
let serverUrl: string | undefined;
|
|
207
212
|
let serverInstanceId: string | undefined;
|
|
213
|
+
let webAccess = new WebAccess(false);
|
|
214
|
+
let networkSettings: NetworkSettings = { mode: "local" };
|
|
215
|
+
let networkApplying = false;
|
|
216
|
+
let networkChange: Promise<void> = Promise.resolve();
|
|
217
|
+
let networkError: string | undefined;
|
|
218
|
+
let frpRuntimeDir: string | undefined;
|
|
219
|
+
const frpProcess = new FrpProcess(() => broadcastEvent({ type: "network_changed" }));
|
|
208
220
|
let updateExtensions: (() => Promise<MaintenanceCommandResult>) | undefined;
|
|
209
221
|
let reloadRuntime: (() => Promise<void>) | undefined;
|
|
210
222
|
let maintenanceRunning = false;
|
|
@@ -1707,28 +1719,6 @@ function readJsonBody(req: import("node:http").IncomingMessage, limit = 12 * 102
|
|
|
1707
1719
|
});
|
|
1708
1720
|
}
|
|
1709
1721
|
|
|
1710
|
-
function isTrustedLocalMutation(req: import("node:http").IncomingMessage): boolean {
|
|
1711
|
-
const host = req.headers.host ?? "";
|
|
1712
|
-
let hostname = "";
|
|
1713
|
-
try {
|
|
1714
|
-
hostname = new URL(`http://${host}`).hostname.toLowerCase();
|
|
1715
|
-
} catch {
|
|
1716
|
-
return false;
|
|
1717
|
-
}
|
|
1718
|
-
if (hostname !== "127.0.0.1" && hostname !== "localhost" && hostname !== "[::1]") return false;
|
|
1719
|
-
const fetchSite = req.headers["sec-fetch-site"];
|
|
1720
|
-
if (fetchSite === "cross-site") return false;
|
|
1721
|
-
const origin = req.headers.origin;
|
|
1722
|
-
if (origin) {
|
|
1723
|
-
try {
|
|
1724
|
-
if (new URL(origin).host.toLowerCase() !== host.toLowerCase()) return false;
|
|
1725
|
-
} catch {
|
|
1726
|
-
return false;
|
|
1727
|
-
}
|
|
1728
|
-
}
|
|
1729
|
-
return true;
|
|
1730
|
-
}
|
|
1731
|
-
|
|
1732
1722
|
function findRecord(id: string): RuntimeSession | undefined {
|
|
1733
1723
|
return sessions.get(id);
|
|
1734
1724
|
}
|
|
@@ -2083,8 +2073,76 @@ function scheduleRuntimeReload(): void {
|
|
|
2083
2073
|
async function handleApi(req: import("node:http").IncomingMessage, res: ServerResponse, url: URL): Promise<boolean> {
|
|
2084
2074
|
const path = url.pathname;
|
|
2085
2075
|
const method = req.method ?? "GET";
|
|
2086
|
-
if (
|
|
2087
|
-
|
|
2076
|
+
if (!webAccess.trusted(req)) return sendJson(res, 403, { error: "forbidden origin" });
|
|
2077
|
+
res.setHeader("cache-control", "no-store");
|
|
2078
|
+
if (method === "GET" && path === "/api/auth") {
|
|
2079
|
+
return sendJson(res, 200, { passwordConfigured: webAccess.passwordConfigured });
|
|
2080
|
+
}
|
|
2081
|
+
if (method === "POST" && path === "/api/auth") {
|
|
2082
|
+
const body = await readJsonBody(req, 2048);
|
|
2083
|
+
if (!webAccess.passwordConfigured) {
|
|
2084
|
+
return sendJson(res, 503, { error: "请先在电脑网页的设置中设置访问密码" });
|
|
2085
|
+
}
|
|
2086
|
+
if (!webAccess.remoteEnabled || !await webAccess.acceptsPassword(body.password)) {
|
|
2087
|
+
return sendJson(res, 401, { error: "密码不正确" });
|
|
2088
|
+
}
|
|
2089
|
+
res.setHeader("set-cookie", webAccess.cookie(req));
|
|
2090
|
+
return sendJson(res, 200, { ok: true });
|
|
2091
|
+
}
|
|
2092
|
+
if (!webAccess.authorized(req)) {
|
|
2093
|
+
return sendJson(res, 401, { error: "请输入访问密码" });
|
|
2094
|
+
}
|
|
2095
|
+
if (method === "POST" && path === "/api/auth/password") {
|
|
2096
|
+
if (!isLocalRequest(req)) return sendJson(res, 403, { error: "请在电脑本机网页设置密码" });
|
|
2097
|
+
if (networkApplying) return sendJson(res, 409, { error: "网络设置正在应用,请稍后保存密码" });
|
|
2098
|
+
networkApplying = true;
|
|
2099
|
+
try {
|
|
2100
|
+
const body = await readJsonBody(req, 2048);
|
|
2101
|
+
await webAccess.setPassword(body.password, AUTH_FILE);
|
|
2102
|
+
} catch (error) {
|
|
2103
|
+
return sendJson(res, 400, { error: error instanceof Error ? error.message : String(error) });
|
|
2104
|
+
} finally {
|
|
2105
|
+
networkApplying = false;
|
|
2106
|
+
}
|
|
2107
|
+
for (const client of [...sseClients]) {
|
|
2108
|
+
closeSseClient(client);
|
|
2109
|
+
(client.res as ServerResponse).end();
|
|
2110
|
+
}
|
|
2111
|
+
sseClients.clear();
|
|
2112
|
+
return sendJson(res, 200, { ok: true });
|
|
2113
|
+
}
|
|
2114
|
+
|
|
2115
|
+
if (path === "/api/network" || path === "/api/network/server-files") {
|
|
2116
|
+
if (!isLocalRequest(req)) return sendJson(res, 403, { error: "请在电脑本机网页配置网络访问" });
|
|
2117
|
+
if (method === "GET" && path === "/api/network/server-files") {
|
|
2118
|
+
if (!networkSettings.relay) return sendJson(res, 400, { error: "请先保存服务器中转配置" });
|
|
2119
|
+
return sendJson(res, 200, { files: relayServerFiles(networkSettings.relay) });
|
|
2120
|
+
}
|
|
2121
|
+
if (method === "GET" && path === "/api/network") return sendJson(res, 200, networkView());
|
|
2122
|
+
if (method === "POST" && path === "/api/network") {
|
|
2123
|
+
if (networkApplying) return sendJson(res, 409, { error: "网络设置正在应用" });
|
|
2124
|
+
networkApplying = true;
|
|
2125
|
+
try {
|
|
2126
|
+
const next = updateNetworkSettings(await readJsonBody(req, 16 * 1024), networkSettings);
|
|
2127
|
+
if (next.mode === "relay") await resolveFrpc(next.relay!.frpcPath, dirname(NETWORK_FILE));
|
|
2128
|
+
await saveNetworkSettings(NETWORK_FILE, next);
|
|
2129
|
+
networkSettings = next;
|
|
2130
|
+
networkError = undefined;
|
|
2131
|
+
sendJson(res, 200, networkView());
|
|
2132
|
+
networkChange = new Promise<void>((resolveChange) => setImmediate(resolveChange)).then(async () => {
|
|
2133
|
+
if (!shuttingDown) await applyNetworkSettings();
|
|
2134
|
+
}).catch((error) => {
|
|
2135
|
+
networkError = error instanceof Error ? error.message : String(error);
|
|
2136
|
+
}).finally(() => {
|
|
2137
|
+
networkApplying = false;
|
|
2138
|
+
broadcastEvent({ type: "network_changed" });
|
|
2139
|
+
});
|
|
2140
|
+
return true;
|
|
2141
|
+
} catch (error) {
|
|
2142
|
+
networkApplying = false;
|
|
2143
|
+
return sendJson(res, 400, { error: error instanceof Error ? error.message : String(error) });
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2088
2146
|
}
|
|
2089
2147
|
|
|
2090
2148
|
if (method === "GET" && path === "/api/bootstrap") {
|
|
@@ -2096,6 +2154,10 @@ async function handleApi(req: import("node:http").IncomingMessage, res: ServerRe
|
|
|
2096
2154
|
commands: bootstrapCommands(),
|
|
2097
2155
|
url: serverUrl,
|
|
2098
2156
|
instanceId: serverInstanceId,
|
|
2157
|
+
localClient: isLocalRequest(req),
|
|
2158
|
+
lanEnabled: webAccess.remoteEnabled,
|
|
2159
|
+
passwordConfigured: webAccess.passwordConfigured,
|
|
2160
|
+
lanUrls: isLocalRequest(req) ? getWebLanUrls() : [],
|
|
2099
2161
|
});
|
|
2100
2162
|
}
|
|
2101
2163
|
|
|
@@ -2137,6 +2199,19 @@ async function handleApi(req: import("node:http").IncomingMessage, res: ServerRe
|
|
|
2137
2199
|
}
|
|
2138
2200
|
}
|
|
2139
2201
|
|
|
2202
|
+
if (method === "POST" && path === "/api/attachments") {
|
|
2203
|
+
const body = await readJsonBody(req, 21 * 1024 * 1024);
|
|
2204
|
+
if (typeof body.data !== "string" || !/^[A-Za-z0-9+/]+={0,2}$/.test(body.data)) {
|
|
2205
|
+
return sendJson(res, 400, { error: "图片数据无效" });
|
|
2206
|
+
}
|
|
2207
|
+
try {
|
|
2208
|
+
const attachment = await imageAttachments.create(Buffer.from(body.data, "base64"));
|
|
2209
|
+
return sendJson(res, 200, { attachment: { ...attachment, url: `/api/attachments/${attachment.id}` } });
|
|
2210
|
+
} catch (error) {
|
|
2211
|
+
return sendJson(res, 400, { error: error instanceof Error ? error.message : String(error) });
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2140
2215
|
const attachmentMatch = /^\/api\/attachments\/([a-f0-9-]+)$/.exec(path);
|
|
2141
2216
|
if (attachmentMatch && (method === "GET" || method === "HEAD")) {
|
|
2142
2217
|
try {
|
|
@@ -2922,19 +2997,77 @@ async function handleApi(req: import("node:http").IncomingMessage, res: ServerRe
|
|
|
2922
2997
|
// server lifecycle
|
|
2923
2998
|
// ---------------------------------------------------------------------------
|
|
2924
2999
|
|
|
2925
|
-
function
|
|
3000
|
+
function networkView() {
|
|
3001
|
+
const status = networkError ? { state: "error", message: networkError }
|
|
3002
|
+
: networkSettings.mode === "relay" ? frpProcess.status
|
|
3003
|
+
: { state: networkSettings.mode === "lan" ? "connected" : "stopped", message: networkSettings.mode === "lan" ? "局域网访问已开启" : "仅本机访问" };
|
|
3004
|
+
return { settings: publicNetworkSettings(networkSettings), status, urls: getWebLanUrls(), applying: networkApplying };
|
|
3005
|
+
}
|
|
3006
|
+
|
|
3007
|
+
function disconnectWebClients(): void {
|
|
3008
|
+
for (const client of [...sseClients]) {
|
|
3009
|
+
closeSseClient(client);
|
|
3010
|
+
(client.res as ServerResponse).end();
|
|
3011
|
+
}
|
|
3012
|
+
sseClients.clear();
|
|
3013
|
+
}
|
|
3014
|
+
|
|
3015
|
+
async function stopRelay(): Promise<void> {
|
|
3016
|
+
await frpProcess.stop();
|
|
3017
|
+
if (frpRuntimeDir) await rm(frpRuntimeDir, { recursive: true, force: true });
|
|
3018
|
+
frpRuntimeDir = undefined;
|
|
3019
|
+
}
|
|
3020
|
+
|
|
3021
|
+
async function startRelay(): Promise<void> {
|
|
3022
|
+
if (networkSettings.mode !== "relay" || !networkSettings.relay || !serverUrl) return;
|
|
3023
|
+
const relay = networkSettings.relay;
|
|
3024
|
+
const executable = await resolveFrpc(relay.frpcPath, dirname(NETWORK_FILE));
|
|
3025
|
+
const runtimeRoot = join(dirname(NETWORK_FILE), "runtime");
|
|
3026
|
+
await mkdir(runtimeRoot, { recursive: true });
|
|
3027
|
+
frpRuntimeDir = await mkdtemp(join(runtimeRoot, "frpc-"));
|
|
3028
|
+
const configPath = join(frpRuntimeDir, "frpc.toml");
|
|
3029
|
+
await writeTextAtomically(configPath, frpcConfig(relay, Number(new URL(serverUrl).port)));
|
|
3030
|
+
await chmod(configPath, 0o600);
|
|
3031
|
+
await frpProcess.start({ executable, args: ["-c", configPath], cwd: dirname(NETWORK_FILE) });
|
|
3032
|
+
}
|
|
3033
|
+
|
|
3034
|
+
async function applyNetworkSettings(): Promise<void> {
|
|
3035
|
+
if (!server || !serverUrl) return;
|
|
3036
|
+
await stopRelay();
|
|
3037
|
+
const next = await WebAccess.load(networkSettings.mode === "lan", AUTH_FILE, proxyForNetwork(networkSettings));
|
|
3038
|
+
disconnectWebClients();
|
|
3039
|
+
if (next.lan !== webAccess.lan) {
|
|
3040
|
+
const port = Number(new URL(serverUrl).port);
|
|
3041
|
+
const previousHost = webAccess.lan ? "0.0.0.0" : HOST;
|
|
3042
|
+
const app = server;
|
|
3043
|
+
await new Promise<void>((resolveClose) => {
|
|
3044
|
+
app.close(() => resolveClose());
|
|
3045
|
+
app.closeAllConnections();
|
|
3046
|
+
});
|
|
3047
|
+
try {
|
|
3048
|
+
await listenOn(app, port, next.lan ? "0.0.0.0" : HOST, false);
|
|
3049
|
+
} catch (error) {
|
|
3050
|
+
await listenOn(app, port, previousHost, false);
|
|
3051
|
+
throw error;
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
webAccess = next;
|
|
3055
|
+
await startRelay();
|
|
3056
|
+
}
|
|
3057
|
+
|
|
3058
|
+
function listenOn(app: ReturnType<typeof createServer>, preferredPort: number, host = HOST, allowFallback = true): Promise<number> {
|
|
2926
3059
|
return new Promise((resolvePort, reject) => {
|
|
2927
3060
|
const onError = (error: NodeJS.ErrnoException) => {
|
|
2928
|
-
if (error.code === "EADDRINUSE" && preferredPort !== 0) {
|
|
3061
|
+
if (allowFallback && error.code === "EADDRINUSE" && preferredPort !== 0) {
|
|
2929
3062
|
app.removeListener("error", onError);
|
|
2930
|
-
listenOn(app, 0).then(resolvePort, reject);
|
|
3063
|
+
listenOn(app, 0, host).then(resolvePort, reject);
|
|
2931
3064
|
return;
|
|
2932
3065
|
}
|
|
2933
3066
|
app.removeListener("error", onError);
|
|
2934
3067
|
reject(error);
|
|
2935
3068
|
};
|
|
2936
3069
|
app.once("error", onError);
|
|
2937
|
-
app.listen(preferredPort,
|
|
3070
|
+
app.listen(preferredPort, host, () => {
|
|
2938
3071
|
app.removeListener("error", onError);
|
|
2939
3072
|
const address = app.address();
|
|
2940
3073
|
resolvePort(address && typeof address === "object" ? address.port : preferredPort);
|
|
@@ -2950,6 +3083,7 @@ function openBrowser(url: string): void {
|
|
|
2950
3083
|
}
|
|
2951
3084
|
|
|
2952
3085
|
function exitHook(): void {
|
|
3086
|
+
frpProcess.kill();
|
|
2953
3087
|
for (const record of sessions.values()) {
|
|
2954
3088
|
try {
|
|
2955
3089
|
record.proc?.close();
|
|
@@ -2972,6 +3106,8 @@ function shutdown(): Promise<void> {
|
|
|
2972
3106
|
}
|
|
2973
3107
|
|
|
2974
3108
|
async function shutdownServer(): Promise<void> {
|
|
3109
|
+
await networkChange;
|
|
3110
|
+
await stopRelay();
|
|
2975
3111
|
for (const client of [...sseClients]) {
|
|
2976
3112
|
closeSseClient(client);
|
|
2977
3113
|
try {
|
|
@@ -3010,6 +3146,8 @@ export async function startWebServer(options: {
|
|
|
3010
3146
|
defaultCwd?: string;
|
|
3011
3147
|
port?: number;
|
|
3012
3148
|
open?: boolean;
|
|
3149
|
+
lan?: boolean;
|
|
3150
|
+
mode?: NetworkMode;
|
|
3013
3151
|
initialModelInfo?: ModelInfo;
|
|
3014
3152
|
initialCommands?: unknown[];
|
|
3015
3153
|
updateExtensions?: () => Promise<MaintenanceCommandResult>;
|
|
@@ -3019,7 +3157,16 @@ export async function startWebServer(options: {
|
|
|
3019
3157
|
const initialCwd = normalizePath(options.defaultCwd ?? process.cwd());
|
|
3020
3158
|
updateExtensions = options.updateExtensions;
|
|
3021
3159
|
reloadRuntime = options.reloadRuntime;
|
|
3160
|
+
const mode = options.mode ?? (options.lan === undefined ? undefined : options.lan ? "lan" : "local");
|
|
3022
3161
|
if (serverUrl && server) {
|
|
3162
|
+
await networkChange;
|
|
3163
|
+
if (mode !== undefined) {
|
|
3164
|
+
networkSettings = updateNetworkSettings({ mode }, networkSettings);
|
|
3165
|
+
await saveNetworkSettings(NETWORK_FILE, networkSettings);
|
|
3166
|
+
networkError = undefined;
|
|
3167
|
+
await applyNetworkSettings();
|
|
3168
|
+
broadcastEvent({ type: "network_changed" });
|
|
3169
|
+
}
|
|
3023
3170
|
if (options.initialCommands?.length) commandCache.set(initialCwd, normalizeCommands(options.initialCommands));
|
|
3024
3171
|
if (options.open !== false) openBrowser(serverUrl);
|
|
3025
3172
|
return serverUrl;
|
|
@@ -3037,9 +3184,23 @@ export async function startWebServer(options: {
|
|
|
3037
3184
|
}
|
|
3038
3185
|
if (options.initialCommands?.length) commandCache.set(initialCwd, normalizeCommands(options.initialCommands));
|
|
3039
3186
|
if (state.workspaces.length === 0 && options.defaultCwd) await addWorkspace(options.defaultCwd);
|
|
3040
|
-
const requestedPort = options.port
|
|
3187
|
+
const requestedPort = options.port ?? DEFAULT_PORT;
|
|
3188
|
+
if (!Number.isInteger(requestedPort) || requestedPort < 0 || requestedPort > 65535) {
|
|
3189
|
+
throw new Error("端口必须是 0–65535 之间的整数");
|
|
3190
|
+
}
|
|
3191
|
+
networkSettings = await loadNetworkSettings(NETWORK_FILE);
|
|
3192
|
+
if (mode !== undefined) {
|
|
3193
|
+
networkSettings = updateNetworkSettings({ mode }, networkSettings);
|
|
3194
|
+
await saveNetworkSettings(NETWORK_FILE, networkSettings);
|
|
3195
|
+
}
|
|
3196
|
+
networkApplying = false;
|
|
3197
|
+
networkError = undefined;
|
|
3198
|
+
webAccess = await WebAccess.load(networkSettings.mode === "lan", AUTH_FILE, proxyForNetwork(networkSettings));
|
|
3041
3199
|
const app = createServer(async (req, res) => {
|
|
3042
3200
|
try {
|
|
3201
|
+
res.setHeader("referrer-policy", "no-referrer");
|
|
3202
|
+
res.setHeader("x-content-type-options", "nosniff");
|
|
3203
|
+
res.setHeader("x-frame-options", "DENY");
|
|
3043
3204
|
if (shuttingDown) return sendJson(res, 503, { error: "pi-web 正在关闭" });
|
|
3044
3205
|
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? `${HOST}:${requestedPort}`}`);
|
|
3045
3206
|
if (url.pathname.startsWith("/api/")) {
|
|
@@ -3053,7 +3214,7 @@ export async function startWebServer(options: {
|
|
|
3053
3214
|
else res.end();
|
|
3054
3215
|
}
|
|
3055
3216
|
});
|
|
3056
|
-
const port = await listenOn(app, requestedPort);
|
|
3217
|
+
const port = await listenOn(app, requestedPort, webAccess.lan ? "0.0.0.0" : HOST);
|
|
3057
3218
|
server = app;
|
|
3058
3219
|
serverUrl = `http://${HOST}:${port}`;
|
|
3059
3220
|
serverInstanceId = randomUUID();
|
|
@@ -3061,6 +3222,9 @@ export async function startWebServer(options: {
|
|
|
3061
3222
|
process.on("exit", exitHook);
|
|
3062
3223
|
process.on("SIGINT", sigintHook);
|
|
3063
3224
|
process.on("SIGTERM", sigintHook);
|
|
3225
|
+
try { await startRelay(); } catch (error) {
|
|
3226
|
+
networkError = error instanceof Error ? error.message : String(error);
|
|
3227
|
+
}
|
|
3064
3228
|
if (options.open !== false) openBrowser(serverUrl);
|
|
3065
3229
|
return serverUrl;
|
|
3066
3230
|
}
|
|
@@ -3072,3 +3236,11 @@ export async function stopWebServer(): Promise<void> {
|
|
|
3072
3236
|
export function getWebUrl(): string | undefined {
|
|
3073
3237
|
return serverUrl;
|
|
3074
3238
|
}
|
|
3239
|
+
|
|
3240
|
+
export function getWebLanUrls(): string[] {
|
|
3241
|
+
return serverUrl ? webAccess.urls(Number(new URL(serverUrl).port)) : [];
|
|
3242
|
+
}
|
|
3243
|
+
|
|
3244
|
+
export function getWebConnectionOptions(): { port: number; lan: boolean; mode: NetworkMode } | undefined {
|
|
3245
|
+
return serverUrl ? { port: Number(new URL(serverUrl).port), lan: webAccess.lan, mode: networkSettings.mode } : undefined;
|
|
3246
|
+
}
|
package/web/app.css
CHANGED
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
.lXshSW_root{box-sizing:border-box;width:calc(100% - var(--dsh-composer-side-clearance) - var(--dsh-composer-side-clearance) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));max-width:calc(var(--dsh-composer-card-max-width) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));border:1px solid var(--dsw-alias-border-l1);background:var(--dsw-specific-tip);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border-radius:12px;flex:none;margin:0 auto;overflow:hidden}.lXshSW_body{flex-direction:column;gap:8px;padding:6px 12px;display:flex}.lXshSW_header{text-align:left;cursor:pointer;background:0 0;border:none;align-items:center;gap:10px;width:100%;padding:0;display:flex}.lXshSW_lead{color:var(--dsw-alias-label-tertiary);flex:none;place-items:center;display:grid}.lXshSW_title{color:var(--dsw-alias-label-primary);flex:none;font-size:13px;font-weight:500;line-height:24px}.lXshSW_progress{min-width:0;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;flex:auto;font-size:13px;font-weight:400;line-height:20px;overflow:hidden}.lXshSW_chevron{color:var(--dsw-alias-label-tertiary);flex:none;place-items:center;display:grid}.lXshSW_list{flex-direction:column;gap:8px;max-height:180px;margin:0;padding:0;list-style:none;display:flex;overflow-y:auto}.lXshSW_item{min-width:0;color:var(--dsw-alias-label-secondary);align-items:center;gap:10px;font-size:13px;line-height:20px;display:flex}.lXshSW_glyph{flex:none;place-items:center;width:16px;height:16px;display:grid}.lXshSW_glyphCompleted{color:var(--dsw-alias-state-success-primary)}.lXshSW_glyphPending{color:var(--dsw-alias-label-caption)}.lXshSW_glyphProgress{color:var(--dsw-alias-state-business-primary);animation:1s linear infinite lXshSW_todo-progress-spin}@keyframes lXshSW_todo-progress-spin{to{transform:rotate(360deg)}}.lXshSW_content{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}
|
|
59
59
|
._7yHdaG_dock{box-sizing:border-box;width:calc(100% - var(--dsh-composer-side-clearance) - var(--dsh-composer-side-clearance) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));max-width:calc(var(--dsh-composer-card-max-width) - var(--dsh-composer-dock-inset) - var(--dsh-composer-dock-inset));margin:0 auto calc(0px - var(--dsh-composer-stack-gap) - 3px);padding:0 var(--dsh-composer-dock-inset);flex:none}._7yHdaG_panel{background:var(--dsw-specific-tip);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border-radius:12px 12px 0 0;width:100%;padding:2px 0;position:relative;overflow:hidden}._7yHdaG_panel:after{border:1px solid var(--dsw-alias-border-l1);border-radius:inherit;content:"";pointer-events:none;border-bottom:none;position:absolute;inset:0}._7yHdaG_header{box-sizing:border-box;width:100%;height:36px;color:var(--dsw-alias-label-primary);text-align:left;cursor:pointer;background:0 0;border:none;border-radius:8px;align-items:center;gap:10px;padding:4px 12px;display:flex}._7yHdaG_header:focus-visible{outline:2px solid var(--dsw-alias-label-tertiary);outline-offset:-2px}._7yHdaG_header:disabled{cursor:default}._7yHdaG_lead{color:var(--dsw-alias-label-tertiary);flex:none;place-items:center;display:grid}._7yHdaG_count{min-width:0;font-family:Inter, var(--dsw-font-family);flex:auto;font-size:13px;font-weight:500;line-height:24px}._7yHdaG_chevron{width:14px;height:14px;color:var(--dsw-alias-label-tertiary);flex:none;place-items:center;display:grid}._7yHdaG_list{max-height:180px;margin:0;padding:0;list-style:none;overflow-y:auto}._7yHdaG_row{box-sizing:border-box;border-radius:8px;align-items:center;gap:10px;width:100%;height:36px;padding:4px 5px 4px 12px;display:flex}._7yHdaG_row+._7yHdaG_row{box-shadow:inset 0 1px 0 var(--dsw-alias-border-l1)}._7yHdaG_preview,._7yHdaG_editor{min-width:0;font:var(--dsw-font-xs-13);font-family:Inter, var(--dsw-font-family);flex:auto}._7yHdaG_preview{color:var(--dsw-alias-label-primary-dimmed);text-overflow:ellipsis;white-space:nowrap;word-break:break-word;overflow:hidden}._7yHdaG_editor{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);height:28px;color:var(--dsw-alias-label-primary);border-radius:6px;outline:none;padding:0 8px}._7yHdaG_editor:focus{border-color:var(--dsw-alias-state-business-primary)}._7yHdaG_actions{flex:none;align-items:center;gap:10px;display:flex}._7yHdaG_action{width:28px;height:28px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:999px;flex:none;place-items:center;padding:0;display:grid}._7yHdaG_action:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}._7yHdaG_action:focus-visible{outline:2px solid var(--dsw-alias-label-tertiary);outline-offset:-2px}._7yHdaG_action:disabled{cursor:default;opacity:.45}
|
|
60
60
|
.pXSMma_root{justify-content:center;align-items:center;min-width:0;height:100%;padding:0 24px;display:flex}.pXSMma_stack{width:100%;max-width:var(--dsh-composer-card-max-width);flex-direction:column;align-items:stretch;gap:12px;display:flex;overflow:visible}.pXSMma_headline{color:var(--dsw-alias-label-primary);grid-template-columns:34px auto auto;justify-content:center;align-items:center;column-gap:10px;font-size:26px;font-weight:500;line-height:32px;display:grid}.pXSMma_headlineText{grid-area:1/2}.pXSMma_previewBadge{border:1px solid var(--dsw-alias-interactive-bg-hover);background:var(--dsw-alias-state-business-tertiary);color:var(--dsw-alias-label-primary-bluish);font-family:var(--ds-font-family-code);white-space:nowrap;border-radius:24px;grid-area:1/3;align-self:start;margin-top:2px;margin-left:-3px;padding:1px 7px 0;font-size:12px;font-weight:500;line-height:18px}.pXSMma_fishHitbox{grid-area:1/1;justify-content:center;align-items:center;display:inline-flex}.pXSMma_fish{color:var(--dsw-alias-label-primary);transform-origin:50% 60%}@keyframes pXSMma_hero-fish-swim{0%,to{transform:translate(0)rotate(0)}35%{transform:translate(-1px,-1px)rotate(-5deg)}70%{transform:translate(1px)rotate(3deg)}}@media (hover:hover) and (prefers-reduced-motion:no-preference){.pXSMma_fishHitbox:hover .pXSMma_fish{animation:pXSMma_hero-fish-swim var(--ds-transition-duration-slow) var(--ds-ease-in-out)}}.pXSMma_body{flex-direction:column;gap:12px;min-width:0;display:flex;position:relative;overflow:visible}.pXSMma_body>*{z-index:1;position:relative}.pXSMma_body>.pXSMma_workspaceRow{z-index:10;align-items:center;min-width:0;padding-left:8px;display:flex}.pXSMma_workspace{max-width:min(100%,360px);min-height:28px;color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border:none;border-radius:16px;align-items:center;gap:4px;padding:0 8px;font-size:13px;font-weight:500;line-height:20px;display:inline-flex}.pXSMma_workspace:not(:disabled):hover,.pXSMma_workspace[aria-expanded=true]{background:var(--dsw-alias-interactive-bg-hover)}.pXSMma_workspace:disabled{cursor:default}.pXSMma_folder{color:var(--dsw-alias-label-primary);flex:none}.pXSMma_workspaceLabel{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.pXSMma_chevron{color:var(--dsw-alias-label-caption);flex:none}.pXSMma_modalInput{box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);width:100%;height:44px;color:var(--dsw-alias-label-primary);background:0 0;border-radius:22px;outline:none;padding:7px 14px;font-size:14px;font-weight:400;line-height:22px}.pXSMma_modalInput::placeholder{color:var(--dsw-alias-label-caption)}.pXSMma_modalInput:disabled{color:var(--dsw-alias-label-dimmed)}.pXSMma_modalAction{min-width:72px}.pXSMma_modalError{color:var(--dsw-alias-state-error-primary);margin-top:8px;font-size:12px;line-height:18px}
|
|
61
|
-
.wSkVaW_root{background:var(--dsw-alias-bg-base);--dsh-chat-content-width:748px;--dsh-composer-card-max-width:calc(var(--dsh-chat-content-width) + 32px);--dsh-composer-side-clearance:16px;--dsh-composer-dock-inset:8px;--dsh-composer-text-max-height:336px;flex-direction:column;min-width:0;height:100%;display:flex}.wSkVaW_header{border-bottom:1px solid #0000;flex:none;padding:12px 28px 0 20px;position:relative}.wSkVaW_header:after{content:"";z-index:0;background:var(--dsw-alias-border-l2);pointer-events:none;height:1px;position:absolute;bottom:1px;left:0;right:0}.wSkVaW_headerHidden{display:none}.wSkVaW_titleRow{align-items:center;gap:0;min-height:32px;display:flex}.wSkVaW_titleCluster{flex:1;align-items:center;gap:10px;min-width:0;display:flex}.wSkVaW_crumbs{white-space:nowrap;align-items:center;gap:4px;min-width:0;display:flex;overflow:hidden}.wSkVaW_crumbSeg{align-items:center;gap:4px;min-width:0;display:inline-flex}.wSkVaW_crumbSep{color:var(--dsw-alias-label-caption);font-size:14px;line-height:20px}.wSkVaW_crumb{max-width:220px;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;cursor:pointer;background:0 0;border:none;border-radius:12px;padding:4px 8px;font-size:14px;line-height:20px;overflow:hidden}.wSkVaW_crumb:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.wSkVaW_crumbCurrent{color:var(--dsw-alias-label-primary);cursor:default;font-weight:500}.wSkVaW_headerActions{flex:none;align-items:center;gap:8px;display:flex}.
|
|
61
|
+
.wSkVaW_root{background:var(--dsw-alias-bg-base);--dsh-chat-content-width:748px;--dsh-composer-card-max-width:calc(var(--dsh-chat-content-width) + 32px);--dsh-composer-side-clearance:16px;--dsh-composer-dock-inset:8px;--dsh-composer-text-max-height:336px;flex-direction:column;min-width:0;height:100%;display:flex}.wSkVaW_header{border-bottom:1px solid #0000;flex:none;padding:12px 28px 0 20px;position:relative}.wSkVaW_header:after{content:"";z-index:0;background:var(--dsw-alias-border-l2);pointer-events:none;height:1px;position:absolute;bottom:1px;left:0;right:0}.wSkVaW_headerHidden{display:none}.wSkVaW_titleRow{align-items:center;gap:0;min-height:32px;display:flex}.wSkVaW_titleCluster{flex:1;align-items:center;gap:10px;min-width:0;display:flex}.wSkVaW_crumbs{white-space:nowrap;align-items:center;gap:4px;min-width:0;display:flex;overflow:hidden}.wSkVaW_crumbSeg{align-items:center;gap:4px;min-width:0;display:inline-flex}.wSkVaW_crumbSep{color:var(--dsw-alias-label-caption);font-size:14px;line-height:20px}.wSkVaW_crumb{max-width:220px;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;cursor:pointer;background:0 0;border:none;border-radius:12px;padding:4px 8px;font-size:14px;line-height:20px;overflow:hidden}.wSkVaW_crumb:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.wSkVaW_crumbCurrent{color:var(--dsw-alias-label-primary);cursor:default;font-weight:500}.wSkVaW_headerActions{flex:none;align-items:center;gap:8px;display:flex}.wSkVaW_tabs{z-index:1;gap:36px;margin-top:4px;padding-left:8px;display:flex;position:relative}.wSkVaW_tab{color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;padding:0 0 11px;font-size:13px;font-weight:500;line-height:16px;position:relative}.wSkVaW_tab:after{content:"";background:0 0;border-radius:2px;height:2px;position:absolute;bottom:1px;left:0;right:0}.wSkVaW_tabActive{color:var(--dsw-alias-state-business-primary)}.wSkVaW_tabActive:after{background:var(--dsw-alias-state-business-primary)}.wSkVaW_viewArea{flex-direction:column;flex:1;min-height:0;display:flex}.wSkVaW_composerStack{--dsh-composer-stack-gap:6px;gap:var(--dsh-composer-stack-gap);flex-direction:column;display:flex}.wSkVaW_composerSeat{--dsh-composer-text-max-height:336px;flex-direction:column;flex:none;display:flex}.wSkVaW_root[data-phase=active]{overflow:hidden}.wSkVaW_root[data-phase=active] .wSkVaW_header{flex:none}.wSkVaW_scrollBody{flex-direction:column;flex:1;min-height:0;display:flex;overflow:hidden}.wSkVaW_root[data-phase=active] .wSkVaW_viewArea{scrollbar-gutter:stable;overscroll-behavior:contain;overflow-anchor:none;flex:1 1 auto;min-height:0;overflow-x:hidden;overflow-y:auto}.wSkVaW_root[data-phase=active] .wSkVaW_composerSeat{z-index:7;background:linear-gradient(180deg, color-mix(in srgb, var(--dsw-alias-bg-base) 0%, transparent) 0px, var(--dsw-alias-bg-base) 36px)}.wSkVaW_scrollBody:has([data-conversation-composer-overlay]){scrollbar-gutter:auto;position:relative;overflow:hidden auto}.wSkVaW_scrollBody:has([data-conversation-composer-overlay])>[data-slot=conversation\.session]>.wSkVaW_viewArea{flex:1 1 0;min-height:0;overflow:hidden}.wSkVaW_scrollBody:has([data-conversation-composer-overlay])>.wSkVaW_composerSeat{right:var(--dsh-scrollbar-width);position:absolute;bottom:0;left:0}.wSkVaW_composerHero{width:min(calc(var(--dsh-composer-card-max-width) + 2 * var(--dsh-composer-side-clearance)), 100%);z-index:1;align-self:center;gap:8px;padding-bottom:32px;position:relative}.wSkVaW_heroGlow{z-index:-1;aspect-ratio:1051/468;pointer-events:none;width:135.438%;position:absolute;bottom:92px;left:50%;transform:translate(-50%,50%)}.wSkVaW_heroWorkspaceRow{align-items:center;gap:2px;min-width:0;margin-top:4px;padding-left:20px;display:flex}.wSkVaW_root[data-phase=hero] .wSkVaW_scrollBody{justify-content:center;overflow-y:auto}.wSkVaW_root[data-phase=settling] .wSkVaW_composerSeat{visibility:hidden}
|
|
62
62
|
.ydkMvW_root{border-left:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);flex-direction:column;min-width:0;height:100%;display:flex}.ydkMvW_header{border-bottom:1px solid var(--dsw-alias-border-l2);justify-content:space-between;align-items:center;gap:8px;padding:14px 12px 12px;display:flex}.ydkMvW_title{color:var(--dsw-alias-label-primary);text-overflow:ellipsis;white-space:nowrap;font-size:14px;font-weight:500;line-height:20px;overflow:hidden}.ydkMvW_close{width:28px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:999px;flex:none;place-items:center;display:grid}.ydkMvW_close:hover{background:var(--dsw-alias-interactive-bg-hover)}.ydkMvW_body{flex:1;min-height:0;padding:12px 16px;overflow-y:auto}.ydkMvW_empty{color:var(--dsw-alias-label-tertiary);padding:8px 0;font-size:13px;line-height:20px}.ydkMvW_section{margin-bottom:16px}.ydkMvW_sectionLabel{color:var(--dsw-alias-label-secondary);margin-bottom:6px;font-size:12px;font-weight:500;line-height:18px}.ydkMvW_code{background:var(--dsw-alias-markdown-code-block);font-family:var(--ds-font-family-code);color:var(--dsw-alias-label-primary);white-space:pre-wrap;word-break:break-word;border-radius:12px;margin:0;padding:16px;font-size:13px;line-height:22px}.ydkMvW_code[data-error]{color:var(--dsw-alias-state-error-primary)}
|
|
63
63
|
.hXDBwq_visuallyHidden{clip:rect(0 0 0 0);white-space:nowrap;width:1px;height:1px;position:absolute;overflow:hidden}
|
|
64
64
|
.QWLzlG_root{flex-direction:column;display:flex}.QWLzlG_row{position:relative;overflow:hidden}.QWLzlG_root[data-state=running] .QWLzlG_row:after{content:"";inset-block:0;background:linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--dsw-alias-bg-base) 60%, transparent) 55%, transparent 100%);pointer-events:none;width:300px;animation:2.6s ease-out infinite QWLzlG_dsh-reasoning-row-sweep;position:absolute;left:0}@keyframes QWLzlG_dsh-reasoning-row-sweep{0%{left:-300px}90%,to{left:100%}}.QWLzlG_leading{flex-shrink:0}.QWLzlG_chevron{color:var(--dsw-alias-label-secondary)}.QWLzlG_title{font-weight:400}.QWLzlG_separator{background:var(--dsw-alias-label-caption);border-radius:1px;flex:none;width:2px;height:2px;margin:0 8px}.QWLzlG_summary{min-width:0;color:var(--dsw-alias-label-tertiary);text-overflow:ellipsis;white-space:nowrap;flex:auto;font-size:14px;line-height:24px;overflow:hidden}.QWLzlG_summary[data-follow-end]{text-overflow:clip}.QWLzlG_thinkBody{color:var(--dsw-alias-label-tertiary);white-space:pre-wrap;word-break:break-word;padding:4px 0 4px 22px;font-size:14px;line-height:24px}@media (prefers-reduced-motion:reduce){.QWLzlG_root[data-state=running] .QWLzlG_row:after{animation:none}}
|
|
@@ -198,6 +198,9 @@ body[data-workspace-dragging] .YDXeBa_projectRow[draggable=true]{cursor:grabbing
|
|
|
198
198
|
.ztWv_q_callRow .Sxvs8a_root{margin:4px 0 4px 22px;max-height:min(56vh,520px);overflow:auto}
|
|
199
199
|
.pi-messageGroup{display:contents}
|
|
200
200
|
.pi-assistantMessageGroup{display:flex;flex-direction:column}
|
|
201
|
+
/* Adjacent execution groups offset 12px of the conversation column's 16px gap. */
|
|
202
|
+
.pi-messageGroup:has(> .pi-assistantMessageGroup > :is([data-chat-flow-kind=tool-call],[data-chat-flow-kind=assistant-step]):last-child,> [data-chat-flow-kind=tool-result],> [data-chat-flow-kind=bash])
|
|
203
|
+
+ .pi-messageGroup:has(> .pi-assistantMessageGroup > :is([data-chat-flow-kind=tool-call],[data-chat-flow-kind=assistant-step]):first-child,> [data-chat-flow-kind=tool-result],> [data-chat-flow-kind=bash]) > :first-child{margin-top:-12px}
|
|
201
204
|
.pi-assistantMessageActions{display:flex;align-items:center;justify-content:flex-start;min-height:28px;margin-top:0;padding:0 14px 12px}
|
|
202
205
|
.pi-assistantCopyButton{width:28px;height:28px;border-radius:6px;flex:none}
|
|
203
206
|
.pi-composerActions{display:inline-flex;align-items:center;gap:6px;flex:none}
|
|
@@ -264,6 +267,19 @@ body[data-workspace-dragging] .YDXeBa_projectRow[draggable=true]{cursor:grabbing
|
|
|
264
267
|
[data-chat-flow-kind=assistant-text] .md-table-wrap :is(th,td):last-child{border-right:0}
|
|
265
268
|
[data-chat-flow-kind=assistant-text] .md-table-wrap [data-align=center]{text-align:center}
|
|
266
269
|
[data-chat-flow-kind=assistant-text] .md-table-wrap [data-align=right]{text-align:right}
|
|
270
|
+
@media(max-width:640px),(pointer:coarse) and (max-width:900px){
|
|
271
|
+
[data-chat-flow-kind=user] .gdEzaW_bubble,[data-chat-flow-kind=assistant-text] ._markdown_1nba0_5{font-size:13px;line-height:21px}
|
|
272
|
+
[data-chat-flow-kind=assistant-text] ._markdown_1nba0_5 h1{font-size:22px;line-height:30px;margin:24px 0 10px}
|
|
273
|
+
[data-chat-flow-kind=assistant-text] ._markdown_1nba0_5 h2{font-size:19px;line-height:27px;margin:22px 0 9px}
|
|
274
|
+
[data-chat-flow-kind=assistant-text] ._markdown_1nba0_5 h3{font-size:16px;line-height:24px;margin:18px 0 8px}
|
|
275
|
+
[data-chat-flow-kind=assistant-text] ._markdown_1nba0_5 h4{font-size:14px;line-height:22px;margin:16px 0 6px}
|
|
276
|
+
[data-chat-flow-kind=assistant-text] ._markdown_1nba0_5 :is(h5,h6){font-size:13px;line-height:21px;margin:14px 0 6px}
|
|
277
|
+
[data-chat-flow-kind=assistant-text] ._markdown_1nba0_5 :is(p,ul,ol){margin:10px 0}
|
|
278
|
+
[data-chat-flow-kind=assistant-text] ._markdown_1nba0_5 li::marker{line-height:21px}
|
|
279
|
+
[data-chat-flow-kind=assistant-text] .md-code-block{font-size:12px;line-height:19px;margin:12px 0;padding:12px}
|
|
280
|
+
[data-chat-flow-kind=assistant-text] .md-table-wrap table{font-size:12px;line-height:19px}
|
|
281
|
+
[data-chat-flow] .Sxvs8a_root,[data-chat-flow] .CY-8Ka_title,[data-chat-flow] .CY-8Ka_summary,[data-chat-flow] .QWLzlG_thinkBody,[data-chat-flow] .QWLzlG_summary{font-size:12px;line-height:20px}
|
|
282
|
+
}
|
|
267
283
|
[data-chat-flow-kind=assistant-step] .QWLzlG_leading,[data-chat-flow-kind=assistant-step] .QWLzlG_title{color:var(--dsw-alias-state-warn-label)}
|
|
268
284
|
.CY-8Ka_root[data-state=ok] .CY-8Ka_leading{color:var(--dsw-alias-state-success-primary)}
|
|
269
285
|
.CY-8Ka_root[data-state=running] .CY-8Ka_leading,.CY-8Ka_root[data-state=running] .CY-8Ka_title{color:var(--dsw-alias-state-business-primary)}
|
|
@@ -298,6 +314,7 @@ body[data-workspace-dragging] .YDXeBa_projectRow[draggable=true]{cursor:grabbing
|
|
|
298
314
|
.pi-queuedMessageWithdraw:disabled{cursor:wait;opacity:.45}
|
|
299
315
|
.pi-historyView{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden;background:var(--dsw-alias-bg-base)}
|
|
300
316
|
.pi-historyHeader{box-sizing:border-box;width:min(100%,860px);flex:none;display:flex;align-items:flex-start;justify-content:space-between;flex-wrap:wrap;gap:12px;margin:0 auto;padding:24px 22px 14px}
|
|
317
|
+
.pi-historyActions{display:flex;align-items:center;flex-wrap:wrap;gap:8px}
|
|
301
318
|
.pi-historyReturn{display:inline-flex;align-items:center;gap:6px;max-width:100%;padding:6px 0;color:var(--dsw-alias-state-business-primary);background:transparent;border:0;cursor:pointer;font:500 12px/20px var(--dsw-font-family)}
|
|
302
319
|
.pi-historyReturn svg{flex:none}
|
|
303
320
|
.pi-historyReturn:hover:not(:disabled){text-decoration:underline}
|
|
@@ -352,7 +369,7 @@ body[data-workspace-dragging] .YDXeBa_projectRow[draggable=true]{cursor:grabbing
|
|
|
352
369
|
.pi-historyDecisionCancel:hover:not(:disabled){color:var(--dsw-alias-label-primary)}
|
|
353
370
|
.pi-historyBusy{flex:none;padding:9px 16px;color:var(--dsw-alias-state-business-primary);background:var(--dsw-alias-state-business-tertiary);border-top:1px solid var(--dsw-alias-border-l2);font-size:12px;line-height:18px;text-align:center}
|
|
354
371
|
@media(max-width:980px){.pi-historyDecision{align-items:stretch;flex-direction:column}.pi-historyDecisionActions{width:100%}.pi-historyDecisionButton{min-width:0;flex:1}}
|
|
355
|
-
@media(max-width:760px){.pi-dialogBackdrop{padding:12px}.pi-historyHeader{padding:16px}.pi-historyTabs{padding:0 16px}.pi-historyKind{display:none}.pi-historyDecisionActions{flex-direction:column}.pi-historyDecisionCancel{align-self:flex-end}.
|
|
372
|
+
@media(max-width:760px){.pi-dialogBackdrop{padding:12px}.pi-historyHeader{padding:16px}.pi-historyTabs{padding:0 16px}.pi-historyKind{display:none}.pi-historyDecisionActions{flex-direction:column}.pi-historyDecisionCancel{align-self:flex-end}.nL4_yW_sessionLogButton{min-width:auto}}
|
|
356
373
|
/* Composer input bar — restored from DeepSeek Harness InputBar.module.css (missing from the copied DSH stylesheet) */
|
|
357
374
|
.uV2eYG_root{display:flex;flex-direction:column;align-items:center;padding:0 var(--dsh-composer-side-clearance) 8px}
|
|
358
375
|
.uV2eYG_hero{padding:0 var(--dsh-composer-side-clearance)}
|
|
@@ -537,3 +554,63 @@ body[data-ds-dark-theme] .pi-filePreview .hljs-meta,body[data-ds-dark-theme] .pi
|
|
|
537
554
|
@keyframes pi-turn-target-flash{0%{box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--dsw-alias-state-business-primary) 9%,transparent)}35%{box-shadow:inset 0 0 0 2px color-mix(in srgb,var(--dsw-alias-state-business-primary) 54%,transparent),0 0 0 4px color-mix(in srgb,var(--dsw-alias-state-business-primary) 10%,transparent)}100%{box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--dsw-alias-state-business-primary) 9%,transparent)}}
|
|
538
555
|
@media(max-width:640px){.pi-turnNav{right:2px;width:34px}.pi-scrollBottom{right:5px}.pi-turnNavMarker{width:34px}.pi-turnNavMarker:hover .pi-turnNavLine,.pi-turnNavMarker:focus-visible .pi-turnNavLine{width:24px}.pi-turnTooltip{right:32px;width:min(292px,calc(100vw - 48px))}}
|
|
539
556
|
@media(prefers-reduced-motion:reduce){.pi-turnNavLine,.pi-turnTooltip,.pi-scrollBottom{transition:none}.pi-turnTargetFlash [data-chat-flow-kind=user] .gdEzaW_bubble{animation:none}}
|
|
557
|
+
.pi-networkForm,.pi-networkRelay,.pi-networkAdvanced{display:grid;gap:12px;min-width:0}
|
|
558
|
+
.pi-networkForm [hidden]{display:none}
|
|
559
|
+
.pi-networkForm label{display:grid;gap:5px;min-width:0;font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary)}
|
|
560
|
+
.pi-networkForm p{margin:0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:20px;overflow-wrap:anywhere}
|
|
561
|
+
.pi-networkForm a{color:var(--dsw-alias-state-business-primary);text-decoration:underline}
|
|
562
|
+
.pi-networkForm select{cursor:pointer}
|
|
563
|
+
.pi-networkForm summary{cursor:pointer;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:24px}
|
|
564
|
+
.pi-networkAdvanced{padding-top:10px}
|
|
565
|
+
.pi-networkForm .pi-settingsStatus{margin-top:0}
|
|
566
|
+
.pi-networkDownloads{display:grid;gap:8px}
|
|
567
|
+
.pi-networkDownloadButtons{display:flex;flex-wrap:wrap;gap:8px}
|
|
568
|
+
.pi-networkDownloadButtons .pi-settingsAction{width:auto;max-width:100%;height:auto;min-height:34px;overflow-wrap:anywhere}
|
|
569
|
+
@media(max-width:560px){.pi-networkDownloadButtons{display:grid;grid-template-columns:minmax(0,1fr)}.pi-networkDownloadButtons .pi-settingsAction{width:100%;min-height:44px}}
|
|
570
|
+
.pi-passwordForm{margin-top:12px}
|
|
571
|
+
.pi-passwordForm label,.pi-loginCard label{font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary)}
|
|
572
|
+
.pi-passwordInput{box-sizing:border-box;min-width:0;width:100%;height:44px;padding:8px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:6px;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);font:16px/24px var(--dsw-font-family)}
|
|
573
|
+
.pi-passwordInput:focus{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}
|
|
574
|
+
.pi-passwordStatus{margin:0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:20px}
|
|
575
|
+
.pi-loginBackdrop{position:fixed;z-index:1200;top:var(--pi-viewport-top,0px);left:0;right:0;box-sizing:border-box;height:var(--pi-viewport-height,100dvh);display:grid;place-items:center;padding:20px;background:var(--dsw-alias-bg-base)}
|
|
576
|
+
.pi-loginCard{box-sizing:border-box;display:flex;flex-direction:column;gap:14px;width:min(360px,100%);max-height:100%;overflow:auto;padding:24px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary)}
|
|
577
|
+
.pi-loginCard h1{margin:0;font-size:22px;line-height:30px}
|
|
578
|
+
.pi-loginMessage{margin:0;color:var(--dsw-alias-label-secondary);font-size:14px;line-height:22px}
|
|
579
|
+
.pi-loginCard .pi-settingsAction{width:100%;min-height:44px}
|
|
580
|
+
.pi-lanUrl{box-sizing:border-box;width:100%;min-width:0;padding:8px;border:1px solid var(--dsw-alias-border-l2);border-radius:6px;background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);font:12px/20px var(--ds-font-family-code)}
|
|
581
|
+
.pi-copyField{box-sizing:border-box;width:100%;min-height:160px;max-height:40vh;margin:12px 0;padding:10px;resize:vertical;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-base);color:var(--dsw-alias-label-primary);font:16px/24px var(--dsw-font-family)}
|
|
582
|
+
@media(pointer:coarse){
|
|
583
|
+
#root{box-sizing:border-box;position:fixed;top:var(--pi-viewport-top,0px);left:0;right:0;height:var(--pi-viewport-height,100dvh);padding:env(safe-area-inset-top) env(safe-area-inset-right) 0 env(safe-area-inset-left)}
|
|
584
|
+
.wSkVaW_root,.wSkVaW_composerSeat{--dsh-composer-text-max-height:min(200px,calc(var(--pi-viewport-height,100dvh) * .3))}
|
|
585
|
+
.wSkVaW_composerSeat{padding-bottom:env(safe-area-inset-bottom)}
|
|
586
|
+
.YDXeBa_projectRow,.YDXeBa_sessionRow{height:auto;min-height:48px}
|
|
587
|
+
.YDXeBa_rowActions,.YDXeBa_projectRow .YDXeBa_rowActions{display:inline-flex;height:auto;gap:0}
|
|
588
|
+
.YDXeBa_iconButton,.hHd-Xa_iconButton,.pi-mobileSidebarButton,.pi-settingsClose,.pi-queuedMessageWithdraw{width:44px;height:44px}
|
|
589
|
+
.YDXeBa_sessionRow .YDXeBa_time{display:none}
|
|
590
|
+
.YDXeBa_dragHandle{display:none}
|
|
591
|
+
.qDHVXG_sessionOverflowButton,.hHd-Xa_newSession,._7KE1Ra_trigger,.pi-settingsAction,.pi-settingsTheme button{min-height:44px}
|
|
592
|
+
.uV2eYG_add,.uV2eYG_primary,.pi-abortButton{width:44px;height:44px;transform:none}
|
|
593
|
+
.pi-dialogBackdrop,.pi-settingsBackdrop{top:var(--pi-viewport-top,0px);bottom:auto;height:var(--pi-viewport-height,100dvh);box-sizing:border-box}
|
|
594
|
+
.pi-dialogCard,.pi-settingsPanel{max-height:calc(var(--pi-viewport-height,100dvh) - 24px)}
|
|
595
|
+
input,select,textarea{font-size:16px}
|
|
596
|
+
.pi-mobileSidebarButton{top:7px;left:6px}
|
|
597
|
+
}
|
|
598
|
+
@media(max-width:640px){
|
|
599
|
+
.pI_x6G_sidebarCol{box-shadow:none}
|
|
600
|
+
.pI_x6G_frame[data-mobile-sidebar-open] .pI_x6G_sidebarCol{box-shadow:12px 0 32px rgb(31 35 41 / .14)}
|
|
601
|
+
.wSkVaW_root{--dsh-composer-side-clearance:8px}
|
|
602
|
+
.wSkVaW_header{padding-right:8px}
|
|
603
|
+
.wSkVaW_crumb{max-width:140px}
|
|
604
|
+
|
|
605
|
+
.uV2eYG_row{gap:4px;padding:2px 6px 6px}
|
|
606
|
+
.uV2eYG_tools,.uV2eYG_trailing{gap:4px}
|
|
607
|
+
.uV2eYG_trailing{flex:1;justify-content:flex-end}
|
|
608
|
+
.pi-modelControl{min-width:0;flex:1;max-width:180px}
|
|
609
|
+
.pi-modelControl ._7KE1Ra_trigger{max-width:100%;padding:0 4px}
|
|
610
|
+
.pi-thinkingControl ._7KE1Ra_trigger{padding:0 4px}
|
|
611
|
+
.pi-thinkingIcon,._7KE1Ra_chevron,.JObwrW_root{display:none}
|
|
612
|
+
.FJxK0a_root{flex-wrap:nowrap;overflow-x:auto;justify-content:flex-start;max-height:28px;white-space:nowrap}
|
|
613
|
+
.FJxK0a_root>span{flex:none}
|
|
614
|
+
.wSkVaW_composerHero{padding-bottom:12px}
|
|
615
|
+
.pi-settingsPanel{padding-bottom:env(safe-area-inset-bottom)}
|
|
616
|
+
}
|