Commit 6008eeb5 authored by AI-甘富林's avatar AI-甘富林

调整客户端设置页布局并补充运行时路径

parent 97645407
...@@ -354,6 +354,7 @@ export class ProjectWorkspaceExecutorService { ...@@ -354,6 +354,7 @@ export class ProjectWorkspaceExecutorService {
PYTHONUTF8: "1", PYTHONUTF8: "1",
PYTHONIOENCODING: "utf-8", PYTHONIOENCODING: "utf-8",
PATH: [ PATH: [
paths.nodeExecutable ? path.dirname(paths.nodeExecutable) : null,
paths.ffmpegExecutable ? path.dirname(paths.ffmpegExecutable) : null, paths.ffmpegExecutable ? path.dirname(paths.ffmpegExecutable) : null,
paths.ffprobeExecutable ? path.dirname(paths.ffprobeExecutable) : null, paths.ffprobeExecutable ? path.dirname(paths.ffprobeExecutable) : null,
path.join(paths.runtimeDir, "python", "Scripts"), path.join(paths.runtimeDir, "python", "Scripts"),
......
...@@ -5065,55 +5065,47 @@ export default function App() { ...@@ -5065,55 +5065,47 @@ export default function App() {
<div className="page-stack settings-page-stack settings-page-shell"> <div className="page-stack settings-page-stack settings-page-shell">
{showSettingsStatusHint ? <div className={"inline-hint settings-runtime-hint" + (chatLaunchState === "error" ? " error" : "")}>{startupMessage}</div> : null} {showSettingsStatusHint ? <div className={"inline-hint settings-runtime-hint" + (chatLaunchState === "error" ? " error" : "")}>{startupMessage}</div> : null}
<div className="settings-console-grid"> <div className="settings-console-grid">
<section className="panel settings-panel settings-panel-modern settings-panel-connection"> <section className="panel settings-panel settings-panel-modern settings-panel-basic-config">
<div className="settings-section-card settings-section-card-compact"> <div className="settings-section-card settings-section-card-compact settings-basic-config-card">
<div className="settings-section-headline"> <div className="settings-section-headline settings-section-headline-basic">
<div> <span className="settings-section-kicker">基础配置</span>
<span className="settings-section-kicker">基础配置</span>
<h4>客户端绑定</h4>
</div>
<StatusChip tone={workspace?.apiKeyConfigured ? "positive" : "warning"}>{workspace?.apiKeyConfigured ? "已绑定" : "未绑定"}</StatusChip>
</div>
<div className="settings-inline-key-row">
<label className="settings-input-label">
<span className="settings-input-label-text">龙虾密钥</span>
<input
type="password"
value={lobsterKeyDraft}
placeholder={workspace?.apiKeyConfigured ? "输入新的龙虾密钥或更新绑定" : "请输入龙虾密钥"}
onChange={(event) => setLobsterKeyDraft(event.target.value)}
/>
</label>
<button className="settings-primary-button settings-inline-save-button" disabled={saving || !hasPendingLobsterKey} onClick={() => void saveConfig({ lobsterKey: lobsterKeyDraft })}>{saving ? ui.saving : "保存"}</button>
</div> </div>
</div> <div className="settings-basic-config-form">
</section> <div className="settings-basic-config-row">
<section className="panel settings-panel settings-panel-secondary settings-panel-diagnostics"> <label className="settings-input-label">
<div className="settings-section-card settings-section-card-compact"> <span className="settings-input-label-text">龙虾密钥</span>
<div className="settings-section-headline"> <input
<div> type="password"
<span className="settings-section-kicker">诊断与工作区</span> value={lobsterKeyDraft}
<h4>工作目录</h4> placeholder={workspace?.apiKeyConfigured ? "输入新的龙虾密钥或更新绑定" : "请输入龙虾密钥"}
</div> onChange={(event) => setLobsterKeyDraft(event.target.value)}
<StatusChip tone={hasPendingWorkspacePathChange ? "warning" : "info"}>{hasPendingWorkspacePathChange ? "待保存" : "已同步"}</StatusChip> />
</div> </label>
<div className="workspace-directory-card"> <button className="settings-primary-button settings-inline-save-button" disabled={saving || !hasPendingLobsterKey} onClick={() => void saveConfig({ lobsterKey: lobsterKeyDraft })}>{saving ? ui.saving : "保存"}</button>
<div className="workspace-directory-panel">
<strong className="workspace-directory-path">{displayedWorkspacePath}</strong>
</div> </div>
{hasPendingWorkspacePathChange ? ( <div className="settings-basic-config-row settings-basic-config-row-directory">
<div className="workspace-directory-draft-row"> <div className="settings-input-label settings-directory-label">
<span className="workspace-directory-draft-badge">待保存</span> <span className="settings-input-label-text">工作目录</span>
<div className="workspace-directory-inline-actions"> <div className="workspace-directory-card settings-basic-directory-card">
<button disabled={saving} onClick={() => void saveWorkspaceDirectory()}>{saving ? ui.saving : ui.save}</button> <div className="workspace-directory-panel settings-basic-directory-panel">
<button className="secondary workspace-directory-inline-button" disabled={saving} onClick={restoreWorkspaceDirectory}>恢复当前</button> <strong className="workspace-directory-path">{displayedWorkspacePath}</strong>
</div>
{hasPendingWorkspacePathChange ? (
<div className="workspace-directory-draft-row">
<span className="workspace-directory-draft-badge">待保存</span>
<div className="workspace-directory-inline-actions">
<button disabled={saving} onClick={() => void saveWorkspaceDirectory()}>{saving ? ui.saving : ui.save}</button>
<button className="secondary workspace-directory-inline-button" disabled={saving} onClick={restoreWorkspaceDirectory}>恢复当前</button>
</div>
</div>
) : null}
</div> </div>
</div> </div>
) : null} <div className="button-row settings-actions workspace-directory-actions settings-basic-directory-actions">
</div> <button className="settings-primary-button" disabled={saving || !config} onClick={() => void pickWorkspaceDirectory()}>更改目录</button>
<div className="button-row settings-actions workspace-directory-actions"> <button className="secondary" disabled={saving} onClick={() => void exportDiagnostics()}>{ui.export}</button>
<button className="settings-primary-button" disabled={saving || !config} onClick={() => void pickWorkspaceDirectory()}>更改目录</button> </div>
<button className="secondary" disabled={saving} onClick={() => void exportDiagnostics()}>{ui.export}</button> </div>
</div> </div>
</div> </div>
</section> </section>
......
...@@ -4113,14 +4113,18 @@ button.secondary { ...@@ -4113,14 +4113,18 @@ button.secondary {
min-height: 0; min-height: 0;
display: grid; display: grid;
gap: 10px; gap: 10px;
grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-columns: minmax(0, 1.15fr) minmax(380px, 0.85fr);
grid-template-rows: minmax(236px, 0.58fr) minmax(0, 1.42fr); grid-template-rows: minmax(236px, 0.58fr) minmax(0, 1.42fr);
grid-template-areas: grid-template-areas:
"connection diagnostics xhs-feishu" "basic-config xhs-feishu"
"models models models"; "models models";
overflow: hidden; overflow: hidden;
} }
.settings-panel-basic-config {
grid-area: basic-config;
}
.settings-panel-connection { .settings-panel-connection {
grid-area: connection; grid-area: connection;
} }
...@@ -4282,13 +4286,69 @@ button.secondary { ...@@ -4282,13 +4286,69 @@ button.secondary {
display: grid; display: grid;
} }
.settings-basic-config-card {
grid-template-rows: auto minmax(0, 1fr);
gap: 8px;
}
.settings-section-headline-basic {
align-items: flex-start;
justify-content: flex-start;
}
.settings-basic-config-form {
min-height: 0;
display: grid;
align-content: center;
gap: 10px;
}
.settings-basic-config-row {
min-width: 0;
display: grid;
grid-template-columns: minmax(0, 1fr) max-content;
align-items: end;
gap: 10px;
}
.settings-basic-config-row-directory {
align-items: start;
}
.settings-basic-directory-card {
gap: 6px;
}
.settings-basic-directory-panel {
min-height: 38px;
align-items: center;
}
.settings-basic-directory-actions {
display: grid;
grid-auto-flow: column;
grid-auto-columns: 78px;
align-self: end;
}
.settings-basic-directory-actions button,
.settings-inline-save-button { .settings-inline-save-button {
min-width: 88px; height: 38px;
padding-inline: 16px; min-height: 38px;
justify-self: start; padding-block: 0;
padding-inline: 8px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 12px;
white-space: nowrap; white-space: nowrap;
} }
.settings-inline-save-button {
min-width: 64px;
justify-self: start;
}
.settings-input-label { .settings-input-label {
min-width: 0; min-width: 0;
gap: 5px; gap: 5px;
...@@ -4525,6 +4585,20 @@ button.secondary { ...@@ -4525,6 +4585,20 @@ button.secondary {
grid-template-rows: auto minmax(0, 1fr) auto; grid-template-rows: auto minmax(0, 1fr) auto;
} }
.settings-panel-basic-config .settings-section-card {
grid-template-rows: auto minmax(0, 1fr);
}
.settings-panel-basic-config .workspace-directory-card {
gap: 6px;
}
.settings-panel-basic-config .workspace-directory-panel {
min-height: 38px;
height: auto;
align-items: center;
}
.workspace-directory-eyebrow { .workspace-directory-eyebrow {
font-size: 10px; font-size: 10px;
letter-spacing: 0.16em; letter-spacing: 0.16em;
...@@ -4552,11 +4626,10 @@ button.secondary { ...@@ -4552,11 +4626,10 @@ button.secondary {
@media (max-width: 1180px) { @media (max-width: 1180px) {
.settings-console-grid { .settings-console-grid {
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: minmax(0, 1.1fr) minmax(340px, 0.9fr);
grid-template-rows: minmax(164px, auto) minmax(210px, auto) minmax(0, 1fr); grid-template-rows: minmax(164px, auto) minmax(0, 1fr);
grid-template-areas: grid-template-areas:
"connection diagnostics" "basic-config xhs-feishu"
"xhs-feishu xhs-feishu"
"models models"; "models models";
} }
...@@ -4594,8 +4667,7 @@ button.secondary { ...@@ -4594,8 +4667,7 @@ button.secondary {
grid-template-columns: 1fr; grid-template-columns: 1fr;
grid-template-rows: auto; grid-template-rows: auto;
grid-template-areas: grid-template-areas:
"connection" "basic-config"
"diagnostics"
"xhs-feishu" "xhs-feishu"
"models"; "models";
overflow: visible; overflow: visible;
...@@ -4612,6 +4684,21 @@ button.secondary { ...@@ -4612,6 +4684,21 @@ button.secondary {
grid-template-columns: minmax(0, 1fr); grid-template-columns: minmax(0, 1fr);
} }
.settings-basic-config-row {
grid-template-columns: minmax(0, 1fr);
}
.settings-inline-save-button,
.settings-basic-directory-actions {
justify-self: stretch;
}
.settings-basic-directory-actions {
grid-auto-flow: row;
grid-auto-columns: unset;
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.settings-field-grid-digital-human { .settings-field-grid-digital-human {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
...@@ -4653,6 +4740,10 @@ button.secondary { ...@@ -4653,6 +4740,10 @@ button.secondary {
.workspace-directory-actions button { .workspace-directory-actions button {
width: 100%; width: 100%;
} }
.settings-basic-directory-actions {
grid-template-columns: 1fr;
}
} }
.composer-attachment-input { .composer-attachment-input {
......
...@@ -43,8 +43,10 @@ async function main(): Promise<void> { ...@@ -43,8 +43,10 @@ async function main(): Promise<void> {
assert(existsSync(powerShellPath), "Windows PowerShell was not found for workspace automation smoke."); assert(existsSync(powerShellPath), "Windows PowerShell was not found for workspace automation smoke.");
const stableRuntimeDir = path.join(tempRoot, "stable-runtime"); const stableRuntimeDir = path.join(tempRoot, "stable-runtime");
const stableNodePath = path.join(stableRuntimeDir, "node", "node.exe");
const stableFfmpegPath = path.join(stableRuntimeDir, "ffmpeg", "bin", "ffmpeg.exe"); const stableFfmpegPath = path.join(stableRuntimeDir, "ffmpeg", "bin", "ffmpeg.exe");
const stableFfprobePath = path.join(stableRuntimeDir, "ffmpeg", "bin", "ffprobe.exe"); const stableFfprobePath = path.join(stableRuntimeDir, "ffmpeg", "bin", "ffprobe.exe");
await writeUtf8(stableNodePath, "stable-node");
await writeUtf8(stableFfmpegPath, "stable-ffmpeg"); await writeUtf8(stableFfmpegPath, "stable-ffmpeg");
await writeUtf8(stableFfprobePath, "stable-ffprobe"); await writeUtf8(stableFfprobePath, "stable-ffprobe");
...@@ -89,6 +91,7 @@ async function main(): Promise<void> { ...@@ -89,6 +91,7 @@ async function main(): Promise<void> {
"$payload = @{", "$payload = @{",
" FFMPEG_BIN = [string]$env:FFMPEG_BIN", " FFMPEG_BIN = [string]$env:FFMPEG_BIN",
" FFPROBE_BIN = [string]$env:FFPROBE_BIN", " FFPROBE_BIN = [string]$env:FFPROBE_BIN",
" PATH = [string]$env:PATH",
"} | ConvertTo-Json -Compress", "} | ConvertTo-Json -Compress",
"Write-Output 'QJC_WORKSPACE_EVENT\t{\"type\":\"started\",\"runId\":\"ffmpeg-runtime-smoke-run\"}'", "Write-Output 'QJC_WORKSPACE_EVENT\t{\"type\":\"started\",\"runId\":\"ffmpeg-runtime-smoke-run\"}'",
"Write-Output ('QJC_WORKSPACE_EVENT\t{\"type\":\"completed\",\"content\":' + (ConvertTo-Json $payload -Compress) + '}')", "Write-Output ('QJC_WORKSPACE_EVENT\t{\"type\":\"completed\",\"content\":' + (ConvertTo-Json $payload -Compress) + '}')",
...@@ -108,7 +111,7 @@ async function main(): Promise<void> { ...@@ -108,7 +111,7 @@ async function main(): Promise<void> {
resolveBundledPaths() { resolveBundledPaths() {
return { return {
runtimeDir: stableRuntimeDir, runtimeDir: stableRuntimeDir,
nodeExecutable: path.join(stableRuntimeDir, "node", "node.exe"), nodeExecutable: stableNodePath,
openClawEntry: path.join(stableRuntimeDir, "openclaw", "index.js"), openClawEntry: path.join(stableRuntimeDir, "openclaw", "index.js"),
packagedOpenClawEntry: path.join(stableRuntimeDir, "openclaw", "package", "openclaw.mjs"), packagedOpenClawEntry: path.join(stableRuntimeDir, "openclaw", "package", "openclaw.mjs"),
runtimeManifestPath: path.join(stableRuntimeDir, "runtime-manifest.json"), runtimeManifestPath: path.join(stableRuntimeDir, "runtime-manifest.json"),
...@@ -136,13 +139,19 @@ async function main(): Promise<void> { ...@@ -136,13 +139,19 @@ async function main(): Promise<void> {
projectRoot, projectRoot,
prompt: "verify ffmpeg injection" prompt: "verify ffmpeg injection"
}); });
assert("reply" in execution, "Workspace automation did not complete with a reply.");
const injectedPayload = JSON.parse(execution.reply.content || "{}") as { const injectedPayload = JSON.parse(execution.reply.content || "{}") as {
FFMPEG_BIN?: string; FFMPEG_BIN?: string;
FFPROBE_BIN?: string; FFPROBE_BIN?: string;
PATH?: string;
}; };
assert(injectedPayload.FFMPEG_BIN === stableFfmpegPath, "Workspace automation did not receive the bundled FFMPEG_BIN path."); assert(injectedPayload.FFMPEG_BIN === stableFfmpegPath, "Workspace automation did not receive the bundled FFMPEG_BIN path.");
assert(injectedPayload.FFPROBE_BIN === stableFfprobePath, "Workspace automation did not receive the bundled FFPROBE_BIN path."); assert(injectedPayload.FFPROBE_BIN === stableFfprobePath, "Workspace automation did not receive the bundled FFPROBE_BIN path.");
assert(
(injectedPayload.PATH ?? "").split(path.delimiter).includes(path.dirname(stableNodePath)),
"Workspace automation PATH did not include the bundled runtime/node directory."
);
const summary = { const summary = {
ok: true, ok: true,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment