Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions apps/presentation/dashboard/src/data/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,23 @@ const configuredChatOrigin = String(import.meta.env?.VITE_LOOPX_CHAT_ORIGIN ?? "
.trim()
.replace(/\/+$/, "");

export type ConfigurationBackupResult = {
ok: boolean; status?: string; goal_count: number; machine_configuration_present: boolean;
sha256: string; checkpoint_ref?: string;
};

export function exportConfigurationBackup(goalIds?: string[]) {
return requestJson<ConfigurationBackupResult & {backup: Record<string, unknown>}>("/api/chat/configuration-backup/export", {
method: "POST", body: JSON.stringify(goalIds === undefined ? {} : {goal_ids: goalIds}),
});
}

export function restoreConfigurationCheckpoint(backup: Record<string, unknown>, execute: boolean) {
return requestJson<ConfigurationBackupResult>("/api/chat/configuration-backup/restore", {
method: "POST", body: JSON.stringify({backup, expected_sha256: backup.sha256, execute}),
});
}

function chatApiUrl(path: string) {
if (!configuredChatOrigin || /^https?:\/\//.test(path)) {
return path;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {useEffect, useState} from "react";
import {exportConfigurationBackup, restoreConfigurationCheckpoint, type ConfigurationBackupResult} from "../../data/chat";
import {useWorkspaceI18n} from "./i18n";

export function ConfigurationBackupSettings({goalId}: {goalId: string | null}) {
const {locale} = useWorkspaceI18n();
const zh = locale === "zh-CN";
const [backup, setBackup] = useState<Record<string, unknown> | null>(null);
const [result, setResult] = useState<ConfigurationBackupResult | null>(null);
const [error, setError] = useState("");
const [busy, setBusy] = useState(false);
useEffect(() => {setBackup(null); setResult(null); setError("");}, [goalId]);
async function perform(action: () => Promise<void>) {
setBusy(true); setError("");
try {await action();} catch (failure) {setError(String(failure));}
finally {setBusy(false);}
}
return <details className="personal-detail-card">
<summary>{zh ? "配置备份与恢复" : "Configuration backup and recovery"}</summary>
<p>{zh ? "备份包含私有路径、Goal 设置及设备默认值,需自行保管和审查。凭据文件不包含在此组件中。恢复只建立隔离检查点;启用仍在原配置页面办理。" : "Keep and review this private backup: it includes Goal settings, paths and device defaults. Credential files are outside this component. Recovery creates an isolated checkpoint; activate settings through their existing editors."}</p>
<button className="personal-secondary-action" disabled={busy} type="button" onClick={() => void perform(async () => {
const exported = await exportConfigurationBackup(goalId ? [goalId] : undefined);
const url = URL.createObjectURL(new Blob([JSON.stringify(exported.backup, null, 2) + "\n"], {type: "application/json"}));
const anchor = document.createElement("a"); anchor.href = url; anchor.download = "loopx-configuration-backup.json";
anchor.click(); URL.revokeObjectURL(url);
setResult(exported);
})}>{zh ? "下载配置备份" : "Download configuration backup"}</button>
<label>{zh ? "恢复备份文件" : "Recover backup file"}<input aria-label={zh ? "恢复备份文件" : "Recover backup file"} disabled={busy} type="file" accept=".json,application/json" onChange={(event) => {
const file = event.currentTarget.files?.[0]; event.currentTarget.value = "";
setBackup(null); setResult(null);
if (file) void perform(async () => {
const candidate = JSON.parse(await file.text()) as Record<string, unknown>;
const preview = await restoreConfigurationCheckpoint(candidate, false);
setBackup(candidate); setResult(preview);
});
}}/></label>
{backup && result?.status === "preview" ? <button className="personal-primary-action" disabled={busy} type="button" onClick={() => void perform(async () => {
setResult(await restoreConfigurationCheckpoint(backup, true));
})}>{zh ? "恢复为隔离检查点" : "Restore isolated checkpoint"}</button> : null}
{result ? <p role="status">{result.status === "restored"
? (zh ? "检查点已恢复并核对;当前设置及存储 provider 未改变。" : "Checkpoint restored and verified. Live settings and storage provider are unchanged.")
: (zh ? "配置备份已核对" : "Configuration backup verified")}: {result.goal_count} Goal · {result.machine_configuration_present ? (zh ? "包含设备配置" : "includes device configuration") : (zh ? "设备配置缺省" : "device configuration absent")}
{result.checkpoint_ref ? <code>{result.checkpoint_ref}</code> : null}</p> : null}
{error ? <p role="alert">{error}</p> : null}
</details>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { GoalCapabilitySettings } from "./goal-capability-settings";
import { GoalOwnershipSettings } from "./goal-ownership-settings";
import { AutomationCadenceSettings } from "./automation-cadence-settings";
import { MachineConfigurationSettings } from "./machine-configuration-settings";
import { ConfigurationBackupSettings } from "./configuration-backup-settings";
import { OperatorCredentialSettings } from "./operator-credential-settings";
import type { PersonalWorkspaceCallbacks, WorkspaceGoal, WorkspaceGoalNotification } from "./personal-workspace-model";
import type { WorkspaceTheme } from "./workspace-theme";
Expand Down Expand Up @@ -221,6 +222,7 @@ export function WorkspaceSettingsPage({
/>
) : null}
{tab === "ownership" && selectedGoal ? <GoalOwnershipSettings key={selectedGoal.goalId} goalId={selectedGoal.goalId} onChanged={onChanged} /> : null}
{tab === "capabilities" && (capabilityScope === "machine" || capabilityGoalId) ? <ConfigurationBackupSettings key={`${capabilityScope}:${capabilityGoalId}`} goalId={capabilityScope === "goal" ? capabilityGoalId : null} /> : null}
{tab === "cadence" && selectedGoal ? <AutomationCadenceSettings key={selectedGoal.goalId} goal={selectedGoal} /> : null}

{tab === "appearance" ? (
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/rfcs/STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ appendix may keep dated history, but no dated log heading may precede it.
| [RFC: Research Exploration Control Plane v0](research-exploration-control-plane-v0.md) | Accepted | none | — |
| [RFC: Semantic Vocabulary Convergence and Commit-Time Drift Checks (v0)](semantic-vocabulary-convergence-v0.md) | Accepted | none | [5 entries](ledger/semantic-vocabulary-convergence-v0/) |
| [RFC: Shared Goal Alignment and Governed Amendment Protocol (v0)](shared-goal-alignment-and-governed-amendment-v0.md) | Accepted | none | [2 entries](ledger/shared-goal-alignment-and-governed-amendment-v0/) |
| [RFC: LoopX Shared Control-Plane Authority and Pluggable State Providers (v0)](shared-goal-authority-state-provider-v0.md) | Accepted | none | [23 entries](ledger/shared-goal-authority-state-provider-v0/) |
| [RFC: LoopX Shared Control-Plane Authority and Pluggable State Providers (v0)](shared-goal-authority-state-provider-v0.md) | Accepted | none | [24 entries](ledger/shared-goal-authority-state-provider-v0/) |
| [RFC: Single-Owner Local Daemon (v0)](single-owner-local-daemon-v0.md) | Accepted | none | — |
| [RFC: TypeScript Control-Plane Migration Direction v0](typescript-control-plane-migration-v0.md) | Accepted | none | [14 entries](ledger/typescript-control-plane-migration-v0/) |

Expand Down
2 changes: 1 addition & 1 deletion docs/architecture/rfcs/STATUS.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
| [RFC:研究型探索控制面 v0](research-exploration-control-plane-v0.zh-CN.md) | 已接受 | 无 | — |
| [RFC:语义词表收敛与提交期漂移检查(v0)](semantic-vocabulary-convergence-v0.zh-CN.md) | 已接受 | 无 | [5 条](ledger/semantic-vocabulary-convergence-v0/) |
| [RFC:共享 Goal 对齐与受治理 Amendment 协议(v0)](shared-goal-alignment-and-governed-amendment-v0.zh-CN.md) | 已接受 | 无 | [2 条](ledger/shared-goal-alignment-and-governed-amendment-v0/) |
| [RFC:LoopX 共享控制面权威与可插拔状态 Provider(v0)](shared-goal-authority-state-provider-v0.zh-CN.md) | 已接受 | 无 | [23 条](ledger/shared-goal-authority-state-provider-v0/) |
| [RFC:LoopX 共享控制面权威与可插拔状态 Provider(v0)](shared-goal-authority-state-provider-v0.zh-CN.md) | 已接受 | 无 | [24 条](ledger/shared-goal-authority-state-provider-v0/) |
| [RFC: Single-Owner Local Daemon (v0)](single-owner-local-daemon-v0.md) | 已接受 | none | — |
| [RFC:LoopX 控制面 TypeScript 渐进迁移方向 v0](typescript-control-plane-migration-v0.zh-CN.md) | 已接受 | 无 | [14 条](ledger/typescript-control-plane-migration-v0/) |

Expand Down
Loading
Loading