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
c591100b
Commit
c591100b
authored
May 14, 2026
by
edy
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat(settings): improve secret reveal and config copying
parent
c9ab97c3
Changes
9
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
613 additions
and
97 deletions
+613
-97
ipc.ts
apps/desktop/src/main/ipc.ts
+60
-1
index.ts
apps/desktop/src/preload/index.ts
+3
-1
App.tsx
apps/ui/src/App.tsx
+15
-0
SettingsPanels.tsx
apps/ui/src/features/settings/SettingsPanels.tsx
+272
-93
mock-desktop-api.ts
apps/ui/src/lib/mock-desktop-api.ts
+1
-0
settings.css
apps/ui/src/styles/settings.css
+147
-0
configRevealSecretSource.test.ts
apps/ui/test/configRevealSecretSource.test.ts
+18
-0
settingsPanelsSource.test.ts
apps/ui/test/settingsPanelsSource.test.ts
+77
-0
index.ts
packages/shared-types/src/index.ts
+20
-2
No files found.
apps/desktop/src/main/ipc.ts
View file @
c591100b
...
@@ -8,6 +8,7 @@ import {
...
@@ -8,6 +8,7 @@ import {
type
ChatAttachment
,
type
ChatAttachment
,
type
ChatMessage
,
type
ChatMessage
,
type
ChatStreamEvent
,
type
ChatStreamEvent
,
type
ConfigSecretId
,
type
DesktopApi
,
type
DesktopApi
,
type
GatewayStatus
,
type
GatewayStatus
,
type
PluginSummary
,
type
PluginSummary
,
...
@@ -669,6 +670,62 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc
...
@@ -669,6 +670,62 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc
return applySecretStateToConfig(await configService.load());
return applySecretStateToConfig(await configService.load());
};
};
const revealConfigSecret = async (secretId: ConfigSecretId): Promise<string | null> => {
let value: string | undefined;
switch (secretId) {
case "
lobsterKey
":
value = await secretManager.getApiKey();
break;
case "
copywritingModelApiKey
":
value = await secretManager.getCopywritingModelApiKey();
break;
case "
imageModelApiKey
":
value = await secretManager.getImageModelApiKey();
break;
case "
videoModelApiKey
":
value = await secretManager.getVideoModelApiKey();
break;
case "
digitalHumanVolcAccessKey
":
value = await secretManager.getDigitalHumanVolcAccessKey();
break;
case "
digitalHumanVolcSecretKey
":
value = await secretManager.getDigitalHumanVolcSecretKey();
break;
case "
digitalHumanQiniuAccessKey
":
value = await secretManager.getDigitalHumanQiniuAccessKey();
break;
case "
digitalHumanQiniuSecretKey
":
value = await secretManager.getDigitalHumanQiniuSecretKey();
break;
case "
videoAnalyzerApiKey
":
value = await secretManager.getVideoAnalyzerApiKey();
break;
case "
replicationBriefApiKey
":
value = await secretManager.getReplicationBriefApiKey();
break;
case "
vectcutApiKey
":
value = await secretManager.getVectCutApiKey();
break;
case "
xhsFeishuAppId
":
value = await secretManager.getXhsFeishuAppId();
break;
case "
xhsFeishuAppSecret
":
value = await secretManager.getXhsFeishuAppSecret();
break;
case "
xhsFeishuAppToken
":
value = await secretManager.getXhsFeishuAppToken();
break;
case "
xhsFeishuTableId
":
value = await secretManager.getXhsFeishuTableId();
break;
default:
throw new Error("
Unsupported
config
secret
id
.
");
}
return value ? value : null;
};
const prepareProjectModelRuntime = async (projectId: string, projectRoot: string): Promise<Record<string, string>> => {
const prepareProjectModelRuntime = async (projectId: string, projectRoot: string): Promise<Record<string, string>> => {
const config = await configService.load();
const config = await configService.load();
const [
const [
...
@@ -2498,6 +2555,7 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc
...
@@ -2498,6 +2555,7 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc
return pickWorkspaceDirectory(BrowserWindow.fromWebContents(event.sender), currentPath);
return pickWorkspaceDirectory(BrowserWindow.fromWebContents(event.sender), currentPath);
});
});
ipcMain.handle(IPC_CHANNELS.configSave, async (_event, input: SaveConfigInput) => saveAppConfig(input));
ipcMain.handle(IPC_CHANNELS.configSave, async (_event, input: SaveConfigInput) => saveAppConfig(input));
ipcMain.handle(IPC_CHANNELS.configRevealSecret, async (_event, secretId: ConfigSecretId) => revealConfigSecret(secretId));
ipcMain.handle(IPC_CHANNELS.authGetSession, async () => authClient.getSessionSummary());
ipcMain.handle(IPC_CHANNELS.authGetSession, async () => authClient.getSessionSummary());
ipcMain.handle(IPC_CHANNELS.authSignIn, async (_event, input: SignInInput) => {
ipcMain.handle(IPC_CHANNELS.authSignIn, async (_event, input: SignInInput) => {
...
@@ -2638,7 +2696,8 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc
...
@@ -2638,7 +2696,8 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc
config: {
config: {
load: () => getEffectiveConfig(),
load: () => getEffectiveConfig(),
pickWorkspaceDirectory: (currentPath?: string) => pickWorkspaceDirectory(null, currentPath),
pickWorkspaceDirectory: (currentPath?: string) => pickWorkspaceDirectory(null, currentPath),
save: (input: SaveConfigInput) => saveAppConfig(input)
save: (input: SaveConfigInput) => saveAppConfig(input),
revealSecret: (secretId: ConfigSecretId) => revealConfigSecret(secretId)
},
},
projects: {
projects: {
list: () => projectStore.listProjects(),
list: () => projectStore.listProjects(),
...
...
apps/desktop/src/preload/index.ts
View file @
c591100b
...
@@ -3,6 +3,7 @@ import {
...
@@ -3,6 +3,7 @@ import {
IPC_CHANNELS
,
IPC_CHANNELS
,
type
ChatAttachment
,
type
ChatAttachment
,
type
ChatStreamListener
,
type
ChatStreamListener
,
type
ConfigSecretId
,
type
DesktopApi
,
type
DesktopApi
,
type
RuntimeCloudFetchAction
,
type
RuntimeCloudFetchAction
,
type
SaveConfigInput
,
type
SaveConfigInput
,
...
@@ -45,7 +46,8 @@ const desktopApi: DesktopApi = {
...
@@ -45,7 +46,8 @@ const desktopApi: DesktopApi = {
config
:
{
config
:
{
load
:
()
=>
ipcRenderer
.
invoke
(
IPC_CHANNELS
.
configLoad
),
load
:
()
=>
ipcRenderer
.
invoke
(
IPC_CHANNELS
.
configLoad
),
pickWorkspaceDirectory
:
(
currentPath
?:
string
)
=>
ipcRenderer
.
invoke
(
IPC_CHANNELS
.
configPickWorkspaceDirectory
,
currentPath
),
pickWorkspaceDirectory
:
(
currentPath
?:
string
)
=>
ipcRenderer
.
invoke
(
IPC_CHANNELS
.
configPickWorkspaceDirectory
,
currentPath
),
save
:
(
input
:
SaveConfigInput
)
=>
ipcRenderer
.
invoke
(
IPC_CHANNELS
.
configSave
,
input
)
save
:
(
input
:
SaveConfigInput
)
=>
ipcRenderer
.
invoke
(
IPC_CHANNELS
.
configSave
,
input
),
revealSecret
:
(
secretId
:
ConfigSecretId
)
=>
ipcRenderer
.
invoke
(
IPC_CHANNELS
.
configRevealSecret
,
secretId
)
},
},
projects
:
{
projects
:
{
list
:
()
=>
ipcRenderer
.
invoke
(
IPC_CHANNELS
.
projectsList
),
list
:
()
=>
ipcRenderer
.
invoke
(
IPC_CHANNELS
.
projectsList
),
...
...
apps/ui/src/App.tsx
View file @
c591100b
...
@@ -6,6 +6,7 @@ import type {
...
@@ -6,6 +6,7 @@ import type {
ChatAttachment
,
ChatAttachment
,
ChatLaunchState
,
ChatLaunchState
,
ChatMessage
,
ChatMessage
,
ConfigSecretId
,
ExpertEntryMode
,
ExpertEntryMode
,
GatewayHealth
,
GatewayHealth
,
GatewayStatus
,
GatewayStatus
,
...
@@ -1385,6 +1386,19 @@ export default function App() {
...
@@ -1385,6 +1386,19 @@ export default function App() {
const
settingsStatusHint
=
showSettingsStatusHint
const
settingsStatusHint
=
showSettingsStatusHint
?
<
div
className=
{
"inline-hint settings-runtime-hint"
+
(
chatLaunchState
===
"error"
?
" error"
:
""
)
}
>
{
startupMessage
}
</
div
>
?
<
div
className=
{
"inline-hint settings-runtime-hint"
+
(
chatLaunchState
===
"error"
?
" error"
:
""
)
}
>
{
startupMessage
}
</
div
>
:
null
;
:
null
;
const
revealConfigSecret
=
useCallback
(
async
(
secretId
:
ConfigSecretId
)
=>
{
setErrorText
(
""
);
try
{
const
secretValue
=
await
desktopApi
.
config
.
revealSecret
(
secretId
);
if
(
!
secretValue
)
{
setErrorText
(
"本机未保存该密钥。"
);
}
return
secretValue
;
}
catch
(
error
)
{
setErrorText
(
err
(
error
));
return
null
;
}
},
[]);
const
settingsPanelsProps
=
{
const
settingsPanelsProps
=
{
config
,
config
,
workspaceApiKeyConfigured
:
Boolean
(
workspace
?.
apiKeyConfigured
),
workspaceApiKeyConfigured
:
Boolean
(
workspace
?.
apiKeyConfigured
),
...
@@ -1458,6 +1472,7 @@ export default function App() {
...
@@ -1458,6 +1472,7 @@ export default function App() {
onResetDigitalHumanConfig
:
resetDigitalHumanSettingsDrafts
,
onResetDigitalHumanConfig
:
resetDigitalHumanSettingsDrafts
,
onSaveDouyinRuntimeConfig
:
()
=>
void
saveDouyinRuntimeConfig
(),
onSaveDouyinRuntimeConfig
:
()
=>
void
saveDouyinRuntimeConfig
(),
onResetDouyinRuntimeConfig
:
resetDouyinRuntimeSettingsDrafts
,
onResetDouyinRuntimeConfig
:
resetDouyinRuntimeSettingsDrafts
,
onRevealSecret
:
revealConfigSecret
,
pickWorkspaceDirectory
,
pickWorkspaceDirectory
,
exportDiagnostics
exportDiagnostics
}
satisfies
ComponentProps
<
typeof
SettingsPanels
>
;
}
satisfies
ComponentProps
<
typeof
SettingsPanels
>
;
...
...
apps/ui/src/features/settings/SettingsPanels.tsx
View file @
c591100b
This diff is collapsed.
Click to expand it.
apps/ui/src/lib/mock-desktop-api.ts
View file @
c591100b
...
@@ -290,6 +290,7 @@ export const mockDesktopApi = {
...
@@ -290,6 +290,7 @@ export const mockDesktopApi = {
}
}
}),
}),
pickWorkspaceDirectory
:
async
(
currentPath
?:
string
)
=>
currentPath
||
"D:/workspace"
,
pickWorkspaceDirectory
:
async
(
currentPath
?:
string
)
=>
currentPath
||
"D:/workspace"
,
revealSecret
:
async
(
secretId
:
string
)
=>
`mock-
${
secretId
}
`
,
save
:
async
(
input
:
SaveConfigInput
)
=>
({
save
:
async
(
input
:
SaveConfigInput
)
=>
({
setupMode
:
input
.
setupMode
,
setupMode
:
input
.
setupMode
,
provider
:
input
.
provider
,
provider
:
input
.
provider
,
...
...
apps/ui/src/styles/settings.css
View file @
c591100b
...
@@ -367,6 +367,153 @@
...
@@ -367,6 +367,153 @@
box-shadow
:
var
(
--settings-focus-shadow
);
box-shadow
:
var
(
--settings-focus-shadow
);
}
}
.settings-secret-input-row
{
position
:
relative
;
min-width
:
0
;
}
.settings-secret-input-row
input
{
width
:
100%
;
min-width
:
0
;
}
.settings-secret-input-row.has-reveal
input
{
padding-right
:
calc
(
var
(
--settings-control-height
)
+
8px
);
}
.settings-secret-value-input
{
overflow
:
hidden
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
}
.settings-secret-reveal-button
{
position
:
absolute
;
top
:
50%
;
right
:
4px
;
width
:
calc
(
var
(
--settings-control-height
)
-
8px
);
height
:
calc
(
var
(
--settings-control-height
)
-
8px
);
display
:
inline-flex
;
align-items
:
center
;
justify-content
:
center
;
transform
:
translateY
(
-50%
);
border
:
0
;
border-radius
:
6px
;
padding
:
0
;
z-index
:
1
;
background
:
transparent
;
color
:
#60729b
;
box-shadow
:
none
;
cursor
:
pointer
;
}
.shell.openclaw-theme
.settings-secret-reveal-button
{
transform
:
translateY
(
-50%
);
box-shadow
:
none
;
}
.settings-secret-reveal-button
:hover:not
(
:disabled
)
{
color
:
#1f3f6d
;
background
:
rgba
(
31
,
63
,
109
,
0.08
);
transform
:
translateY
(
-50%
);
box-shadow
:
none
;
}
.shell.openclaw-theme
.settings-secret-reveal-button
:hover:not
(
:disabled
)
{
transform
:
translateY
(
-50%
);
box-shadow
:
none
;
}
.settings-secret-reveal-button
:focus-visible
,
.settings-secret-reveal-button
:active
{
outline
:
none
;
transform
:
translateY
(
-50%
);
box-shadow
:
none
;
}
.shell.openclaw-theme
.settings-secret-reveal-button
:focus-visible
,
.shell.openclaw-theme
.settings-secret-reveal-button
:active
{
transform
:
translateY
(
-50%
);
box-shadow
:
none
;
}
.settings-secret-reveal-button
:disabled
{
cursor
:
default
;
opacity
:
0.58
;
}
.settings-secret-reveal-button
svg
{
width
:
22px
;
height
:
22px
;
flex
:
0
0
auto
;
}
.settings-readonly-config-field
{
min-width
:
0
;
}
.settings-readonly-config-value
{
min-height
:
var
(
--settings-control-height
);
height
:
var
(
--settings-control-height
);
display
:
flex
;
align-items
:
center
;
gap
:
8px
;
min-width
:
0
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
padding
:
0
var
(
--settings-control-padding-x
);
border
:
1px
solid
var
(
--settings-control-border
);
border-radius
:
var
(
--settings-control-radius
);
background
:
rgba
(
246
,
249
,
255
,
0.82
);
color
:
#405679
;
font-size
:
13px
;
line-height
:
var
(
--settings-control-height
);
box-shadow
:
inset
0
1px
0
rgba
(
255
,
255
,
255
,
0.9
);
}
.settings-readonly-config-text
{
min-width
:
0
;
flex
:
1
1
auto
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
}
.settings-readonly-config-copy-feedback
{
flex
:
0
0
auto
;
min-width
:
42px
;
padding
:
0
7px
;
border-radius
:
999px
;
background
:
rgba
(
37
,
99
,
235
,
0.1
);
color
:
#1d4ed8
;
font-size
:
11px
;
font-weight
:
700
;
line-height
:
22px
;
text-align
:
center
;
}
.settings-readonly-config-value
[
role
=
"button"
]
{
cursor
:
copy
;
}
.settings-readonly-config-value
[
aria-disabled
=
"true"
]
{
cursor
:
default
;
}
.settings-readonly-config-value
[
role
=
"button"
]
:hover:not
([
aria-disabled
=
"true"
])
{
border-color
:
var
(
--settings-control-border-strong
);
color
:
#1f3f6d
;
background
:
rgba
(
255
,
255
,
255
,
0.92
);
}
.settings-readonly-config-value
:focus-visible
{
outline
:
2px
solid
var
(
--settings-focus-outline
);
outline-offset
:
0
;
border-color
:
var
(
--settings-control-border-strong
);
box-shadow
:
var
(
--settings-focus-shadow
);
}
.model-config-grid
{
.model-config-grid
{
display
:
grid
;
display
:
grid
;
min-height
:
0
;
min-height
:
0
;
...
...
apps/ui/test/configRevealSecretSource.test.ts
0 → 100644
View file @
c591100b
import
test
from
"node:test"
import
assert
from
"node:assert/strict"
import
{
readFileSync
}
from
"node:fs"
const
sharedTypesSource
=
readFileSync
(
new
URL
(
"../../../packages/shared-types/src/index.ts"
,
import
.
meta
.
url
),
"utf8"
)
const
preloadSource
=
readFileSync
(
new
URL
(
"../../desktop/src/preload/index.ts"
,
import
.
meta
.
url
),
"utf8"
)
const
ipcSource
=
readFileSync
(
new
URL
(
"../../desktop/src/main/ipc.ts"
,
import
.
meta
.
url
),
"utf8"
)
test
(
"config reveal secret IPC is whitelisted in shared types, preload, and main IPC"
,
()
=>
{
assert
.
match
(
sharedTypesSource
,
/configRevealSecret:
\s
*"config:reveal-secret"/
)
assert
.
match
(
sharedTypesSource
,
/export type ConfigSecretId =/
)
assert
.
match
(
sharedTypesSource
,
/revealSecret
\(
secretId: ConfigSecretId
\)
: Promise<string
\|
null>/
)
assert
.
match
(
preloadSource
,
/type ConfigSecretId/
)
assert
.
match
(
preloadSource
,
/revealSecret:
\(
secretId: ConfigSecretId
\)
=> ipcRenderer
\.
invoke
\(
IPC_CHANNELS
\.
configRevealSecret, secretId
\)
/
)
assert
.
match
(
ipcSource
,
/const revealConfigSecret = async
\(
secretId: ConfigSecretId
\)
: Promise<string
\|
null> =>/
)
assert
.
match
(
ipcSource
,
/ipcMain
\.
handle
\(
IPC_CHANNELS
\.
configRevealSecret/
)
assert
.
match
(
ipcSource
,
/revealSecret:
\(
secretId: ConfigSecretId
\)
=> revealConfigSecret
\(
secretId
\)
/
)
})
apps/ui/test/settingsPanelsSource.test.ts
View file @
c591100b
...
@@ -65,3 +65,80 @@ test("settings panel actions are wired per section instead of global handlers",
...
@@ -65,3 +65,80 @@ test("settings panel actions are wired per section instead of global handlers",
assert
.
match
(
settingsPanelsSource
,
/
\b
onSaveDigitalHumanConfig
\b
/
)
assert
.
match
(
settingsPanelsSource
,
/
\b
onSaveDigitalHumanConfig
\b
/
)
assert
.
match
(
settingsPanelsSource
,
/
\b
onSaveDouyinRuntimeConfig
\b
/
)
assert
.
match
(
settingsPanelsSource
,
/
\b
onSaveDouyinRuntimeConfig
\b
/
)
})
})
test
(
"settings secret inputs default hidden and gate reveal buttons behind saved secrets"
,
()
=>
{
assert
.
match
(
settingsPanelsSource
,
/type=
\{
visible
\?
"text" : "password"
\}
/
)
assert
.
match
(
settingsPanelsSource
,
/configured:
\s
*boolean/
)
assert
.
match
(
settingsPanelsSource
,
/const visible = configured && Boolean
\(
revealedSecrets
\[
secretId
\]\)
/
)
assert
.
match
(
settingsPanelsSource
,
/const secretPlaceholder = configured && !value && !visible
\?
"••••••••••••" : placeholder/
)
assert
.
match
(
settingsPanelsSource
,
/placeholder=
\{
secretPlaceholder
\}
/
)
assert
.
match
(
settingsPanelsSource
,
/configured
\?
\(
/
)
assert
.
match
(
settingsPanelsSource
,
/secretId: "lobsterKey"/
)
assert
.
match
(
settingsPanelsSource
,
/configured: workspaceApiKeyConfigured/
)
assert
.
match
(
settingsPanelsSource
,
/secretId: "copywritingModelApiKey"/
)
assert
.
match
(
settingsPanelsSource
,
/configured: Boolean
\(
config
\?\.
expertModelConfig
\.
copywriting
\.
apiKeyConfigured
\)
/
)
assert
.
match
(
settingsPanelsSource
,
/secretId: "imageModelApiKey"/
)
assert
.
match
(
settingsPanelsSource
,
/secretId: "videoModelApiKey"/
)
assert
.
match
(
settingsPanelsSource
,
/secretId: "videoAnalyzerApiKey"/
)
assert
.
match
(
settingsPanelsSource
,
/secretId: "replicationBriefApiKey"/
)
assert
.
match
(
settingsPanelsSource
,
/secretId: "vectcutApiKey"/
)
assert
.
match
(
settingsPanelsSource
,
/"settings-secret-input-row", configured
\?
"has-reveal" : ""/
)
assert
.
match
(
settingsPanelsSource
,
/
\[
"settings-secret-value-input", inputClassName
\]\.
filter
\(
Boolean
\)\.
join
\(
" "
\)
/
)
assert
.
match
(
settingsPanelsSource
,
/className="settings-secret-reveal-button settings-secret-reveal-button-adornment"/
)
assert
.
match
(
settingsPanelsSource
,
/onRevealSecret
\(
secretId
\)
/
)
assert
.
match
(
settingsStylesSource
,
/
\.
settings-secret-input-row
\s
*
\{[\s\S]
*
?
position:
\s
*relative;/m
)
assert
.
match
(
settingsStylesSource
,
/
\.
settings-secret-input-row
\.
has-reveal input
\s
*
\{[\s\S]
*
?
padding-right:
\s
*calc
\(
var
\(
--settings-control-height
\)
\+
8px
\)
;/m
)
assert
.
match
(
settingsStylesSource
,
/
\.
settings-secret-value-input
\s
*
\{[\s\S]
*
?
overflow:
\s
*hidden;
[\s\S]
*
?
text-overflow:
\s
*ellipsis;
[\s\S]
*
?
white-space:
\s
*nowrap;/m
)
assert
.
match
(
settingsStylesSource
,
/
\.
settings-secret-reveal-button
\s
*
\{[\s\S]
*
?
position:
\s
*absolute;
[\s\S]
*
?
right:
\s
*4px;/m
)
assert
.
match
(
settingsStylesSource
,
/
\.
settings-secret-reveal-button
\s
*
\{[\s\S]
*
?
padding:
\s
*0;
[\s\S]
*
?
z-index:
\s
*1;/m
)
assert
.
match
(
settingsStylesSource
,
/
\.
shell
\.
openclaw-theme
\.
settings-secret-reveal-button
\s
*
\{[\s\S]
*
?
transform:
\s
*translateY
\(
-50%
\)
;/m
)
assert
.
match
(
settingsStylesSource
,
/
\.
settings-secret-reveal-button:hover:not
\(
:disabled
\)\s
*
\{[\s\S]
*
?
transform:
\s
*translateY
\(
-50%
\)
;
[\s\S]
*
?
box-shadow:
\s
*none;/m
)
assert
.
match
(
settingsStylesSource
,
/
\.
shell
\.
openclaw-theme
\.
settings-secret-reveal-button:hover:not
\(
:disabled
\)\s
*
\{[\s\S]
*
?
transform:
\s
*translateY
\(
-50%
\)
;
[\s\S]
*
?
box-shadow:
\s
*none;/m
)
assert
.
match
(
settingsStylesSource
,
/
\.
settings-secret-reveal-button:focus-visible,
\s
*
\n\.
settings-secret-reveal-button:active
\s
*
\{[\s\S]
*
?
transform:
\s
*translateY
\(
-50%
\)
;/m
)
assert
.
match
(
settingsStylesSource
,
/
\.
settings-secret-reveal-button svg
\s
*
\{[\s\S]
*
?
width:
\s
*22px;
[\s\S]
*
?
height:
\s
*22px;/m
)
})
test
(
"fixed expert model cards show base_url and model_id as read-only values"
,
()
=>
{
assert
.
match
(
settingsPanelsSource
,
/config
\?\.
expertModelConfig
\.
copywriting
\.
baseUrl/
)
assert
.
match
(
settingsPanelsSource
,
/config
\?\.
expertModelConfig
\.
copywriting
\.
modelId/
)
assert
.
match
(
settingsPanelsSource
,
/config
\?\.
expertModelConfig
\.
image
\.
baseUrl/
)
assert
.
match
(
settingsPanelsSource
,
/config
\?\.
expertModelConfig
\.
image
\.
modelId/
)
assert
.
match
(
settingsPanelsSource
,
/config
\?\.
expertModelConfig
\.
video
\.
baseUrl/
)
assert
.
match
(
settingsPanelsSource
,
/config
\?\.
expertModelConfig
\.
video
\.
modelId/
)
assert
.
match
(
settingsPanelsSource
,
/settings-readonly-config-value/
)
})
test
(
"douyin runtime base urls, model ids, and file base url use copyable read-only values"
,
()
=>
{
assert
.
match
(
settingsPanelsSource
,
/renderReadonlyConfigValue
\(
"base_url", config
\?\.
douyinRuntimeConfig
\.
videoAnalyzer
\.
baseUrl
\)
/
)
assert
.
match
(
settingsPanelsSource
,
/renderReadonlyConfigValue
\(
"model_id", config
\?\.
douyinRuntimeConfig
\.
videoAnalyzer
\.
modelId
\|\|
"doubao-vision"
\)
/
)
assert
.
match
(
settingsPanelsSource
,
/renderReadonlyConfigValue
\(
"base_url", config
\?\.
douyinRuntimeConfig
\.
replicationBrief
\.
baseUrl
\)
/
)
assert
.
match
(
settingsPanelsSource
,
/renderReadonlyConfigValue
\(
"model_id", config
\?\.
douyinRuntimeConfig
\.
replicationBrief
\.
modelId
\|\|
"qwen-max"
\)
/
)
assert
.
match
(
settingsPanelsSource
,
/renderReadonlyConfigValue
\(
"base_url", config
\?\.
douyinRuntimeConfig
\.
vectcut
\.
baseUrl
\)
/
)
assert
.
match
(
settingsPanelsSource
,
/renderReadonlyConfigValue
\(
"file_base_url", config
\?\.
douyinRuntimeConfig
\.
vectcut
\.
fileBaseUrl
\)
/
)
assert
.
doesNotMatch
(
settingsPanelsSource
,
/setVideoAnalyzerBaseUrl
\(
event
\.
target
\.
value
\)
/
)
assert
.
doesNotMatch
(
settingsPanelsSource
,
/setVideoAnalyzerModelId
\(
event
\.
target
\.
value
\)
/
)
assert
.
doesNotMatch
(
settingsPanelsSource
,
/setReplicationBriefBaseUrl
\(
event
\.
target
\.
value
\)
/
)
assert
.
doesNotMatch
(
settingsPanelsSource
,
/setReplicationBriefModelId
\(
event
\.
target
\.
value
\)
/
)
assert
.
doesNotMatch
(
settingsPanelsSource
,
/setVectcutBaseUrl
\(
event
\.
target
\.
value
\)
/
)
assert
.
doesNotMatch
(
settingsPanelsSource
,
/setVectcutFileBaseUrl
\(
event
\.
target
\.
value
\)
/
)
})
test
(
"read-only config values expose full values by title and copy on click or keyboard"
,
()
=>
{
assert
.
match
(
settingsPanelsSource
,
/const copyReadonlyConfigValue =
\(
value
\?
: string
\)
=>/
)
assert
.
match
(
settingsPanelsSource
,
/const
\[
copiedConfigValue, setCopiedConfigValue
\]
= useState<string
\|
null>
\(
null
\)
/
)
assert
.
match
(
settingsPanelsSource
,
/setCopiedConfigValue
\(
value
\)
/
)
assert
.
match
(
settingsPanelsSource
,
/settings-readonly-config-copy-feedback/
)
assert
.
match
(
settingsPanelsSource
,
/>✅已复制</
)
assert
.
match
(
settingsPanelsSource
,
/navigator
\.
clipboard
\.
writeText
\(
value
\)
/
)
assert
.
match
(
settingsPanelsSource
,
/const handleReadonlyConfigKeyDown =
\(
event: KeyboardEvent<HTMLDivElement>, value
\?
: string
\)
=>/
)
assert
.
match
(
settingsPanelsSource
,
/event
\.
key !== "Enter" && event
\.
key !== " "/
)
assert
.
match
(
settingsPanelsSource
,
/role="button"/
)
assert
.
match
(
settingsPanelsSource
,
/tabIndex=
\{
value
\?
0 : -1
\}
/
)
assert
.
match
(
settingsPanelsSource
,
/title=
\{
value
\|\|
undefined
\}
/
)
assert
.
match
(
settingsPanelsSource
,
/onClick=
\{\(\)
=> copyReadonlyConfigValue
\(
value
\)\}
/
)
assert
.
match
(
settingsPanelsSource
,
/onKeyDown=
\{\(
event
\)
=> handleReadonlyConfigKeyDown
\(
event, value
\)\}
/
)
assert
.
match
(
settingsStylesSource
,
/
\.
settings-readonly-config-value
\s
*
\{[\s\S]
*
?
overflow:
\s
*hidden;
[\s\S]
*
?
text-overflow:
\s
*ellipsis;
[\s\S]
*
?
white-space:
\s
*nowrap;/m
)
assert
.
match
(
settingsStylesSource
,
/
\.
settings-readonly-config-copy-feedback
\s
*
\{
/
)
assert
.
match
(
settingsStylesSource
,
/
\.
settings-readonly-config-value
\[
role="button"
\]\s
*
\{[\s\S]
*
?
cursor:
\s
*copy;/m
)
assert
.
match
(
settingsStylesSource
,
/
\.
settings-readonly-config-value:focus-visible
\s
*
\{
/
)
})
packages/shared-types/src/index.ts
View file @
c591100b
...
@@ -22,6 +22,7 @@
...
@@ -22,6 +22,7 @@
configLoad
:
"config:load"
,
configLoad
:
"config:load"
,
configPickWorkspaceDirectory
:
"config:pick-workspace-directory"
,
configPickWorkspaceDirectory
:
"config:pick-workspace-directory"
,
configSave
:
"config:save"
,
configSave
:
"config:save"
,
configRevealSecret
:
"config:reveal-secret"
,
projectsList
:
"projects:list"
,
projectsList
:
"projects:list"
,
projectsSetActive
:
"projects:set-active"
,
projectsSetActive
:
"projects:set-active"
,
projectsResolveIntent
:
"projects:resolve-intent"
,
projectsResolveIntent
:
"projects:resolve-intent"
,
...
@@ -79,6 +80,22 @@ export type SkillDownloadState = "pending" | "downloading" | "ready" | "failed"
...
@@ -79,6 +80,22 @@ export type SkillDownloadState = "pending" | "downloading" | "ready" | "failed"
export
type
ExpertEntryMode
=
"standalone"
|
"home-chat-shortcut"
;
export
type
ExpertEntryMode
=
"standalone"
|
"home-chat-shortcut"
;
export
type
DailyReportDeliveryState
=
"draft"
|
"sent"
|
"failed"
;
export
type
DailyReportDeliveryState
=
"draft"
|
"sent"
|
"failed"
;
export
type
TaskPanelStatus
=
"pending"
|
"running"
|
"completed"
|
"failed"
;
export
type
TaskPanelStatus
=
"pending"
|
"running"
|
"completed"
|
"failed"
;
export
type
ConfigSecretId
=
|
"lobsterKey"
|
"copywritingModelApiKey"
|
"imageModelApiKey"
|
"videoModelApiKey"
|
"digitalHumanVolcAccessKey"
|
"digitalHumanVolcSecretKey"
|
"digitalHumanQiniuAccessKey"
|
"digitalHumanQiniuSecretKey"
|
"videoAnalyzerApiKey"
|
"replicationBriefApiKey"
|
"vectcutApiKey"
|
"xhsFeishuAppId"
|
"xhsFeishuAppSecret"
|
"xhsFeishuAppToken"
|
"xhsFeishuTableId"
;
export
interface
WorkspaceWarmupResult
{
export
interface
WorkspaceWarmupResult
{
accepted
:
boolean
;
accepted
:
boolean
;
...
@@ -629,11 +646,11 @@ export const FIXED_DIGITAL_HUMAN_CONFIG = {
...
@@ -629,11 +646,11 @@ export const FIXED_DIGITAL_HUMAN_CONFIG = {
export
const
FIXED_DOUYIN_RUNTIME_CONFIG
=
{
export
const
FIXED_DOUYIN_RUNTIME_CONFIG
=
{
videoAnalyzer
:
{
videoAnalyzer
:
{
baseUrl
:
"https://ark.cn-beijing.volces.com/api/v3"
,
baseUrl
:
"https://ark.cn-beijing.volces.com/api/v3"
,
modelId
:
""
,
modelId
:
"
doubao-vision
"
,
},
},
replicationBrief
:
{
replicationBrief
:
{
baseUrl
:
"https://dashscope.aliyuncs.com/compatible-mode/v1"
,
baseUrl
:
"https://dashscope.aliyuncs.com/compatible-mode/v1"
,
modelId
:
""
,
modelId
:
"
qwen-max
"
,
},
},
vectcut
:
{
vectcut
:
{
baseUrl
:
"https://open.vectcut.com/cut_jianying"
,
baseUrl
:
"https://open.vectcut.com/cut_jianying"
,
...
@@ -894,6 +911,7 @@ export interface DesktopApi {
...
@@ -894,6 +911,7 @@ export interface DesktopApi {
load
():
Promise
<
AppConfig
>
;
load
():
Promise
<
AppConfig
>
;
pickWorkspaceDirectory
(
currentPath
?:
string
):
Promise
<
string
|
null
>
;
pickWorkspaceDirectory
(
currentPath
?:
string
):
Promise
<
string
|
null
>
;
save
(
input
:
SaveConfigInput
):
Promise
<
AppConfig
>
;
save
(
input
:
SaveConfigInput
):
Promise
<
AppConfig
>
;
revealSecret
(
secretId
:
ConfigSecretId
):
Promise
<
string
|
null
>
;
};
};
projects
:
{
projects
:
{
list
():
Promise
<
ProjectSummary
[]
>
;
list
():
Promise
<
ProjectSummary
[]
>
;
...
...
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