Commit dbd31e98 authored by AI-甘富林's avatar AI-甘富林

feat(settings): support workspace directory switching

parent 2c7fc20a
This diff is collapsed.
...@@ -948,6 +948,7 @@ const mockDesktopApi = { ...@@ -948,6 +948,7 @@ const mockDesktopApi = {
} }
} }
}), }),
pickWorkspaceDirectory: async (currentPath?: string) => currentPath || "D:/workspace",
save: async (input: SaveConfigInput) => ({ save: async (input: SaveConfigInput) => ({
setupMode: input.setupMode, setupMode: input.setupMode,
provider: input.provider, provider: input.provider,
...@@ -2036,6 +2037,7 @@ export default function App() { ...@@ -2036,6 +2037,7 @@ export default function App() {
const copiedTokenResetRef = useRef<number | null>(null); const copiedTokenResetRef = useRef<number | null>(null);
const composerDragDepthRef = useRef(0); const composerDragDepthRef = useRef(0);
const startupWarmupRequestedRef = useRef(false); const startupWarmupRequestedRef = useRef(false);
const lastLoadedWorkspacePathRef = useRef<string | null>(null);
const [streamSmoke, setStreamSmoke] = useState<SmokeStreamSnapshot | null>(null); const [streamSmoke, setStreamSmoke] = useState<SmokeStreamSnapshot | null>(null);
const minimizeWindow = () => void desktopApi.window.minimize(); const minimizeWindow = () => void desktopApi.window.minimize();
const maximizeWindow = () => void desktopApi.window.maximize(); const maximizeWindow = () => void desktopApi.window.maximize();
...@@ -2044,6 +2046,9 @@ export default function App() { ...@@ -2044,6 +2046,9 @@ export default function App() {
const effectiveSkills = useMemo(() => (readySkills.length ? [DEFAULT_SKILL, ...readySkills] : [DEFAULT_SKILL]), [readySkills]); const effectiveSkills = useMemo(() => (readySkills.length ? [DEFAULT_SKILL, ...readySkills] : [DEFAULT_SKILL]), [readySkills]);
const selectedSkill = useMemo(() => effectiveSkills.find((skill) => skill.id === selectedSkillId) ?? effectiveSkills[0] ?? DEFAULT_SKILL, [effectiveSkills, selectedSkillId]); const selectedSkill = useMemo(() => effectiveSkills.find((skill) => skill.id === selectedSkillId) ?? effectiveSkills[0] ?? DEFAULT_SKILL, [effectiveSkills, selectedSkillId]);
const setupMode = workspace?.setupMode ?? config?.setupMode ?? "employee-key"; const setupMode = workspace?.setupMode ?? config?.setupMode ?? "employee-key";
const savedWorkspacePath = config?.workspacePath ?? "";
const displayedWorkspacePath = workspacePathDraft.trim() || savedWorkspacePath || ui.none;
const hasPendingWorkspacePathChange = Boolean(config && workspacePathDraft.trim() && workspacePathDraft.trim() !== savedWorkspacePath);
const shellReady = workspace?.shellReady ?? false; const shellReady = workspace?.shellReady ?? false;
const bindingRequired = workspace?.bindingRequired ?? !Boolean(workspace?.apiKeyConfigured ?? config?.apiKeyConfigured); const bindingRequired = workspace?.bindingRequired ?? !Boolean(workspace?.apiKeyConfigured ?? config?.apiKeyConfigured);
const chatLaunchState: ChatLaunchState = workspace?.chatLaunchState ?? (!bindingRequired ? "starting" : "unbound"); const chatLaunchState: ChatLaunchState = workspace?.chatLaunchState ?? (!bindingRequired ? "starting" : "unbound");
...@@ -2305,7 +2310,6 @@ export default function App() { ...@@ -2305,7 +2310,6 @@ export default function App() {
setRuntimeTelemetry(nextTelemetry); setRuntimeTelemetry(nextTelemetry);
setSystemSummary(nextSystem); setSystemSummary(nextSystem);
setExpertDefinitions(nextExperts); setExpertDefinitions(nextExperts);
setWorkspacePathDraft((current) => current || nextConfig.workspacePath);
setGatewayStatus(statusResult); setGatewayStatus(statusResult);
const nextReadySkills = nextWorkspace.skills.filter((skill) => skill.ready); const nextReadySkills = nextWorkspace.skills.filter((skill) => skill.ready);
...@@ -2504,7 +2508,14 @@ export default function App() { ...@@ -2504,7 +2508,14 @@ export default function App() {
return; return;
} }
setWorkspacePathDraft(config.workspacePath); setWorkspacePathDraft((current) => {
const lastLoadedWorkspacePath = lastLoadedWorkspacePathRef.current;
lastLoadedWorkspacePathRef.current = config.workspacePath;
if (!current || current === lastLoadedWorkspacePath) {
return config.workspacePath;
}
return current;
});
setImageModelBaseUrlDraft(config.expertModelConfig.image.baseUrl); setImageModelBaseUrlDraft(config.expertModelConfig.image.baseUrl);
setImageModelIdDraft(config.expertModelConfig.image.modelId ?? FIXED_EXPERT_MODEL_ENDPOINTS.image.modelId); setImageModelIdDraft(config.expertModelConfig.image.modelId ?? FIXED_EXPERT_MODEL_ENDPOINTS.image.modelId);
setVideoModelBaseUrlDraft(config.expertModelConfig.video.baseUrl); setVideoModelBaseUrlDraft(config.expertModelConfig.video.baseUrl);
...@@ -3381,6 +3392,7 @@ export default function App() { ...@@ -3381,6 +3392,7 @@ export default function App() {
lobsterKey?: string; lobsterKey?: string;
workspacePath?: string; workspacePath?: string;
expertModelConfig?: SaveConfigInput["expertModelConfig"]; expertModelConfig?: SaveConfigInput["expertModelConfig"];
successMessage?: string;
}) { }) {
if (!config) { if (!config) {
return; return;
...@@ -3435,7 +3447,7 @@ export default function App() { ...@@ -3435,7 +3447,7 @@ export default function App() {
setDigitalHumanVolcSecretKeyDraft(""); setDigitalHumanVolcSecretKeyDraft("");
setDigitalHumanQiniuAccessKeyDraft(""); setDigitalHumanQiniuAccessKeyDraft("");
setDigitalHumanQiniuSecretKeyDraft(""); setDigitalHumanQiniuSecretKeyDraft("");
setInfoText(trimmedLobsterKey ? ui.saveSuccessPending : ui.saveSuccessApplied); setInfoText(options?.successMessage ?? (trimmedLobsterKey ? ui.saveSuccessPending : ui.saveSuccessApplied));
void refresh(false); void refresh(false);
} catch (error) { } catch (error) {
setErrorText(err(error)); setErrorText(err(error));
...@@ -4079,6 +4091,46 @@ export default function App() { ...@@ -4079,6 +4091,46 @@ export default function App() {
} }
} }
async function pickWorkspaceDirectory() {
if (!config || saving) {
return;
}
setErrorText("");
try {
const nextPath = await desktopApi.config.pickWorkspaceDirectory(workspacePathDraft.trim() || config.workspacePath);
if (!nextPath?.trim()) {
return;
}
setWorkspacePathDraft(nextPath.trim());
setInfoText("");
} catch (error) {
setErrorText(err(error));
}
}
function restoreWorkspaceDirectory() {
if (!config) {
return;
}
setWorkspacePathDraft(config.workspacePath);
setErrorText("");
setInfoText("");
}
async function saveWorkspaceDirectory() {
if (!hasPendingWorkspacePathChange) {
return;
}
await saveConfig({
workspacePath: workspacePathDraft,
successMessage: "工作目录已保存,正在重新预热工作区。"
});
}
const sidebarSessionLabel = viewMode === "experts" ? "专家会话" : "会话管理"; const sidebarSessionLabel = viewMode === "experts" ? "专家会话" : "会话管理";
const selectedSkillBadge = selectedSkillId === DEFAULT_SKILL.id ? "千匠问天" : "@" + selectedSkill.name; const selectedSkillBadge = selectedSkillId === DEFAULT_SKILL.id ? "千匠问天" : "@" + selectedSkill.name;
const sidebarNewSessionAction = ( const sidebarNewSessionAction = (
...@@ -4905,30 +4957,27 @@ export default function App() { ...@@ -4905,30 +4957,27 @@ export default function App() {
<div className="settings-section-headline"> <div className="settings-section-headline">
<div> <div>
<span className="settings-section-kicker">诊断与工作区</span> <span className="settings-section-kicker">诊断与工作区</span>
<h4>{ui.diagnostics}</h4>
<p>{ui.diagnosticsDesc}</p>
</div> </div>
</div> </div>
<div className="settings-static-list"> <div className="workspace-directory-card">
<div className="settings-static-item"> <div className="workspace-directory-panel">
<span>{ui.workspacePath}</span> <span className="workspace-directory-eyebrow">当前生效目录</span>
<strong>{config?.workspacePath || workspacePathDraft || ui.none}</strong> <strong className="workspace-directory-path">{displayedWorkspacePath}</strong>
<p className="workspace-directory-hint">项目会从该目录加载;保存后会重新预热工作区。</p>
</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>
</div> </div>
<div className="diagnostic-meta-list">
{runtimeCloudStatus ? (
<>
<div className="mini-info"><span>Runtime Cloud Target</span><strong>{runtimeCloudStatus.baseUrl || ui.none}</strong></div>
<div className="mini-info"><span>Target Source</span><strong>{runtimeCloudStatus.baseUrlSource ?? ui.none}</strong></div>
<div className="mini-info"><span>Binding Host</span><strong>{runtimeCloudStatus.baseHost ?? ui.none}</strong></div>
</>
) : null} ) : null}
</div> </div>
<div className="button-row settings-actions"> <div className="button-row settings-actions workspace-directory-actions">
<button className="secondary" onClick={() => void handleCopyText("workspace-path", config?.workspacePath || workspacePathDraft || ui.none)}> <button disabled={saving || !config} onClick={() => void pickWorkspaceDirectory()}>更改目录</button>
{copiedToken === "workspace-path" ? ui.copied : ui.copy} <button className="secondary" disabled={saving} onClick={() => void exportDiagnostics()}>{ui.export}</button>
</button>
<button className="secondary" onClick={() => void exportDiagnostics()}>{ui.export}</button>
</div> </div>
</div> </div>
</section> </section>
......
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
runtimeCloudFetchConfig: "runtime-cloud:fetch-config", runtimeCloudFetchConfig: "runtime-cloud:fetch-config",
runtimeTelemetryGetStatus: "runtime-telemetry:get-status", runtimeTelemetryGetStatus: "runtime-telemetry:get-status",
configLoad: "config:load", configLoad: "config:load",
configPickWorkspaceDirectory: "config:pick-workspace-directory",
configSave: "config:save", configSave: "config:save",
projectsList: "projects:list", projectsList: "projects:list",
projectsSetActive: "projects:set-active", projectsSetActive: "projects:set-active",
...@@ -802,6 +803,7 @@ export interface DesktopApi { ...@@ -802,6 +803,7 @@ export interface DesktopApi {
}; };
config: { config: {
load(): Promise<AppConfig>; load(): Promise<AppConfig>;
pickWorkspaceDirectory(currentPath?: string): Promise<string | null>;
save(input: SaveConfigInput): Promise<AppConfig>; save(input: SaveConfigInput): Promise<AppConfig>;
}; };
projects: { projects: {
......
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