Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Q
qjclaw-dmg
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
AI-甘富林
qjclaw-dmg
Commits
dbd31e98
Commit
dbd31e98
authored
Apr 22, 2026
by
AI-甘富林
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat(settings): support workspace directory switching
parent
2c7fc20a
Changes
3
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
241 additions
and
151 deletions
+241
-151
ipc.ts
apps/desktop/src/main/ipc.ts
+168
-129
App.tsx
apps/ui/src/App.tsx
+71
-22
index.ts
packages/shared-types/src/index.ts
+2
-0
No files found.
apps/desktop/src/main/ipc.ts
View file @
dbd31e98
This diff is collapsed.
Click to expand it.
apps/ui/src/App.tsx
View file @
dbd31e98
...
...
@@ -948,6 +948,7 @@ const mockDesktopApi = {
}
}
}),
pickWorkspaceDirectory
:
async
(
currentPath
?:
string
)
=>
currentPath
||
"D:/workspace"
,
save
:
async
(
input
:
SaveConfigInput
)
=>
({
setupMode
:
input
.
setupMode
,
provider
:
input
.
provider
,
...
...
@@ -2036,6 +2037,7 @@ export default function App() {
const copiedTokenResetRef = useRef<number | null>(null);
const composerDragDepthRef = useRef(0);
const startupWarmupRequestedRef = useRef(false);
const lastLoadedWorkspacePathRef = useRef<string | null>(null);
const [streamSmoke, setStreamSmoke] = useState<SmokeStreamSnapshot | null>(null);
const minimizeWindow = () => void desktopApi.window.minimize();
const maximizeWindow = () => void desktopApi.window.maximize();
...
...
@@ -2044,6 +2046,9 @@ export default function App() {
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 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 bindingRequired = workspace?.bindingRequired ?? !Boolean(workspace?.apiKeyConfigured ?? config?.apiKeyConfigured);
const chatLaunchState: ChatLaunchState = workspace?.chatLaunchState ?? (!bindingRequired ? "starting" : "unbound");
...
...
@@ -2305,7 +2310,6 @@ export default function App() {
setRuntimeTelemetry(nextTelemetry);
setSystemSummary(nextSystem);
setExpertDefinitions(nextExperts);
setWorkspacePathDraft((current) => current || nextConfig.workspacePath);
setGatewayStatus(statusResult);
const nextReadySkills = nextWorkspace.skills.filter((skill) => skill.ready);
...
...
@@ -2504,7 +2508,14 @@ export default function App() {
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);
setImageModelIdDraft(config.expertModelConfig.image.modelId ?? FIXED_EXPERT_MODEL_ENDPOINTS.image.modelId);
setVideoModelBaseUrlDraft(config.expertModelConfig.video.baseUrl);
...
...
@@ -3381,6 +3392,7 @@ export default function App() {
lobsterKey?: string;
workspacePath?: string;
expertModelConfig?: SaveConfigInput["expertModelConfig"];
successMessage?: string;
}) {
if (!config) {
return;
...
...
@@ -3435,7 +3447,7 @@ export default function App() {
setDigitalHumanVolcSecretKeyDraft("");
setDigitalHumanQiniuAccessKeyDraft("");
setDigitalHumanQiniuSecretKeyDraft("");
setInfoText(
trimmedLobsterKey ? ui.saveSuccessPending : ui.saveSuccessApplied
);
setInfoText(
options?.successMessage ?? (trimmedLobsterKey ? ui.saveSuccessPending : ui.saveSuccessApplied)
);
void refresh(false);
} catch (error) {
setErrorText(err(error));
...
...
@@ -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 selectedSkillBadge = selectedSkillId === DEFAULT_SKILL.id ? "千匠问天" : "@" + selectedSkill.name;
const sidebarNewSessionAction = (
...
...
@@ -4905,30 +4957,27 @@ export default function App() {
<div className="settings-section-headline">
<div>
<span className="settings-section-kicker">诊断与工作区</span>
<h4>{ui.diagnostics}</h4>
<p>{ui.diagnosticsDesc}</p>
</div>
</div>
<div className="settings-static-list">
<div className="settings-static-item">
<span>{ui.workspacePath}</span>
<strong>{config?.workspacePath || workspacePathDraft || ui.none}</strong>
<div className="workspace-directory-card">
<div className="workspace-directory-panel">
<span className="workspace-directory-eyebrow">当前生效目录</span>
<strong className="workspace-directory-path">{displayedWorkspacePath}</strong>
<p className="workspace-directory-hint">项目会从该目录加载;保存后会重新预热工作区。</p>
</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>
</>
{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 className="button-row settings-actions">
<button className="secondary" onClick={() => void handleCopyText("workspace-path", config?.workspacePath || workspacePathDraft || ui.none)}>
{copiedToken === "workspace-path" ? ui.copied : ui.copy}
</button>
<button className="secondary" onClick={() => void exportDiagnostics()}>{ui.export}</button>
<div className="button-row settings-actions workspace-directory-actions">
<button disabled={saving || !config} onClick={() => void pickWorkspaceDirectory()}>更改目录</button>
<button className="secondary" disabled={saving} onClick={() => void exportDiagnostics()}>{ui.export}</button>
</div>
</div>
</section>
...
...
packages/shared-types/src/index.ts
View file @
dbd31e98
...
...
@@ -20,6 +20,7 @@
runtimeCloudFetchConfig
:
"runtime-cloud:fetch-config"
,
runtimeTelemetryGetStatus
:
"runtime-telemetry:get-status"
,
configLoad
:
"config:load"
,
configPickWorkspaceDirectory
:
"config:pick-workspace-directory"
,
configSave
:
"config:save"
,
projectsList
:
"projects:list"
,
projectsSetActive
:
"projects:set-active"
,
...
...
@@ -802,6 +803,7 @@ export interface DesktopApi {
};
config
:
{
load
():
Promise
<
AppConfig
>
;
pickWorkspaceDirectory
(
currentPath
?:
string
):
Promise
<
string
|
null
>
;
save
(
input
:
SaveConfigInput
):
Promise
<
AppConfig
>
;
};
projects
:
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment