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
c9ab97c3
Commit
c9ab97c3
authored
May 14, 2026
by
edy
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix(ui): scope settings actions
parent
68462773
Changes
8
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
1296 additions
and
498 deletions
+1296
-498
App.tsx
apps/ui/src/App.tsx
+109
-12
SettingsPanels.tsx
apps/ui/src/features/settings/SettingsPanels.tsx
+415
-246
SettingsView.tsx
apps/ui/src/features/settings/SettingsView.tsx
+4
-1
settingsDrafts.ts
apps/ui/src/features/settings/settingsDrafts.ts
+109
-0
useSaveSettings.ts
apps/ui/src/features/settings/useSaveSettings.ts
+155
-68
settings.css
apps/ui/src/styles/settings.css
+305
-171
settingsDrafts.test.ts
apps/ui/test/settingsDrafts.test.ts
+132
-0
settingsPanelsSource.test.ts
apps/ui/test/settingsPanelsSource.test.ts
+67
-0
No files found.
apps/ui/src/App.tsx
View file @
c9ab97c3
...
@@ -71,6 +71,15 @@ import {
...
@@ -71,6 +71,15 @@ import {
}
from
"./features/shell/startupStatus"
;
}
from
"./features/shell/startupStatus"
;
import
{
SettingsPanels
}
from
"./features/settings/SettingsPanels"
;
import
{
SettingsPanels
}
from
"./features/settings/SettingsPanels"
;
import
{
SettingsView
}
from
"./features/settings/SettingsView"
;
import
{
SettingsView
}
from
"./features/settings/SettingsView"
;
import
{
getHasPendingSettingsChange
,
getResetCopywritingSettingsDrafts
,
getResetDigitalHumanSettingsDrafts
,
getResetDouyinRuntimeSettingsDrafts
,
getResetImageSettingsDrafts
,
getResetVideoSettingsDrafts
,
getResetXhsFeishuSettingsDrafts
}
from
"./features/settings/settingsDrafts"
;
import
{
useSaveSettings
}
from
"./features/settings/useSaveSettings"
;
import
{
useSaveSettings
}
from
"./features/settings/useSaveSettings"
;
import
{
useSettingsState
}
from
"./features/settings/useSettingsState"
;
import
{
useSettingsState
}
from
"./features/settings/useSettingsState"
;
import
{
useSmokeActionHandlers
}
from
"./features/smoke/useSmokeActionHandlers"
;
import
{
useSmokeActionHandlers
}
from
"./features/smoke/useSmokeActionHandlers"
;
...
@@ -397,8 +406,7 @@ export default function App() {
...
@@ -397,8 +406,7 @@ export default function App() {
saving
,
saving
,
setSaving
,
setSaving
,
hasPendingLobsterKey
,
hasPendingLobsterKey
,
hasPendingXhsFeishuConfig
,
hasPendingXhsFeishuConfig
hasPendingModelKeys
}
=
useSettingsState
(
config
);
}
=
useSettingsState
(
config
);
const
{
const
{
messageTraces
,
messageTraces
,
...
@@ -411,8 +419,13 @@ export default function App() {
...
@@ -411,8 +419,13 @@ export default function App() {
}
=
useMessageTraces
();
}
=
useMessageTraces
();
const
{
const
{
saveConfig
,
saveConfig
,
saveWorkspaceDirectory
,
saveLobsterKey
,
restoreWorkspaceDirectory
,
saveXhsFeishuConfig
,
saveCopywritingConfig
,
saveImageConfig
,
saveVideoConfig
,
saveDigitalHumanConfig
,
saveDouyinRuntimeConfig
,
pickWorkspaceDirectory
pickWorkspaceDirectory
}
=
useSaveSettings
({
}
=
useSaveSettings
({
config
,
config
,
...
@@ -491,6 +504,76 @@ export default function App() {
...
@@ -491,6 +504,76 @@ export default function App() {
const
savedWorkspacePath
=
config
?.
workspacePath
??
""
;
const
savedWorkspacePath
=
config
?.
workspacePath
??
""
;
const
displayedWorkspacePath
=
workspacePathDraft
.
trim
()
||
savedWorkspacePath
||
ui
.
none
;
const
displayedWorkspacePath
=
workspacePathDraft
.
trim
()
||
savedWorkspacePath
||
ui
.
none
;
const
hasPendingWorkspacePathChange
=
Boolean
(
config
&&
workspacePathDraft
.
trim
()
&&
workspacePathDraft
.
trim
()
!==
savedWorkspacePath
);
const
hasPendingWorkspacePathChange
=
Boolean
(
config
&&
workspacePathDraft
.
trim
()
&&
workspacePathDraft
.
trim
()
!==
savedWorkspacePath
);
const
hasPendingBasicConfig
=
hasPendingLobsterKey
||
hasPendingWorkspacePathChange
;
const
hasPendingCopywritingConfig
=
Boolean
(
copywritingModelApiKeyDraft
.
trim
());
const
hasPendingImageConfig
=
Boolean
(
imageModelApiKeyDraft
.
trim
());
const
hasPendingVideoConfig
=
Boolean
(
videoModelApiKeyDraft
.
trim
());
const
hasPendingDigitalHumanConfig
=
Boolean
(
digitalHumanVolcAccessKeyDraft
.
trim
()
||
digitalHumanVolcSecretKeyDraft
.
trim
()
||
digitalHumanQiniuAccessKeyDraft
.
trim
()
||
digitalHumanQiniuSecretKeyDraft
.
trim
()
);
const
hasPendingDouyinRuntimeConfig
=
Boolean
(
videoAnalyzerBaseUrlDraft
.
trim
()
!==
(
config
?.
douyinRuntimeConfig
.
videoAnalyzer
.
baseUrl
??
""
).
trim
()
||
videoAnalyzerModelIdDraft
.
trim
()
!==
(
config
?.
douyinRuntimeConfig
.
videoAnalyzer
.
modelId
??
""
).
trim
()
||
videoAnalyzerApiKeyDraft
.
trim
()
||
replicationBriefBaseUrlDraft
.
trim
()
!==
(
config
?.
douyinRuntimeConfig
.
replicationBrief
.
baseUrl
??
""
).
trim
()
||
replicationBriefModelIdDraft
.
trim
()
!==
(
config
?.
douyinRuntimeConfig
.
replicationBrief
.
modelId
??
""
).
trim
()
||
replicationBriefApiKeyDraft
.
trim
()
||
vectcutBaseUrlDraft
.
trim
()
!==
(
config
?.
douyinRuntimeConfig
.
vectcut
.
baseUrl
??
""
).
trim
()
||
vectcutFileBaseUrlDraft
.
trim
()
!==
(
config
?.
douyinRuntimeConfig
.
vectcut
.
fileBaseUrl
??
""
).
trim
()
||
vectcutApiKeyDraft
.
trim
()
);
const
hasPendingSettingsChange
=
getHasPendingSettingsChange
({
hasPendingBasicConfig
,
hasPendingXhsFeishuConfig
,
hasPendingCopywritingConfig
,
hasPendingImageConfig
,
hasPendingVideoConfig
,
hasPendingDigitalHumanConfig
,
hasPendingDouyinRuntimeConfig
});
void
hasPendingSettingsChange
;
const
resetXhsFeishuSettingsDrafts
=
useCallback
(()
=>
{
const
drafts
=
getResetXhsFeishuSettingsDrafts
();
setXhsFeishuAppIdDraft
(
drafts
.
xhsFeishuAppId
);
setXhsFeishuAppSecretDraft
(
drafts
.
xhsFeishuAppSecret
);
setXhsFeishuAppTokenDraft
(
drafts
.
xhsFeishuAppToken
);
setXhsFeishuTableIdDraft
(
drafts
.
xhsFeishuTableId
);
},
[]);
const
resetCopywritingSettingsDrafts
=
useCallback
(()
=>
{
setCopywritingModelApiKeyDraft
(
getResetCopywritingSettingsDrafts
().
copywritingModelApiKey
);
},
[]);
const
resetImageSettingsDrafts
=
useCallback
(()
=>
{
setImageModelApiKeyDraft
(
getResetImageSettingsDrafts
().
imageModelApiKey
);
},
[]);
const
resetVideoSettingsDrafts
=
useCallback
(()
=>
{
setVideoModelApiKeyDraft
(
getResetVideoSettingsDrafts
().
videoModelApiKey
);
},
[]);
const
resetDigitalHumanSettingsDrafts
=
useCallback
(()
=>
{
const
drafts
=
getResetDigitalHumanSettingsDrafts
();
setDigitalHumanVolcAccessKeyDraft
(
drafts
.
digitalHumanVolcAccessKey
);
setDigitalHumanVolcSecretKeyDraft
(
drafts
.
digitalHumanVolcSecretKey
);
setDigitalHumanQiniuAccessKeyDraft
(
drafts
.
digitalHumanQiniuAccessKey
);
setDigitalHumanQiniuSecretKeyDraft
(
drafts
.
digitalHumanQiniuSecretKey
);
},
[]);
const
resetDouyinRuntimeSettingsDrafts
=
useCallback
(()
=>
{
if
(
!
config
)
{
return
;
}
const
drafts
=
getResetDouyinRuntimeSettingsDrafts
(
config
);
setVideoAnalyzerBaseUrlDraft
(
drafts
.
videoAnalyzerBaseUrl
);
setVideoAnalyzerModelIdDraft
(
drafts
.
videoAnalyzerModelId
);
setVideoAnalyzerApiKeyDraft
(
drafts
.
videoAnalyzerApiKey
);
setReplicationBriefBaseUrlDraft
(
drafts
.
replicationBriefBaseUrl
);
setReplicationBriefModelIdDraft
(
drafts
.
replicationBriefModelId
);
setReplicationBriefApiKeyDraft
(
drafts
.
replicationBriefApiKey
);
setVectcutBaseUrlDraft
(
drafts
.
vectcutBaseUrl
);
setVectcutFileBaseUrlDraft
(
drafts
.
vectcutFileBaseUrl
);
setVectcutApiKeyDraft
(
drafts
.
vectcutApiKey
);
},
[
config
]);
const
startupProgress
=
getStartupProgress
(
startupPhase
);
const
startupProgress
=
getStartupProgress
(
startupPhase
);
const
startupCurtainStatus
=
getStartupCurtainStatus
(
startupPhase
,
chatLaunchState
);
const
startupCurtainStatus
=
getStartupCurtainStatus
(
startupPhase
,
chatLaunchState
);
const
startupCurtainFootnote
=
getStartupCurtainFootnote
(
chatLaunchState
);
const
startupCurtainFootnote
=
getStartupCurtainFootnote
(
chatLaunchState
);
...
@@ -1230,7 +1313,7 @@ export default function App() {
...
@@ -1230,7 +1313,7 @@ export default function App() {
saving
,
saving
,
bindingLabel
:
ui
.
binding
,
bindingLabel
:
ui
.
binding
,
onLobsterKeyChange
:
setLobsterKeyDraft
,
onLobsterKeyChange
:
setLobsterKeyDraft
,
onSave
:
()
=>
void
save
Config
({
lobsterKey
:
lobsterKeyDraft
}
)
onSave
:
()
=>
void
save
LobsterKey
(
)
},
},
hasExpertProjects
:
Boolean
(
expertPageProjects
.
length
),
hasExpertProjects
:
Boolean
(
expertPageProjects
.
length
),
noExpertsLabel
:
expertsPageCopy
.
noExperts
,
noExpertsLabel
:
expertsPageCopy
.
noExperts
,
...
@@ -1306,12 +1389,16 @@ export default function App() {
...
@@ -1306,12 +1389,16 @@ export default function App() {
config
,
config
,
workspaceApiKeyConfigured
:
Boolean
(
workspace
?.
apiKeyConfigured
),
workspaceApiKeyConfigured
:
Boolean
(
workspace
?.
apiKeyConfigured
),
displayedWorkspacePath
,
displayedWorkspacePath
,
hasPendingWorkspacePathChange
,
saving
,
hasPendingLobsterKey
,
hasPendingLobsterKey
,
hasPendingWorkspacePathChange
,
hasPendingXhsFeishuConfig
,
hasPendingXhsFeishuConfig
,
hasPendingModelKeys
,
hasPendingCopywritingConfig
,
saving
,
hasPendingImageConfig
,
labels
:
{
saving
:
ui
.
saving
,
save
:
ui
.
save
,
export
:
ui
.
export
},
hasPendingVideoConfig
,
hasPendingDigitalHumanConfig
,
hasPendingDouyinRuntimeConfig
,
labels
:
{
export
:
ui
.
export
},
drafts
:
{
drafts
:
{
lobsterKey
:
lobsterKeyDraft
,
lobsterKey
:
lobsterKeyDraft
,
xhsFeishuAppId
:
xhsFeishuAppIdDraft
,
xhsFeishuAppId
:
xhsFeishuAppIdDraft
,
...
@@ -1358,9 +1445,19 @@ export default function App() {
...
@@ -1358,9 +1445,19 @@ export default function App() {
setVectcutFileBaseUrl
:
setVectcutFileBaseUrlDraft
,
setVectcutFileBaseUrl
:
setVectcutFileBaseUrlDraft
,
setVectcutApiKey
:
setVectcutApiKeyDraft
setVectcutApiKey
:
setVectcutApiKeyDraft
},
},
saveConfig
,
onSaveLobsterKey
:
()
=>
void
saveLobsterKey
(),
saveWorkspaceDirectory
,
onSaveXhsFeishuConfig
:
()
=>
void
saveXhsFeishuConfig
(),
restoreWorkspaceDirectory
,
onResetXhsFeishuConfig
:
resetXhsFeishuSettingsDrafts
,
onSaveCopywritingConfig
:
()
=>
void
saveCopywritingConfig
(),
onResetCopywritingConfig
:
resetCopywritingSettingsDrafts
,
onSaveImageConfig
:
()
=>
void
saveImageConfig
(),
onResetImageConfig
:
resetImageSettingsDrafts
,
onSaveVideoConfig
:
()
=>
void
saveVideoConfig
(),
onResetVideoConfig
:
resetVideoSettingsDrafts
,
onSaveDigitalHumanConfig
:
()
=>
void
saveDigitalHumanConfig
(),
onResetDigitalHumanConfig
:
resetDigitalHumanSettingsDrafts
,
onSaveDouyinRuntimeConfig
:
()
=>
void
saveDouyinRuntimeConfig
(),
onResetDouyinRuntimeConfig
:
resetDouyinRuntimeSettingsDrafts
,
pickWorkspaceDirectory
,
pickWorkspaceDirectory
,
exportDiagnostics
exportDiagnostics
}
satisfies
ComponentProps
<
typeof
SettingsPanels
>
;
}
satisfies
ComponentProps
<
typeof
SettingsPanels
>
;
...
...
apps/ui/src/features/settings/SettingsPanels.tsx
View file @
c9ab97c3
import
{
useState
}
from
"react"
import
type
{
AppConfig
}
from
"@qjclaw/shared-types"
import
type
{
AppConfig
}
from
"@qjclaw/shared-types"
import
{
StatusChip
}
from
"../../components/ui/StatusChip
"
import
{
Tabs
,
type
TabItem
}
from
"../../components/ui/Tabs
"
type
SetDraft
=
(
value
:
string
)
=>
void
type
SetDraft
=
(
value
:
string
)
=>
void
type
SettingsActionHandler
=
()
=>
void
|
Promise
<
void
>
interface
SettingsDrafts
{
interface
SettingsDrafts
{
lobsterKey
:
string
lobsterKey
:
string
...
@@ -55,66 +57,163 @@ interface SettingsPanelsProps {
...
@@ -55,66 +57,163 @@ interface SettingsPanelsProps {
config
:
AppConfig
|
null
config
:
AppConfig
|
null
workspaceApiKeyConfigured
:
boolean
workspaceApiKeyConfigured
:
boolean
displayedWorkspacePath
:
string
displayedWorkspacePath
:
string
hasPendingWorkspacePathChange
:
boolean
saving
:
boolean
hasPendingLobsterKey
:
boolean
hasPendingLobsterKey
:
boolean
hasPendingWorkspacePathChange
:
boolean
hasPendingXhsFeishuConfig
:
boolean
hasPendingXhsFeishuConfig
:
boolean
hasPendingModelKeys
:
boolean
hasPendingCopywritingConfig
:
boolean
saving
:
boolean
hasPendingImageConfig
:
boolean
hasPendingVideoConfig
:
boolean
hasPendingDigitalHumanConfig
:
boolean
hasPendingDouyinRuntimeConfig
:
boolean
labels
:
{
labels
:
{
saving
:
string
save
:
string
export
:
string
export
:
string
}
}
drafts
:
SettingsDrafts
drafts
:
SettingsDrafts
setters
:
SettingsDraftSetters
setters
:
SettingsDraftSetters
saveConfig
:
(
options
?:
{
lobsterKey
?:
string
})
=>
void
|
Promise
<
void
>
onSaveLobsterKey
:
SettingsActionHandler
saveWorkspaceDirectory
:
()
=>
void
|
Promise
<
void
>
onSaveXhsFeishuConfig
:
SettingsActionHandler
restoreWorkspaceDirectory
:
()
=>
void
onResetXhsFeishuConfig
:
SettingsActionHandler
onSaveCopywritingConfig
:
SettingsActionHandler
onResetCopywritingConfig
:
SettingsActionHandler
onSaveImageConfig
:
SettingsActionHandler
onResetImageConfig
:
SettingsActionHandler
onSaveVideoConfig
:
SettingsActionHandler
onResetVideoConfig
:
SettingsActionHandler
onSaveDigitalHumanConfig
:
SettingsActionHandler
onResetDigitalHumanConfig
:
SettingsActionHandler
onSaveDouyinRuntimeConfig
:
SettingsActionHandler
onResetDouyinRuntimeConfig
:
SettingsActionHandler
onConfigSourceChange
?:
(
source
:
SettingsConfigSource
)
=>
void
|
Promise
<
void
>
loadCloudConfig
?:
()
=>
void
|
Promise
<
void
>
pickWorkspaceDirectory
:
()
=>
void
|
Promise
<
void
>
pickWorkspaceDirectory
:
()
=>
void
|
Promise
<
void
>
exportDiagnostics
:
()
=>
void
|
Promise
<
void
>
exportDiagnostics
:
()
=>
void
|
Promise
<
void
>
}
}
interface
SectionActions
{
hasPending
:
boolean
onSave
:
SettingsActionHandler
onReset
:
SettingsActionHandler
className
?:
string
}
type
SettingsTabId
=
"basic"
|
"xhsFeishu"
|
"copywriting"
|
"image"
|
"video"
|
"digitalHuman"
|
"douyinRuntime"
type
SettingsConfigSource
=
"cloud"
|
"local"
const
settingsTabs
:
TabItem
[]
=
[
{
id
:
"basic"
,
label
:
"基础配置"
},
{
id
:
"xhsFeishu"
,
label
:
"小红书飞书"
},
{
id
:
"copywriting"
,
label
:
"文案模型"
},
{
id
:
"image"
,
label
:
"生图模型"
},
{
id
:
"video"
,
label
:
"视频模型"
},
{
id
:
"digitalHuman"
,
label
:
"数字人配置"
},
{
id
:
"douyinRuntime"
,
label
:
"抖音运行时"
}
]
export
function
SettingsPanels
({
export
function
SettingsPanels
({
config
,
config
,
workspaceApiKeyConfigured
,
workspaceApiKeyConfigured
,
displayedWorkspacePath
,
displayedWorkspacePath
,
hasPendingWorkspacePathChange
,
saving
,
hasPendingLobsterKey
,
hasPendingLobsterKey
,
hasPendingWorkspacePathChange
,
hasPendingXhsFeishuConfig
,
hasPendingXhsFeishuConfig
,
hasPendingModelKeys
,
hasPendingCopywritingConfig
,
saving
,
hasPendingImageConfig
,
hasPendingVideoConfig
,
hasPendingDigitalHumanConfig
,
hasPendingDouyinRuntimeConfig
,
labels
,
labels
,
drafts
,
drafts
,
setters
,
setters
,
saveConfig
,
onSaveLobsterKey
,
saveWorkspaceDirectory
,
onSaveXhsFeishuConfig
,
restoreWorkspaceDirectory
,
onResetXhsFeishuConfig
,
onSaveCopywritingConfig
,
onResetCopywritingConfig
,
onSaveImageConfig
,
onResetImageConfig
,
onSaveVideoConfig
,
onResetVideoConfig
,
onSaveDigitalHumanConfig
,
onResetDigitalHumanConfig
,
onSaveDouyinRuntimeConfig
,
onResetDouyinRuntimeConfig
,
onConfigSourceChange
,
loadCloudConfig
,
pickWorkspaceDirectory
,
pickWorkspaceDirectory
,
exportDiagnostics
exportDiagnostics
}:
SettingsPanelsProps
)
{
}:
SettingsPanelsProps
)
{
const
xhsFeishuConfigured
=
Boolean
(
const
[
activeTab
,
setActiveTab
]
=
useState
<
SettingsTabId
>
(
"basic"
)
config
?.
xhsFeishuConfig
.
appIdConfigured
const
[
configSource
,
setConfigSource
]
=
useState
<
SettingsConfigSource
>
(
"local"
)
&&
config
?.
xhsFeishuConfig
.
appSecretConfigured
&&
config
?.
xhsFeishuConfig
.
appTokenConfigured
const
handleConfigSourceChange
=
(
source
:
SettingsConfigSource
)
=>
{
&&
config
?.
xhsFeishuConfig
.
tableIdConfigured
setConfigSource
(
source
)
)
void
onConfigSourceChange
?.(
source
)
const
digitalHumanConfigured
=
Boolean
(
if
(
source
===
"cloud"
)
{
config
?.
expertModelConfig
.
digitalHuman
.
volcAccessKeyConfigured
void
loadCloudConfig
?.()
&&
config
?.
expertModelConfig
.
digitalHuman
.
volcSecretKeyConfigured
}
&&
config
?.
expertModelConfig
.
digitalHuman
.
qiniuAccessKeyConfigured
}
&&
config
?.
expertModelConfig
.
digitalHuman
.
qiniuSecretKeyConfigured
const
renderActions
=
({
hasPending
,
onReset
,
onSave
,
className
}:
SectionActions
)
=>
(
<
div
className=
{
[
"settings-actions-row"
,
className
].
filter
(
Boolean
).
join
(
" "
)
}
>
<
button
type=
"button"
className=
"settings-action-button settings-action-button-secondary"
disabled=
{
saving
||
!
hasPending
}
onClick=
{
()
=>
void
onReset
()
}
>
重置
</
button
>
<
button
type=
"button"
className=
"settings-action-button settings-action-button-primary"
disabled=
{
saving
||
!
hasPending
}
onClick=
{
()
=>
void
onSave
()
}
>
{
saving
?
"保存中"
:
"保存"
}
</
button
>
</
div
>
)
)
return
(
return
(
<>
<
div
className=
"settings-tabs-layout"
>
<
div
className=
"settings-tabs-toolbar"
>
<
div
className=
"settings-tabs-row"
>
<
Tabs
items=
{
settingsTabs
}
value=
{
activeTab
}
onValueChange=
{
(
value
)
=>
setActiveTab
(
value
as
SettingsTabId
)
}
ariaLabel=
"设置分组"
className=
"settings-tabs"
/>
</
div
>
<
div
className=
"settings-config-source-toggle"
role=
"group"
aria
-
label=
"配置来源"
>
<
button
type=
"button"
className=
{
"settings-config-source-option settings-config-source-option-cloud"
+
(
configSource
===
"cloud"
?
" active"
:
""
)
}
aria
-
pressed=
{
configSource
===
"cloud"
}
onClick=
{
()
=>
handleConfigSourceChange
(
"cloud"
)
}
>
云端配置
</
button
>
<
button
type=
"button"
className=
{
"settings-config-source-option settings-config-source-option-local"
+
(
configSource
===
"local"
?
" active"
:
""
)
}
aria
-
pressed=
{
configSource
===
"local"
}
onClick=
{
()
=>
handleConfigSourceChange
(
"local"
)
}
>
本地配置
</
button
>
</
div
>
</
div
>
{
activeTab
===
"basic"
?
(
<
section
className=
"panel settings-panel settings-panel-modern settings-panel-basic-config"
>
<
section
className=
"panel settings-panel settings-panel-modern settings-panel-basic-config"
>
<
div
className=
"settings-section-card settings-section-card-compact settings-basic-config-card"
>
<
div
className=
"settings-section-card settings-section-card-compact settings-basic-config-card"
>
<
div
className=
"settings-section-headline settings-section-headline-basic"
>
<
span
className=
"settings-section-kicker"
>
基础配置
</
span
>
</
div
>
<
div
className=
"settings-basic-config-form"
>
<
div
className=
"settings-basic-config-form"
>
<
div
className=
"settings-basic-config-row
"
>
<
div
className=
"settings-basic-config-row settings-basic-config-row-key
"
>
<
label
className=
"settings-input-label
"
>
<
label
className=
"settings-input-label settings-basic-config-field
"
>
<
span
className=
"settings-input-label-text"
>
龙虾密钥
</
span
>
<
span
className=
"settings-input-label-text"
>
龙虾密钥
</
span
>
<
input
<
input
className=
"settings-truncated-input"
className=
"settings-truncated-input"
...
@@ -125,42 +224,60 @@ export function SettingsPanels({
...
@@ -125,42 +224,60 @@ export function SettingsPanels({
onChange=
{
(
event
)
=>
setters
.
setLobsterKey
(
event
.
target
.
value
)
}
onChange=
{
(
event
)
=>
setters
.
setLobsterKey
(
event
.
target
.
value
)
}
/>
/>
</
label
>
</
label
>
<
button
className=
"settings-primary-button settings-inline-save-button"
disabled=
{
saving
||
!
hasPendingLobsterKey
}
onClick=
{
()
=>
void
saveConfig
({
lobsterKey
:
drafts
.
lobsterKey
})
}
>
{
saving
?
labels
.
saving
:
"保存"
}
</
button
>
<
div
className=
"settings-actions-row settings-actions-row-inline-save"
>
<
button
type=
"button"
className=
"settings-action-button settings-action-button-primary settings-inline-save-button"
disabled=
{
saving
||
!
hasPendingLobsterKey
}
onClick=
{
()
=>
void
onSaveLobsterKey
()
}
>
{
saving
?
"保存中"
:
"保存"
}
</
button
>
</
div
>
</
div
>
</
div
>
<
div
className=
"settings-basic-config-row settings-basic-config-row-directory"
>
<
div
className=
"settings-basic-config-row settings-basic-config-row-directory"
>
<
div
className=
"settings-input-label settings-directory-label
"
>
<
div
className=
"settings-input-label settings-directory-label settings-basic-config-field
"
>
<
span
className=
"settings-input-label-text"
>
工作目录
</
span
>
<
span
className=
"settings-input-label-text"
>
工作目录
</
span
>
<
div
className=
"workspace-directory-card settings-basic-directory-card"
>
<
div
className=
"workspace-directory-card settings-basic-directory-card"
>
<
div
className=
"workspace-directory-panel settings-basic-directory-panel
"
>
<
div
className=
"workspace-directory-panel settings-basic-directory-panel settings-readonly-field
"
>
<
strong
className=
"workspace-directory-path"
title=
{
displayedWorkspacePath
}
>
{
displayedWorkspacePath
}
</
strong
>
<
strong
className=
"workspace-directory-path"
title=
{
displayedWorkspacePath
}
>
{
displayedWorkspacePath
}
</
strong
>
</
div
>
</
div
>
{
hasPendingWorkspacePathChange
?
(
{
hasPendingWorkspacePathChange
?
(
<
div
className=
"workspace-directory-draft-row"
>
<
div
className=
"workspace-directory-hint"
>
当前目录已修改。
</
div
>
<
span
className=
"workspace-directory-draft-badge"
>
待保存
</
span
>
)
:
(
<
div
className=
"workspace-directory-inline-actions"
>
<
div
className=
"workspace-directory-hint"
>
导出诊断不会参与保存状态。
</
div
>
<
button
disabled=
{
saving
}
onClick=
{
()
=>
void
saveWorkspaceDirectory
()
}
>
{
saving
?
labels
.
saving
:
labels
.
save
}
</
button
>
)
}
<
button
className=
"secondary workspace-directory-inline-button"
disabled=
{
saving
}
onClick=
{
restoreWorkspaceDirectory
}
>
恢复当前
</
button
>
</
div
>
</
div
>
)
:
null
}
</
div
>
</
div
>
</
div
>
</
div
>
<
div
className=
"button-row settings-actions workspace-directory-actions settings-basic-directory-actions"
>
<
div
className=
"button-row settings-actions-row workspace-directory-actions settings-basic-directory-actions"
>
<
button
className=
"settings-primary-button"
disabled=
{
saving
||
!
config
}
onClick=
{
()
=>
void
pickWorkspaceDirectory
()
}
>
更改目录
</
button
>
<
button
<
button
className=
"secondary"
disabled=
{
saving
}
onClick=
{
()
=>
void
exportDiagnostics
()
}
>
{
labels
.
export
}
</
button
>
type=
"button"
className=
"settings-action-button settings-action-button-secondary"
disabled=
{
saving
||
!
config
}
onClick=
{
()
=>
void
pickWorkspaceDirectory
()
}
>
更改目录
</
button
>
<
button
type=
"button"
className=
"settings-action-button settings-action-button-secondary"
disabled=
{
saving
}
onClick=
{
()
=>
void
exportDiagnostics
()
}
>
{
labels
.
export
}
</
button
>
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
</
section
>
</
section
>
)
:
null
}
{
activeTab
===
"xhsFeishu"
?
(
<
section
className=
"panel settings-panel settings-panel-secondary settings-panel-xhs-feishu"
>
<
section
className=
"panel settings-panel settings-panel-secondary settings-panel-xhs-feishu"
>
<
div
className=
"settings-section-card settings-section-card-compact"
>
<
div
className=
"settings-section-card settings-section-card-compact-body-actions"
>
<
div
className=
"settings-section-headline"
>
<
div
className=
"settings-xhs-feishu-form"
>
<
div
>
<
span
className=
"settings-section-kicker"
>
小红书飞书配置
</
span
>
</
div
>
<
StatusChip
tone=
{
xhsFeishuConfigured
?
"positive"
:
"warning"
}
>
{
xhsFeishuConfigured
?
"已配置"
:
"未配置"
}
</
StatusChip
>
</
div
>
<
div
className=
"settings-field-grid settings-field-grid-xhs-feishu"
>
<
div
className=
"settings-field-grid settings-field-grid-xhs-feishu"
>
<
label
className=
"settings-input-label"
>
<
label
className=
"settings-input-label"
>
<
span
className=
"settings-input-label-text"
>
FEISHU_APP_ID
</
span
>
<
span
className=
"settings-input-label-text"
>
FEISHU_APP_ID
</
span
>
...
@@ -179,24 +296,26 @@ export function SettingsPanels({
...
@@ -179,24 +296,26 @@ export function SettingsPanels({
<
input
type=
"password"
value=
{
drafts
.
xhsFeishuTableId
}
placeholder=
{
config
?.
xhsFeishuConfig
.
tableIdConfigured
?
"留空则保持当前已保存密钥"
:
"请输入 Table ID"
}
onChange=
{
(
event
)
=>
setters
.
setXhsFeishuTableId
(
event
.
target
.
value
)
}
/>
<
input
type=
"password"
value=
{
drafts
.
xhsFeishuTableId
}
placeholder=
{
config
?.
xhsFeishuConfig
.
tableIdConfigured
?
"留空则保持当前已保存密钥"
:
"请输入 Table ID"
}
onChange=
{
(
event
)
=>
setters
.
setXhsFeishuTableId
(
event
.
target
.
value
)
}
/>
</
label
>
</
label
>
</
div
>
</
div
>
<
div
className=
"button-row settings-actions"
>
{
renderActions
({
<
button
className=
"settings-primary-button"
disabled=
{
saving
||
!
hasPendingXhsFeishuConfig
}
onClick=
{
()
=>
void
saveConfig
()
}
>
{
saving
?
labels
.
saving
:
"保存"
}
</
button
>
hasPending
:
hasPendingXhsFeishuConfig
,
onReset
:
()
=>
void
onResetXhsFeishuConfig
(),
onSave
:
()
=>
void
onSaveXhsFeishuConfig
(),
className
:
"settings-xhs-feishu-actions"
})
}
</
div
>
</
div
>
</
div
>
</
div
>
</
section
>
</
section
>
)
:
null
}
{
activeTab
===
"copywriting"
?
(
<
section
className=
"panel settings-panel settings-panel-models"
>
<
section
className=
"panel settings-panel settings-panel-models"
>
<
div
className=
"settings-section-card settings-section-card-models"
>
<
div
className=
"settings-section-card settings-section-card-models"
>
<
div
className=
"settings-section-headline settings-section-headline-minimal"
>
<
div
className=
"model-config-grid model-config-grid-single"
>
<
span
className=
"settings-section-kicker"
>
专家模型配置
</
span
>
</
div
>
<
div
className=
"model-config-grid model-config-grid-four"
>
<
article
className=
"model-config-card model-config-card-copywriting"
>
<
article
className=
"model-config-card model-config-card-copywriting"
>
<
div
className=
"model-config-card-head"
>
<
div
className=
"model-config-card-head"
>
<
div
>
<
div
>
<
strong
>
文案模型
</
strong
>
<
p
>
用于标题、脚本、口播稿与内容润色
</
p
>
<
p
>
用于标题、脚本、口播稿与内容润色
</
p
>
</
div
>
</
div
>
<
StatusChip
tone=
{
config
?.
expertModelConfig
.
copywriting
.
apiKeyConfigured
?
"positive"
:
"warning"
}
>
{
config
?.
expertModelConfig
.
copywriting
.
apiKeyConfigured
?
"已配置"
:
"未配置"
}
</
StatusChip
>
</
div
>
</
div
>
<
div
className=
"model-config-card-body"
>
<
div
className=
"model-config-card-body"
>
<
div
className=
"settings-field-grid single"
>
<
div
className=
"settings-field-grid single"
>
...
@@ -205,14 +324,26 @@ export function SettingsPanels({
...
@@ -205,14 +324,26 @@ export function SettingsPanels({
</
label
>
</
label
>
</
div
>
</
div
>
</
div
>
</
div
>
{
renderActions
({
hasPending
:
hasPendingCopywritingConfig
,
onReset
:
()
=>
void
onResetCopywritingConfig
(),
onSave
:
()
=>
void
onSaveCopywritingConfig
()
})
}
</
article
>
</
article
>
</
div
>
</
div
>
</
section
>
)
:
null
}
{
activeTab
===
"image"
?
(
<
section
className=
"panel settings-panel settings-panel-models"
>
<
div
className=
"settings-section-card settings-section-card-models"
>
<
div
className=
"model-config-grid model-config-grid-single"
>
<
article
className=
"model-config-card model-config-card-image"
>
<
article
className=
"model-config-card model-config-card-image"
>
<
div
className=
"model-config-card-head"
>
<
div
className=
"model-config-card-head"
>
<
div
>
<
div
>
<
strong
>
生图模型
</
strong
>
<
p
>
用于封面草图、画面创意和视觉素材生成
</
p
>
<
p
>
用于封面草图、画面创意和视觉素材生成
</
p
>
</
div
>
</
div
>
<
StatusChip
tone=
{
config
?.
expertModelConfig
.
image
.
apiKeyConfigured
?
"positive"
:
"warning"
}
>
{
config
?.
expertModelConfig
.
image
.
apiKeyConfigured
?
"已配置"
:
"未配置"
}
</
StatusChip
>
</
div
>
</
div
>
<
div
className=
"model-config-card-body"
>
<
div
className=
"model-config-card-body"
>
<
div
className=
"settings-field-grid single"
>
<
div
className=
"settings-field-grid single"
>
...
@@ -221,14 +352,26 @@ export function SettingsPanels({
...
@@ -221,14 +352,26 @@ export function SettingsPanels({
</
label
>
</
label
>
</
div
>
</
div
>
</
div
>
</
div
>
{
renderActions
({
hasPending
:
hasPendingImageConfig
,
onReset
:
()
=>
void
onResetImageConfig
(),
onSave
:
()
=>
void
onSaveImageConfig
()
})
}
</
article
>
</
article
>
</
div
>
</
div
>
</
section
>
)
:
null
}
{
activeTab
===
"video"
?
(
<
section
className=
"panel settings-panel settings-panel-models"
>
<
div
className=
"settings-section-card settings-section-card-models"
>
<
div
className=
"model-config-grid model-config-grid-single"
>
<
article
className=
"model-config-card model-config-card-video"
>
<
article
className=
"model-config-card model-config-card-video"
>
<
div
className=
"model-config-card-head"
>
<
div
className=
"model-config-card-head"
>
<
div
>
<
div
>
<
strong
>
普通视频模型
</
strong
>
<
p
>
用于纯画面视频生成
</
p
>
<
p
>
用于纯画面视频生成
</
p
>
</
div
>
</
div
>
<
StatusChip
tone=
{
config
?.
expertModelConfig
.
video
.
apiKeyConfigured
?
"positive"
:
"warning"
}
>
{
config
?.
expertModelConfig
.
video
.
apiKeyConfigured
?
"已配置"
:
"未配置"
}
</
StatusChip
>
</
div
>
</
div
>
<
div
className=
"model-config-card-body"
>
<
div
className=
"model-config-card-body"
>
<
div
className=
"settings-field-grid single"
>
<
div
className=
"settings-field-grid single"
>
...
@@ -237,14 +380,26 @@ export function SettingsPanels({
...
@@ -237,14 +380,26 @@ export function SettingsPanels({
</
label
>
</
label
>
</
div
>
</
div
>
</
div
>
</
div
>
{
renderActions
({
hasPending
:
hasPendingVideoConfig
,
onReset
:
()
=>
void
onResetVideoConfig
(),
onSave
:
()
=>
void
onSaveVideoConfig
()
})
}
</
article
>
</
article
>
</
div
>
</
div
>
</
section
>
)
:
null
}
{
activeTab
===
"digitalHuman"
?
(
<
section
className=
"panel settings-panel settings-panel-models"
>
<
div
className=
"settings-section-card settings-section-card-models"
>
<
div
className=
"model-config-grid model-config-grid-single"
>
<
article
className=
"model-config-card model-config-card-digital-human"
>
<
article
className=
"model-config-card model-config-card-digital-human"
>
<
div
className=
"model-config-card-head"
>
<
div
className=
"model-config-card-head"
>
<
div
>
<
div
>
<
strong
>
数字人配置
</
strong
>
<
p
>
用于数字人口播视频,火山与七牛的四个 Key
</
p
>
<
p
>
用于数字人口播视频,火山与七牛的四个 Key
</
p
>
</
div
>
</
div
>
<
StatusChip
tone=
{
digitalHumanConfigured
?
"positive"
:
"warning"
}
>
{
digitalHumanConfigured
?
"已配置"
:
"未配置"
}
</
StatusChip
>
</
div
>
</
div
>
<
div
className=
"model-config-card-body model-config-card-body-digital-human"
>
<
div
className=
"model-config-card-body model-config-card-body-digital-human"
>
<
div
className=
"settings-field-grid settings-field-grid-digital-human"
>
<
div
className=
"settings-field-grid settings-field-grid-digital-human"
>
...
@@ -266,14 +421,26 @@ export function SettingsPanels({
...
@@ -266,14 +421,26 @@ export function SettingsPanels({
</
label
>
</
label
>
</
div
>
</
div
>
</
div
>
</
div
>
{
renderActions
({
hasPending
:
hasPendingDigitalHumanConfig
,
onReset
:
()
=>
void
onResetDigitalHumanConfig
(),
onSave
:
()
=>
void
onSaveDigitalHumanConfig
()
})
}
</
article
>
</
article
>
<
article
className=
"model-config-card model-config-card-video-analyzer"
>
</
div
>
</
div
>
</
section
>
)
:
null
}
{
activeTab
===
"douyinRuntime"
?
(
<
section
className=
"panel settings-panel settings-panel-models"
>
<
div
className=
"settings-section-card settings-section-card-models"
>
<
div
className=
"model-config-grid model-config-grid-four model-config-grid-runtime"
>
<
article
className=
"model-config-card model-config-card-video-analyzer"
aria
-
label=
"Video Analyzer"
>
<
div
className=
"model-config-card-head"
>
<
div
className=
"model-config-card-head"
>
<
div
>
<
div
>
<
strong
>
Video Analyzer
</
strong
>
<
p
>
用于抖音样本分析阶段。
</
p
>
<
p
>
用于抖音样本分析阶段。
</
p
>
</
div
>
</
div
>
<
StatusChip
tone=
{
config
?.
douyinRuntimeConfig
.
videoAnalyzer
.
apiKeyConfigured
?
"positive"
:
"warning"
}
>
{
config
?.
douyinRuntimeConfig
.
videoAnalyzer
.
apiKeyConfigured
?
"已配置"
:
"未配置"
}
</
StatusChip
>
</
div
>
</
div
>
<
div
className=
"model-config-card-body"
>
<
div
className=
"model-config-card-body"
>
<
div
className=
"settings-field-grid"
>
<
div
className=
"settings-field-grid"
>
...
@@ -292,13 +459,11 @@ export function SettingsPanels({
...
@@ -292,13 +459,11 @@ export function SettingsPanels({
</
div
>
</
div
>
</
div
>
</
div
>
</
article
>
</
article
>
<
article
className=
"model-config-card model-config-card-replication-b
rief"
>
<
article
className=
"model-config-card model-config-card-replication-brief"
aria
-
label=
"Replication B
rief"
>
<
div
className=
"model-config-card-head"
>
<
div
className=
"model-config-card-head"
>
<
div
>
<
div
>
<
strong
>
Replication Brief
</
strong
>
<
p
>
用于抖音 brief 生成阶段。
</
p
>
<
p
>
用于抖音 brief 生成阶段。
</
p
>
</
div
>
</
div
>
<
StatusChip
tone=
{
config
?.
douyinRuntimeConfig
.
replicationBrief
.
apiKeyConfigured
?
"positive"
:
"warning"
}
>
{
config
?.
douyinRuntimeConfig
.
replicationBrief
.
apiKeyConfigured
?
"已配置"
:
"未配置"
}
</
StatusChip
>
</
div
>
</
div
>
<
div
className=
"model-config-card-body"
>
<
div
className=
"model-config-card-body"
>
<
div
className=
"settings-field-grid"
>
<
div
className=
"settings-field-grid"
>
...
@@ -317,13 +482,11 @@ export function SettingsPanels({
...
@@ -317,13 +482,11 @@ export function SettingsPanels({
</
div
>
</
div
>
</
div
>
</
div
>
</
article
>
</
article
>
<
article
className=
"model-config-card model-config-card-vectc
ut"
>
<
article
className=
"model-config-card model-config-card-vectcut"
aria
-
label=
"VectC
ut"
>
<
div
className=
"model-config-card-head"
>
<
div
className=
"model-config-card-head"
>
<
div
>
<
div
>
<
strong
>
VectCut
</
strong
>
<
p
>
用于抖音后处理剪辑阶段。
</
p
>
<
p
>
用于抖音后处理剪辑阶段。
</
p
>
</
div
>
</
div
>
<
StatusChip
tone=
{
config
?.
douyinRuntimeConfig
.
vectcut
.
apiKeyConfigured
?
"positive"
:
"warning"
}
>
{
config
?.
douyinRuntimeConfig
.
vectcut
.
apiKeyConfigured
?
"已配置"
:
"未配置"
}
</
StatusChip
>
</
div
>
</
div
>
<
div
className=
"model-config-card-body"
>
<
div
className=
"model-config-card-body"
>
<
div
className=
"settings-field-grid"
>
<
div
className=
"settings-field-grid"
>
...
@@ -342,12 +505,18 @@ export function SettingsPanels({
...
@@ -342,12 +505,18 @@ export function SettingsPanels({
</
div
>
</
div
>
</
div
>
</
div
>
</
article
>
</
article
>
<
div
className=
"settings-runtime-actions-panel"
>
{
renderActions
({
hasPending
:
hasPendingDouyinRuntimeConfig
,
onReset
:
()
=>
void
onResetDouyinRuntimeConfig
(),
onSave
:
()
=>
void
onSaveDouyinRuntimeConfig
(),
className
:
"settings-runtime-actions"
})
}
</
div
>
</
div
>
<
div
className=
"button-row settings-actions"
>
<
button
className=
"settings-primary-button"
disabled=
{
saving
||
!
hasPendingModelKeys
}
onClick=
{
()
=>
void
saveConfig
()
}
>
{
saving
?
labels
.
saving
:
"保存模型配置"
}
</
button
>
</
div
>
</
div
>
</
div
>
</
div
>
</
section
>
</
section
>
</>
)
:
null
}
</
div
>
)
)
}
}
apps/ui/src/features/settings/SettingsView.tsx
View file @
c9ab97c3
...
@@ -5,7 +5,10 @@ interface SettingsViewProps {
...
@@ -5,7 +5,10 @@ interface SettingsViewProps {
children
:
ReactNode
children
:
ReactNode
}
}
export
function
SettingsView
({
statusHint
,
children
}:
SettingsViewProps
)
{
export
function
SettingsView
({
statusHint
,
children
}:
SettingsViewProps
)
{
return
(
return
(
<
div
className=
"page-stack settings-page-stack settings-page-shell"
>
<
div
className=
"page-stack settings-page-stack settings-page-shell"
>
{
statusHint
}
{
statusHint
}
...
...
apps/ui/src/features/settings/settingsDrafts.ts
0 → 100644
View file @
c9ab97c3
import
type
{
AppConfig
}
from
"@qjclaw/shared-types"
export
interface
PendingSettingsFlags
{
hasPendingBasicConfig
:
boolean
hasPendingXhsFeishuConfig
:
boolean
hasPendingCopywritingConfig
:
boolean
hasPendingImageConfig
:
boolean
hasPendingVideoConfig
:
boolean
hasPendingDigitalHumanConfig
:
boolean
hasPendingDouyinRuntimeConfig
:
boolean
}
export
interface
BasicResetSettingsDrafts
{
lobsterKey
:
string
workspacePath
:
string
}
export
interface
DigitalHumanResetSettingsDrafts
{
digitalHumanVolcAccessKey
:
string
digitalHumanVolcSecretKey
:
string
digitalHumanQiniuAccessKey
:
string
digitalHumanQiniuSecretKey
:
string
}
export
interface
DouyinRuntimeResetSettingsDrafts
{
videoAnalyzerBaseUrl
:
string
videoAnalyzerModelId
:
string
videoAnalyzerApiKey
:
string
replicationBriefBaseUrl
:
string
replicationBriefModelId
:
string
replicationBriefApiKey
:
string
vectcutBaseUrl
:
string
vectcutFileBaseUrl
:
string
vectcutApiKey
:
string
}
export
interface
XhsFeishuResetSettingsDrafts
{
xhsFeishuAppId
:
string
xhsFeishuAppSecret
:
string
xhsFeishuAppToken
:
string
xhsFeishuTableId
:
string
}
export
function
getHasPendingSettingsChange
(
flags
:
PendingSettingsFlags
)
{
return
flags
.
hasPendingBasicConfig
||
flags
.
hasPendingXhsFeishuConfig
||
flags
.
hasPendingCopywritingConfig
||
flags
.
hasPendingImageConfig
||
flags
.
hasPendingVideoConfig
||
flags
.
hasPendingDigitalHumanConfig
||
flags
.
hasPendingDouyinRuntimeConfig
}
export
function
getResetBasicSettingsDrafts
(
config
:
AppConfig
):
BasicResetSettingsDrafts
{
return
{
lobsterKey
:
""
,
workspacePath
:
config
.
workspacePath
}
}
export
function
getResetCopywritingSettingsDrafts
()
{
return
{
copywritingModelApiKey
:
""
}
}
export
function
getResetImageSettingsDrafts
()
{
return
{
imageModelApiKey
:
""
}
}
export
function
getResetVideoSettingsDrafts
()
{
return
{
videoModelApiKey
:
""
}
}
export
function
getResetDigitalHumanSettingsDrafts
():
DigitalHumanResetSettingsDrafts
{
return
{
digitalHumanVolcAccessKey
:
""
,
digitalHumanVolcSecretKey
:
""
,
digitalHumanQiniuAccessKey
:
""
,
digitalHumanQiniuSecretKey
:
""
}
}
export
function
getResetDouyinRuntimeSettingsDrafts
(
config
:
AppConfig
):
DouyinRuntimeResetSettingsDrafts
{
return
{
videoAnalyzerBaseUrl
:
config
.
douyinRuntimeConfig
.
videoAnalyzer
.
baseUrl
,
videoAnalyzerModelId
:
config
.
douyinRuntimeConfig
.
videoAnalyzer
.
modelId
??
""
,
videoAnalyzerApiKey
:
""
,
replicationBriefBaseUrl
:
config
.
douyinRuntimeConfig
.
replicationBrief
.
baseUrl
,
replicationBriefModelId
:
config
.
douyinRuntimeConfig
.
replicationBrief
.
modelId
??
""
,
replicationBriefApiKey
:
""
,
vectcutBaseUrl
:
config
.
douyinRuntimeConfig
.
vectcut
.
baseUrl
,
vectcutFileBaseUrl
:
config
.
douyinRuntimeConfig
.
vectcut
.
fileBaseUrl
,
vectcutApiKey
:
""
}
}
export
function
getResetXhsFeishuSettingsDrafts
():
XhsFeishuResetSettingsDrafts
{
return
{
xhsFeishuAppId
:
""
,
xhsFeishuAppSecret
:
""
,
xhsFeishuAppToken
:
""
,
xhsFeishuTableId
:
""
}
}
apps/ui/src/features/settings/useSaveSettings.ts
View file @
c9ab97c3
...
@@ -77,67 +77,36 @@ export function useSaveSettings({
...
@@ -77,67 +77,36 @@ export function useSaveSettings({
douyinRuntimeConfig
?:
SaveConfigInput
[
"douyinRuntimeConfig"
]
douyinRuntimeConfig
?:
SaveConfigInput
[
"douyinRuntimeConfig"
]
xhsFeishuConfig
?:
SaveConfigInput
[
"xhsFeishuConfig"
]
xhsFeishuConfig
?:
SaveConfigInput
[
"xhsFeishuConfig"
]
successMessage
?:
string
successMessage
?:
string
resetDrafts
?:
(
savedConfig
:
AppConfig
)
=>
void
})
=>
{
})
=>
{
if
(
!
config
)
{
if
(
!
config
||
saving
)
{
return
return
}
}
const
trimmedLobsterKey
=
options
?.
lobsterKey
?.
trim
()
??
drafts
.
lobsterKey
.
trim
()
const
resolvedWorkspacePath
=
options
?.
workspacePath
?.
trim
()
||
drafts
.
workspacePath
.
trim
()
||
config
.
workspacePath
const
resolvedExpertModelConfig
=
options
?.
expertModelConfig
??
{
image
:
{
apiKey
:
drafts
.
imageModelApiKey
.
trim
()
||
undefined
},
video
:
{
apiKey
:
drafts
.
videoModelApiKey
.
trim
()
||
undefined
},
copywriting
:
{
apiKey
:
drafts
.
copywritingModelApiKey
.
trim
()
||
undefined
},
digitalHuman
:
{
volcAccessKey
:
drafts
.
digitalHumanVolcAccessKey
.
trim
()
||
undefined
,
volcSecretKey
:
drafts
.
digitalHumanVolcSecretKey
.
trim
()
||
undefined
,
qiniuAccessKey
:
drafts
.
digitalHumanQiniuAccessKey
.
trim
()
||
undefined
,
qiniuSecretKey
:
drafts
.
digitalHumanQiniuSecretKey
.
trim
()
||
undefined
}
}
const
resolvedDouyinRuntimeConfig
=
options
?.
douyinRuntimeConfig
??
{
videoAnalyzer
:
{
baseUrl
:
drafts
.
videoAnalyzerBaseUrl
.
trim
()
||
undefined
,
modelId
:
drafts
.
videoAnalyzerModelId
.
trim
()
||
undefined
,
apiKey
:
drafts
.
videoAnalyzerApiKey
.
trim
()
||
undefined
},
replicationBrief
:
{
baseUrl
:
drafts
.
replicationBriefBaseUrl
.
trim
()
||
undefined
,
modelId
:
drafts
.
replicationBriefModelId
.
trim
()
||
undefined
,
apiKey
:
drafts
.
replicationBriefApiKey
.
trim
()
||
undefined
},
vectcut
:
{
baseUrl
:
drafts
.
vectcutBaseUrl
.
trim
()
||
undefined
,
fileBaseUrl
:
drafts
.
vectcutFileBaseUrl
.
trim
()
||
undefined
,
apiKey
:
drafts
.
vectcutApiKey
.
trim
()
||
undefined
}
}
const
resolvedXhsFeishuConfig
=
options
?.
xhsFeishuConfig
??
{
appId
:
drafts
.
xhsFeishuAppId
.
trim
()
||
undefined
,
appSecret
:
drafts
.
xhsFeishuAppSecret
.
trim
()
||
undefined
,
appToken
:
drafts
.
xhsFeishuAppToken
.
trim
()
||
undefined
,
tableId
:
drafts
.
xhsFeishuTableId
.
trim
()
||
undefined
}
const
input
:
SaveConfigInput
=
{
const
input
:
SaveConfigInput
=
{
setupMode
:
config
.
setupMode
,
setupMode
:
config
.
setupMode
,
provider
:
config
.
provider
,
provider
:
config
.
provider
,
baseUrl
:
config
.
baseUrl
,
baseUrl
:
config
.
baseUrl
,
defaultModel
:
config
.
defaultModel
,
defaultModel
:
config
.
defaultModel
,
workspacePath
:
resolvedW
orkspacePath
,
workspacePath
:
options
?.
workspacePath
?.
trim
()
||
config
.
w
orkspacePath
,
gatewayUrl
:
config
.
gatewayUrl
,
gatewayUrl
:
config
.
gatewayUrl
,
cloudApiBaseUrl
:
config
.
cloudApiBaseUrl
,
cloudApiBaseUrl
:
config
.
cloudApiBaseUrl
,
runtimeCloudApiBaseUrl
:
config
.
runtimeCloudApiBaseUrl
.
trim
(),
runtimeCloudApiBaseUrl
:
config
.
runtimeCloudApiBaseUrl
.
trim
(),
runtimeMode
:
"bundled-runtime"
,
runtimeMode
:
"bundled-runtime"
expertModelConfig
:
resolvedExpertModelConfig
,
}
douyinRuntimeConfig
:
resolvedDouyinRuntimeConfig
,
xhsFeishuConfig
:
resolvedXhsFeishuConfig
,
const
trimmedLobsterKey
=
options
?.
lobsterKey
?.
trim
()
...(
trimmedLobsterKey
?
{
apiKey
:
trimmedLobsterKey
}
:
{})
if
(
typeof
options
?.
lobsterKey
===
"string"
&&
trimmedLobsterKey
)
{
input
.
apiKey
=
trimmedLobsterKey
}
if
(
options
?.
expertModelConfig
)
{
input
.
expertModelConfig
=
options
.
expertModelConfig
}
if
(
options
?.
douyinRuntimeConfig
)
{
input
.
douyinRuntimeConfig
=
options
.
douyinRuntimeConfig
}
if
(
options
?.
xhsFeishuConfig
)
{
input
.
xhsFeishuConfig
=
options
.
xhsFeishuConfig
}
}
setters
.
setSaving
(
true
)
setters
.
setSaving
(
true
)
...
@@ -147,22 +116,7 @@ export function useSaveSettings({
...
@@ -147,22 +116,7 @@ export function useSaveSettings({
try
{
try
{
const
savedConfig
=
await
desktopApi
.
config
.
save
(
input
)
const
savedConfig
=
await
desktopApi
.
config
.
save
(
input
)
setters
.
setConfig
(
savedConfig
)
setters
.
setConfig
(
savedConfig
)
setters
.
setWorkspacePathDraft
(
savedConfig
.
workspacePath
)
options
?.
resetDrafts
?.(
savedConfig
)
setters
.
setLobsterKeyDraft
(
""
)
setters
.
setImageModelApiKeyDraft
(
""
)
setters
.
setVideoModelApiKeyDraft
(
""
)
setters
.
setCopywritingModelApiKeyDraft
(
""
)
setters
.
setDigitalHumanVolcAccessKeyDraft
(
""
)
setters
.
setDigitalHumanVolcSecretKeyDraft
(
""
)
setters
.
setDigitalHumanQiniuAccessKeyDraft
(
""
)
setters
.
setDigitalHumanQiniuSecretKeyDraft
(
""
)
setters
.
setVideoAnalyzerApiKeyDraft
(
""
)
setters
.
setReplicationBriefApiKeyDraft
(
""
)
setters
.
setVectcutApiKeyDraft
(
""
)
setters
.
setXhsFeishuAppIdDraft
(
""
)
setters
.
setXhsFeishuAppSecretDraft
(
""
)
setters
.
setXhsFeishuAppTokenDraft
(
""
)
setters
.
setXhsFeishuTableIdDraft
(
""
)
setters
.
setInfoText
(
options
?.
successMessage
??
(
trimmedLobsterKey
?
labels
.
saveSuccessPending
:
labels
.
saveSuccessApplied
))
setters
.
setInfoText
(
options
?.
successMessage
??
(
trimmedLobsterKey
?
labels
.
saveSuccessPending
:
labels
.
saveSuccessApplied
))
void
refresh
(
false
)
void
refresh
(
false
)
}
catch
(
error
)
{
}
catch
(
error
)
{
...
@@ -170,7 +124,16 @@ export function useSaveSettings({
...
@@ -170,7 +124,16 @@ export function useSaveSettings({
}
finally
{
}
finally
{
setters
.
setSaving
(
false
)
setters
.
setSaving
(
false
)
}
}
},
[
config
,
desktopApi
,
drafts
,
labels
.
saveSuccessApplied
,
labels
.
saveSuccessPending
,
normalizeError
,
refresh
,
setters
])
},
[
config
,
desktopApi
,
labels
.
saveSuccessApplied
,
labels
.
saveSuccessPending
,
normalizeError
,
refresh
,
saving
,
setters
])
const
saveLobsterKey
=
useCallback
(
async
()
=>
{
await
saveConfig
({
lobsterKey
:
drafts
.
lobsterKey
,
resetDrafts
:
()
=>
{
setters
.
setLobsterKeyDraft
(
""
)
}
})
},
[
drafts
.
lobsterKey
,
saveConfig
,
setters
])
const
pickWorkspaceDirectory
=
useCallback
(
async
()
=>
{
const
pickWorkspaceDirectory
=
useCallback
(
async
()
=>
{
if
(
!
config
||
saving
)
{
if
(
!
config
||
saving
)
{
...
@@ -204,14 +167,138 @@ export function useSaveSettings({
...
@@ -204,14 +167,138 @@ export function useSaveSettings({
const
saveWorkspaceDirectory
=
useCallback
(
async
()
=>
{
const
saveWorkspaceDirectory
=
useCallback
(
async
()
=>
{
await
saveConfig
({
await
saveConfig
({
workspacePath
:
drafts
.
workspacePath
,
workspacePath
:
drafts
.
workspacePath
,
successMessage
:
labels
.
workspaceSaved
successMessage
:
labels
.
workspaceSaved
,
resetDrafts
:
(
savedConfig
)
=>
{
setters
.
setWorkspacePathDraft
(
savedConfig
.
workspacePath
)
}
})
},
[
drafts
.
workspacePath
,
labels
.
workspaceSaved
,
saveConfig
,
setters
])
const
saveXhsFeishuConfig
=
useCallback
(
async
()
=>
{
await
saveConfig
({
xhsFeishuConfig
:
{
appId
:
drafts
.
xhsFeishuAppId
.
trim
()
||
undefined
,
appSecret
:
drafts
.
xhsFeishuAppSecret
.
trim
()
||
undefined
,
appToken
:
drafts
.
xhsFeishuAppToken
.
trim
()
||
undefined
,
tableId
:
drafts
.
xhsFeishuTableId
.
trim
()
||
undefined
},
resetDrafts
:
()
=>
{
setters
.
setXhsFeishuAppIdDraft
(
""
)
setters
.
setXhsFeishuAppSecretDraft
(
""
)
setters
.
setXhsFeishuAppTokenDraft
(
""
)
setters
.
setXhsFeishuTableIdDraft
(
""
)
}
})
})
},
[
drafts
.
workspacePath
,
labels
.
workspaceSaved
,
saveConfig
])
},
[
drafts
.
xhsFeishuAppId
,
drafts
.
xhsFeishuAppSecret
,
drafts
.
xhsFeishuAppToken
,
drafts
.
xhsFeishuTableId
,
saveConfig
,
setters
])
const
saveCopywritingConfig
=
useCallback
(
async
()
=>
{
await
saveConfig
({
expertModelConfig
:
{
copywriting
:
{
apiKey
:
drafts
.
copywritingModelApiKey
.
trim
()
||
undefined
}
},
resetDrafts
:
()
=>
{
setters
.
setCopywritingModelApiKeyDraft
(
""
)
}
})
},
[
drafts
.
copywritingModelApiKey
,
saveConfig
,
setters
])
const
saveImageConfig
=
useCallback
(
async
()
=>
{
await
saveConfig
({
expertModelConfig
:
{
image
:
{
apiKey
:
drafts
.
imageModelApiKey
.
trim
()
||
undefined
}
},
resetDrafts
:
()
=>
{
setters
.
setImageModelApiKeyDraft
(
""
)
}
})
},
[
drafts
.
imageModelApiKey
,
saveConfig
,
setters
])
const
saveVideoConfig
=
useCallback
(
async
()
=>
{
await
saveConfig
({
expertModelConfig
:
{
video
:
{
apiKey
:
drafts
.
videoModelApiKey
.
trim
()
||
undefined
}
},
resetDrafts
:
()
=>
{
setters
.
setVideoModelApiKeyDraft
(
""
)
}
})
},
[
drafts
.
videoModelApiKey
,
saveConfig
,
setters
])
const
saveDigitalHumanConfig
=
useCallback
(
async
()
=>
{
await
saveConfig
({
expertModelConfig
:
{
digitalHuman
:
{
volcAccessKey
:
drafts
.
digitalHumanVolcAccessKey
.
trim
()
||
undefined
,
volcSecretKey
:
drafts
.
digitalHumanVolcSecretKey
.
trim
()
||
undefined
,
qiniuAccessKey
:
drafts
.
digitalHumanQiniuAccessKey
.
trim
()
||
undefined
,
qiniuSecretKey
:
drafts
.
digitalHumanQiniuSecretKey
.
trim
()
||
undefined
}
},
resetDrafts
:
()
=>
{
setters
.
setDigitalHumanVolcAccessKeyDraft
(
""
)
setters
.
setDigitalHumanVolcSecretKeyDraft
(
""
)
setters
.
setDigitalHumanQiniuAccessKeyDraft
(
""
)
setters
.
setDigitalHumanQiniuSecretKeyDraft
(
""
)
}
})
},
[
drafts
.
digitalHumanQiniuAccessKey
,
drafts
.
digitalHumanQiniuSecretKey
,
drafts
.
digitalHumanVolcAccessKey
,
drafts
.
digitalHumanVolcSecretKey
,
saveConfig
,
setters
])
const
saveDouyinRuntimeConfig
=
useCallback
(
async
()
=>
{
await
saveConfig
({
douyinRuntimeConfig
:
{
videoAnalyzer
:
{
baseUrl
:
drafts
.
videoAnalyzerBaseUrl
.
trim
()
||
undefined
,
modelId
:
drafts
.
videoAnalyzerModelId
.
trim
()
||
undefined
,
apiKey
:
drafts
.
videoAnalyzerApiKey
.
trim
()
||
undefined
},
replicationBrief
:
{
baseUrl
:
drafts
.
replicationBriefBaseUrl
.
trim
()
||
undefined
,
modelId
:
drafts
.
replicationBriefModelId
.
trim
()
||
undefined
,
apiKey
:
drafts
.
replicationBriefApiKey
.
trim
()
||
undefined
},
vectcut
:
{
baseUrl
:
drafts
.
vectcutBaseUrl
.
trim
()
||
undefined
,
fileBaseUrl
:
drafts
.
vectcutFileBaseUrl
.
trim
()
||
undefined
,
apiKey
:
drafts
.
vectcutApiKey
.
trim
()
||
undefined
}
},
resetDrafts
:
()
=>
{
setters
.
setVideoAnalyzerApiKeyDraft
(
""
)
setters
.
setReplicationBriefApiKeyDraft
(
""
)
setters
.
setVectcutApiKeyDraft
(
""
)
}
})
},
[
drafts
.
replicationBriefApiKey
,
drafts
.
replicationBriefBaseUrl
,
drafts
.
replicationBriefModelId
,
drafts
.
vectcutApiKey
,
drafts
.
vectcutBaseUrl
,
drafts
.
vectcutFileBaseUrl
,
drafts
.
videoAnalyzerApiKey
,
drafts
.
videoAnalyzerBaseUrl
,
drafts
.
videoAnalyzerModelId
,
saveConfig
,
setters
])
return
{
return
{
saveConfig
,
saveConfig
,
saveLobsterKey
,
saveWorkspaceDirectory
,
saveWorkspaceDirectory
,
restoreWorkspaceDirectory
,
restoreWorkspaceDirectory
,
saveXhsFeishuConfig
,
saveCopywritingConfig
,
saveImageConfig
,
saveVideoConfig
,
saveDigitalHumanConfig
,
saveDouyinRuntimeConfig
,
pickWorkspaceDirectory
pickWorkspaceDirectory
}
}
}
}
apps/ui/src/styles/settings.css
View file @
c9ab97c3
...
@@ -8,19 +8,96 @@
...
@@ -8,19 +8,96 @@
overflow
:
hidden
;
overflow
:
hidden
;
}
}
.settings-page-shell
{
--settings-control-height
:
44px
;
--settings-control-radius
:
16px
;
--settings-card-radius
:
24px
;
--settings-control-padding-x
:
14px
;
--settings-control-border
:
rgba
(
167
,
183
,
224
,
0.88
);
--settings-control-border-strong
:
rgba
(
129
,
150
,
205
,
0.92
);
--settings-control-background
:
linear-gradient
(
180deg
,
rgba
(
255
,
255
,
255
,
0.98
)
0%
,
rgba
(
245
,
248
,
255
,
0.92
)
100%
);
--settings-focus-outline
:
rgba
(
59
,
130
,
246
,
0.16
);
--settings-focus-shadow
:
0
0
0
4px
rgba
(
96
,
165
,
250
,
0.14
);
--settings-secondary-shadow
:
0
10px
22px
rgba
(
35
,
52
,
82
,
0.08
);
}
.settings-console-grid
{
.settings-console-grid
{
flex
:
1
1
auto
;
flex
:
1
1
auto
;
min-height
:
0
;
min-height
:
0
;
display
:
grid
;
display
:
flex
;
flex-direction
:
column
;
gap
:
10px
;
overflow
:
hidden
;
}
.settings-tabs-layout
{
flex
:
1
1
auto
;
min-height
:
0
;
display
:
flex
;
flex-direction
:
column
;
gap
:
10px
;
gap
:
10px
;
grid-template-columns
:
repeat
(
2
,
minmax
(
0
,
1
fr
));
grid-template-rows
:
minmax
(
236px
,
0.58
fr
)
minmax
(
0
,
1.42
fr
);
grid-template-areas
:
"basic-config xhs-feishu"
"models models"
;
overflow
:
hidden
;
overflow
:
hidden
;
}
}
.settings-tabs-toolbar
{
flex
:
0
0
auto
;
min-width
:
0
;
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
gap
:
12px
;
}
.settings-tabs-row
{
flex
:
1
1
auto
;
min-width
:
0
;
overflow-x
:
auto
;
overflow-y
:
hidden
;
scrollbar-width
:
none
;
}
.settings-tabs-row
::-webkit-scrollbar
{
display
:
none
;
}
.settings-tabs
{
width
:
max-content
;
max-width
:
none
;
}
.settings-config-source-toggle
{
flex
:
0
0
auto
;
display
:
inline-flex
;
align-items
:
center
;
gap
:
2px
;
min-height
:
var
(
--settings-control-height
);
padding
:
4px
;
border-radius
:
999px
;
background
:
linear-gradient
(
180deg
,
rgba
(
229
,
239
,
235
,
0.94
),
rgba
(
216
,
231
,
224
,
0.9
));
box-shadow
:
inset
0
0
0
1px
rgba
(
170
,
191
,
182
,
0.18
);
}
.settings-config-source-option
{
min-width
:
94px
;
min-height
:
calc
(
var
(
--settings-control-height
)
-
8px
);
padding
:
0
14px
;
border
:
0
;
border-radius
:
999px
;
background
:
transparent
;
color
:
#557062
;
font-size
:
12px
;
font-weight
:
700
;
white-space
:
nowrap
;
box-shadow
:
none
;
transition
:
background
160ms
ease
,
color
160ms
ease
,
box-shadow
160ms
ease
;
}
.settings-config-source-option.active
{
background
:
#ffffff
;
color
:
#176046
;
box-shadow
:
0
8px
18px
rgba
(
36
,
84
,
64
,
0.12
),
inset
0
0
0
1px
rgba
(
168
,
189
,
179
,
0.22
);
}
.settings-panel-basic-config
{
.settings-panel-basic-config
{
grid-area
:
basic-config
;
grid-area
:
basic-config
;
}
}
...
@@ -44,10 +121,6 @@
...
@@ -44,10 +121,6 @@
padding
:
8px
;
padding
:
8px
;
}
}
.settings-panel-xhs-feishu
.settings-section-headline
{
align-items
:
center
;
}
.settings-panel-xhs-feishu
.settings-section-kicker
{
.settings-panel-xhs-feishu
.settings-section-kicker
{
min-height
:
22px
;
min-height
:
22px
;
padding-inline
:
9px
;
padding-inline
:
9px
;
...
@@ -68,6 +141,7 @@
...
@@ -68,6 +141,7 @@
}
}
.settings-panel
{
.settings-panel
{
flex
:
1
1
auto
;
min-height
:
0
;
min-height
:
0
;
padding
:
8px
;
padding
:
8px
;
border-radius
:
24px
;
border-radius
:
24px
;
...
@@ -118,11 +192,19 @@
...
@@ -118,11 +192,19 @@
grid-template-rows
:
auto
minmax
(
0
,
1
fr
)
auto
;
grid-template-rows
:
auto
minmax
(
0
,
1
fr
)
auto
;
}
}
.settings-section-card-compact-body-actions
{
grid-template-rows
:
minmax
(
0
,
1
fr
)
auto
;
}
.settings-section-card-models
{
.settings-section-card-models
{
grid-template-rows
:
auto
minmax
(
0
,
1
fr
)
auto
;
grid-template-rows
:
auto
minmax
(
0
,
1
fr
)
auto
;
overflow
:
hidden
;
overflow
:
hidden
;
}
}
.settings-tabs-layout
.settings-section-card-models
{
grid-template-rows
:
minmax
(
0
,
1
fr
);
}
.settings-section-headline
{
.settings-section-headline
{
display
:
flex
;
display
:
flex
;
align-items
:
flex-start
;
align-items
:
flex-start
;
...
@@ -193,69 +275,57 @@
...
@@ -193,69 +275,57 @@
}
}
.settings-basic-config-card
{
.settings-basic-config-card
{
grid-template-rows
:
auto
minmax
(
0
,
1
fr
);
grid-template-rows
:
minmax
(
0
,
1
fr
);
gap
:
8px
;
gap
:
8px
;
}
}
.settings-section-headline-basic
{
align-items
:
flex-start
;
justify-content
:
flex-start
;
}
.settings-basic-config-form
{
.settings-basic-config-form
{
min-height
:
0
;
min-height
:
0
;
display
:
grid
;
display
:
grid
;
align-content
:
center
;
align-content
:
start
;
gap
:
1
0
px
;
gap
:
1
4
px
;
}
}
.settings-basic-config-row
{
.settings-basic-config-row
{
min-width
:
0
;
min-width
:
0
;
display
:
grid
;
display
:
grid
;
grid-template-columns
:
minmax
(
0
,
1
fr
)
146px
;
grid-template-columns
:
minmax
(
0
,
560px
)
auto
;
align-items
:
end
;
align-items
:
end
;
gap
:
10px
;
gap
:
12px
;
}
.settings-basic-config-row-key
{
grid-template-columns
:
minmax
(
0
,
560px
)
auto
;
}
}
.settings-basic-config-row-directory
{
.settings-basic-config-row-directory
{
align-items
:
start
;
align-items
:
end
;
}
.settings-basic-config-field
{
width
:
100%
;
}
}
.settings-basic-directory-card
{
.settings-basic-directory-card
{
gap
:
6px
;
gap
:
0
;
}
}
.settings-basic-directory-actions
{
.settings-basic-directory-actions
{
display
:
grid
;
grid-auto-flow
:
column
;
grid-auto-columns
:
unset
;
grid-template-columns
:
repeat
(
2
,
70px
);
align-self
:
end
;
align-self
:
end
;
justify-self
:
start
;
justify-self
:
start
;
width
:
146px
;
flex-direction
:
column
;
}
align-items
:
stretch
;
.settings-basic-directory-actions
button
,
.settings-inline-save-button
{
height
:
38px
;
min-height
:
38px
;
padding-block
:
0
;
padding-inline
:
8px
;
display
:
inline-flex
;
align-items
:
center
;
justify-content
:
center
;
font-size
:
12px
;
white-space
:
nowrap
;
}
}
.settings-inline-save-button
{
.settings-inline-save-button
,
min-width
:
72px
;
.settings-basic-directory-actions
.settings-action-button
{
justify-self
:
start
;
width
:
96px
;
min-width
:
96px
;
}
}
.settings-input-label
{
.settings-input-label
{
min-width
:
0
;
min-width
:
0
;
gap
:
5
px
;
gap
:
6
px
;
}
}
.settings-input-label-text
{
.settings-input-label-text
{
...
@@ -270,11 +340,16 @@
...
@@ -270,11 +340,16 @@
.settings-input-label
input
,
.settings-input-label
input
,
.settings-input-label
textarea
,
.settings-input-label
textarea
,
.settings-input-label
select
{
.settings-input-label
select
{
min-height
:
38px
;
min-height
:
var
(
--settings-control-height
);
padding
:
9px
12px
;
height
:
var
(
--settings-control-height
);
border-color
:
rgba
(
170
,
187
,
228
,
0.82
);
padding
:
0
var
(
--settings-control-padding-x
);
background
:
rgba
(
252
,
253
,
255
,
0.92
);
border
:
1px
solid
var
(
--settings-control-border
);
border-radius
:
var
(
--settings-control-radius
);
background
:
var
(
--settings-control-background
);
box-shadow
:
inset
0
1px
0
rgba
(
255
,
255
,
255
,
0.92
);
box-shadow
:
inset
0
1px
0
rgba
(
255
,
255
,
255
,
0.92
);
color
:
#163152
;
font-size
:
13px
;
line-height
:
1.35
;
}
}
.settings-truncated-input
{
.settings-truncated-input
{
...
@@ -286,10 +361,10 @@
...
@@ -286,10 +361,10 @@
.settings-input-label
input
:focus-visible
,
.settings-input-label
input
:focus-visible
,
.settings-input-label
textarea
:focus-visible
,
.settings-input-label
textarea
:focus-visible
,
.settings-input-label
select
:focus-visible
{
.settings-input-label
select
:focus-visible
{
outline
:
2px
solid
rgba
(
109
,
124
,
255
,
0.16
);
outline
:
2px
solid
var
(
--settings-focus-outline
);
outline-offset
:
0
;
outline-offset
:
0
;
border-color
:
rgba
(
109
,
124
,
255
,
0.42
);
border-color
:
var
(
--settings-control-border-strong
);
box-shadow
:
0
0
0
4px
rgba
(
94
,
203
,
255
,
0.12
);
box-shadow
:
var
(
--settings-focus-shadow
);
}
}
.model-config-grid
{
.model-config-grid
{
...
@@ -306,12 +381,19 @@
...
@@ -306,12 +381,19 @@
overflow-x
:
hidden
;
overflow-x
:
hidden
;
padding-right
:
8px
;
padding-right
:
8px
;
grid-template-columns
:
repeat
(
2
,
minmax
(
0
,
1
fr
));
grid-template-columns
:
repeat
(
2
,
minmax
(
0
,
1
fr
));
grid-auto-rows
:
minmax
(
188
px
,
auto
);
grid-auto-rows
:
minmax
(
214
px
,
auto
);
align-content
:
start
;
align-content
:
start
;
scrollbar-width
:
thin
;
scrollbar-width
:
thin
;
scrollbar-color
:
rgba
(
125
,
143
,
255
,
0.34
)
transparent
;
scrollbar-color
:
rgba
(
125
,
143
,
255
,
0.34
)
transparent
;
}
}
.model-config-grid-single
{
min-height
:
0
;
height
:
100%
;
grid-template-columns
:
minmax
(
0
,
560px
);
align-content
:
start
;
}
.model-config-grid-four
::-webkit-scrollbar
{
.model-config-grid-four
::-webkit-scrollbar
{
width
:
8px
;
width
:
8px
;
}
}
...
@@ -333,10 +415,10 @@
...
@@ -333,10 +415,10 @@
.model-config-card
{
.model-config-card
{
display
:
grid
;
display
:
grid
;
min-height
:
0
;
min-height
:
0
;
grid-template-rows
:
auto
minmax
(
0
,
1
fr
);
grid-template-rows
:
auto
minmax
(
0
,
1
fr
)
auto
;
gap
:
8px
;
gap
:
8px
;
padding
:
12px
;
padding
:
12px
;
border-radius
:
18px
;
border-radius
:
var
(
--settings-card-radius
)
;
border
:
1px
solid
rgba
(
178
,
194
,
255
,
0.62
);
border
:
1px
solid
rgba
(
178
,
194
,
255
,
0.62
);
background
:
linear-gradient
(
180deg
,
rgba
(
255
,
255
,
255
,
0.94
),
rgba
(
247
,
249
,
255
,
0.84
));
background
:
linear-gradient
(
180deg
,
rgba
(
255
,
255
,
255
,
0.94
),
rgba
(
247
,
249
,
255
,
0.84
));
box-shadow
:
0
10px
22px
rgba
(
109
,
124
,
255
,
0.08
);
box-shadow
:
0
10px
22px
rgba
(
109
,
124
,
255
,
0.08
);
...
@@ -344,16 +426,102 @@
...
@@ -344,16 +426,102 @@
transition
:
transform
180ms
ease
,
border-color
180ms
ease
,
box-shadow
180ms
ease
;
transition
:
transform
180ms
ease
,
border-color
180ms
ease
,
box-shadow
180ms
ease
;
}
}
.settings-actions-row
{
display
:
flex
;
align-items
:
center
;
justify-content
:
flex-end
;
gap
:
8px
;
min-width
:
0
;
flex-wrap
:
wrap
;
margin-top
:
2px
;
}
.settings-actions-row-inline-save
{
justify-self
:
start
;
}
.settings-action-button
{
min-width
:
92px
;
min-height
:
var
(
--settings-control-height
);
height
:
var
(
--settings-control-height
);
padding
:
0
16px
;
border-radius
:
var
(
--settings-control-radius
);
display
:
inline-flex
;
align-items
:
center
;
justify-content
:
center
;
border
:
1px
solid
rgba
(
179
,
194
,
228
,
0.9
);
background
:
rgba
(
255
,
255
,
255
,
0.94
);
color
:
#2b426d
;
font-size
:
13px
;
font-weight
:
700
;
white-space
:
nowrap
;
box-shadow
:
var
(
--settings-secondary-shadow
);
transition
:
background
160ms
ease
,
border-color
160ms
ease
,
box-shadow
160ms
ease
,
color
160ms
ease
,
transform
160ms
ease
;
}
.settings-action-button
:hover:not
(
:disabled
)
{
border-color
:
rgba
(
150
,
171
,
223
,
0.96
);
background
:
rgba
(
255
,
255
,
255
,
0.98
);
box-shadow
:
0
12px
24px
rgba
(
35
,
52
,
82
,
0.1
);
}
.settings-action-button
:focus-visible
{
outline
:
2px
solid
var
(
--settings-focus-outline
);
outline-offset
:
0
;
box-shadow
:
var
(
--settings-focus-shadow
);
}
.settings-action-button
:disabled
{
cursor
:
not-allowed
;
opacity
:
0.56
;
box-shadow
:
none
;
}
.settings-action-button-primary
{
border-color
:
transparent
;
background
:
linear-gradient
(
135deg
,
#3b82f6
,
#1e40af
);
color
:
#ffffff
;
box-shadow
:
0
12px
24px
rgba
(
37
,
99
,
235
,
0.24
);
}
.settings-action-button-primary
:hover:not
(
:disabled
)
{
background
:
linear-gradient
(
135deg
,
#4c8dff
,
#2148bd
);
box-shadow
:
0
14px
28px
rgba
(
37
,
99
,
235
,
0.28
);
}
.settings-action-button-primary
:focus-visible
{
outline-color
:
rgba
(
37
,
99
,
235
,
0.22
);
}
.settings-action-button-secondary
{
background
:
rgba
(
255
,
255
,
255
,
0.94
);
color
:
#2b426d
;
}
.model-config-card
:hover
{
.model-config-card
:hover
{
transform
:
translateY
(
-1px
);
transform
:
translateY
(
-1px
);
border-color
:
rgba
(
109
,
124
,
255
,
0.36
);
border-color
:
rgba
(
109
,
124
,
255
,
0.36
);
box-shadow
:
0
14px
26px
rgba
(
109
,
124
,
255
,
0.12
);
box-shadow
:
0
14px
26px
rgba
(
109
,
124
,
255
,
0.12
);
}
}
.settings-panel-models
.model-config-grid-single
.model-config-card
{
padding
:
0
;
border
:
0
!important
;
border-radius
:
0
;
background
:
transparent
!important
;
box-shadow
:
none
!important
;
}
.settings-panel-models
.model-config-grid-single
.model-config-card
:hover
{
transform
:
none
;
border-color
:
transparent
!important
;
box-shadow
:
none
!important
;
}
.model-config-card-head
{
.model-config-card-head
{
display
:
flex
;
display
:
flex
;
align-items
:
flex-start
;
align-items
:
flex-start
;
justify-content
:
space-between
;
justify-content
:
flex-start
;
gap
:
8px
;
gap
:
8px
;
}
}
...
@@ -405,95 +573,30 @@
...
@@ -405,95 +573,30 @@
}
}
.settings-field-grid-xhs-feishu
.settings-input-label
{
.settings-field-grid-xhs-feishu
.settings-input-label
{
gap
:
3
px
;
gap
:
6
px
;
}
}
.settings-field-grid-xhs-feishu
.settings-input-label
input
{
.settings-field-grid-xhs-feishu
.settings-input-label
input
{
min-height
:
34px
;
min-height
:
var
(
--settings-control-height
);
padding
:
7px
10px
;
height
:
var
(
--settings-control-height
);
}
padding
:
0
var
(
--settings-control-padding-x
);
.settings-panel-xhs-feishu
.settings-actions
{
margin-top
:
0
;
align-items
:
center
;
justify-content
:
flex-end
;
}
.settings-panel-xhs-feishu
.settings-primary-button
{
min-width
:
72px
;
}
}
.settings-panel
.status-chip
{
.settings-xhs-feishu-form
{
min-height
:
24px
;
min-height
:
0
;
padding
:
0
9px
;
display
:
grid
;
background
:
rgba
(
248
,
250
,
255
,
0.88
);
align-content
:
start
;
color
:
#536587
;
box-shadow
:
inset
0
0
0
1px
rgba
(
181
,
195
,
234
,
0.88
);
}
.settings-panel
.status-chip.positive
{
background
:
rgba
(
16
,
185
,
129
,
0.14
);
color
:
#0f7f59
;
box-shadow
:
none
;
}
.settings-panel
.status-chip.warning
{
background
:
rgba
(
245
,
158
,
11
,
0.16
);
color
:
#b46f0a
;
box-shadow
:
none
;
}
.settings-actions
{
gap
:
8px
;
gap
:
8px
;
flex-wrap
:
wrap
;
}
.settings-actions
button
{
min-height
:
38px
;
}
.settings-page-shell
.settings-primary-button
{
min-width
:
100px
;
min-height
:
38px
;
height
:
38px
;
padding
:
0
14px
;
border-radius
:
14px
;
display
:
inline-flex
;
align-items
:
center
;
justify-content
:
center
;
font-size
:
12px
;
white-space
:
nowrap
;
background
:
linear-gradient
(
135deg
,
#3b82f6
,
#1e40af
);
color
:
#ffffff
;
box-shadow
:
0
12px
24px
rgba
(
37
,
99
,
235
,
0.24
);
}
.settings-page-shell
.settings-primary-button
:hover:not
(
:disabled
)
{
background
:
linear-gradient
(
135deg
,
#4c8dff
,
#2148bd
);
box-shadow
:
0
14px
28px
rgba
(
37
,
99
,
235
,
0.28
);
}
.settings-page-shell
.settings-primary-button
:focus-visible
{
outline-color
:
rgba
(
37
,
99
,
235
,
0.22
);
}
}
.settings-page-shell
.settings-inline-save-button
,
.settings-xhs-feishu-actions
{
.settings-page-shell
.settings-panel-xhs-feishu
.settings-primary-button
{
justify-content
:
flex-end
;
min-width
:
72px
;
margin-top
:
0
;
padding-inline
:
12px
;
}
}
.settings-page-shell
.settings-basic-directory-actions
button
{
.settings-page-shell
.settings-basic-directory-actions
.settings-action-
button
{
width
:
100%
;
width
:
100%
;
min-width
:
0
;
min-width
:
96px
;
padding-inline
:
6px
;
}
.settings-page-shell
.settings-actions
button
:not
(
.settings-primary-button
)
:not
(
.secondary
),
.settings-page-shell
.workspace-directory-inline-actions
button
:not
(
.settings-primary-button
)
:not
(
.secondary
)
{
background
:
rgba
(
255
,
255
,
255
,
0.94
);
color
:
#2b426d
;
box-shadow
:
inset
0
0
0
1px
rgba
(
179
,
194
,
228
,
0.9
);
}
}
.settings-panel
.workspace-directory-card
{
.settings-panel
.workspace-directory-card
{
...
@@ -504,10 +607,10 @@
...
@@ -504,10 +607,10 @@
.settings-panel
.workspace-directory-panel
{
.settings-panel
.workspace-directory-panel
{
min-height
:
0
;
min-height
:
0
;
height
:
100%
;
height
:
100%
;
padding
:
12px
14px
;
padding
:
0
var
(
--settings-control-padding-x
)
;
border-radius
:
18px
;
border-radius
:
var
(
--settings-control-radius
)
;
border
-color
:
rgba
(
173
,
192
,
245
,
0.72
);
border
:
1px
solid
var
(
--settings-control-border
);
background
:
linear-gradient
(
180deg
,
rgba
(
255
,
255
,
255
,
0.96
)
0%
,
rgba
(
245
,
248
,
255
,
0.88
)
100%
);
background
:
var
(
--settings-control-background
);
box-shadow
:
inset
0
1px
0
rgba
(
255
,
255
,
255
,
0.9
),
0
8px
18px
rgba
(
109
,
124
,
255
,
0.06
);
box-shadow
:
inset
0
1px
0
rgba
(
255
,
255
,
255
,
0.9
),
0
8px
18px
rgba
(
109
,
124
,
255
,
0.06
);
}
}
...
@@ -527,16 +630,22 @@
...
@@ -527,16 +630,22 @@
.settings-panel-basic-config
.settings-input-label
input
,
.settings-panel-basic-config
.settings-input-label
input
,
.settings-panel-basic-config
.workspace-directory-panel
{
.settings-panel-basic-config
.workspace-directory-panel
{
width
:
100%
;
width
:
100%
;
min-height
:
38px
;
min-height
:
var
(
--settings-control-height
)
;
height
:
auto
;
height
:
var
(
--settings-control-height
)
;
padding
:
9px
12px
;
padding
:
0
var
(
--settings-control-padding-x
)
;
border-radius
:
18px
;
border-radius
:
var
(
--settings-control-radius
)
;
border
:
1px
solid
rgba
(
173
,
192
,
245
,
0.72
);
border
:
1px
solid
var
(
--settings-control-border
);
background
:
linear-gradient
(
180deg
,
rgba
(
255
,
255
,
255
,
0.96
)
0%
,
rgba
(
245
,
248
,
255
,
0.88
)
100%
);
background
:
var
(
--settings-control-background
);
box-shadow
:
inset
0
1px
0
rgba
(
255
,
255
,
255
,
0.9
),
0
8px
18px
rgba
(
109
,
124
,
255
,
0.06
);
box-shadow
:
inset
0
1px
0
rgba
(
255
,
255
,
255
,
0.9
),
0
8px
18px
rgba
(
109
,
124
,
255
,
0.06
);
}
}
.settings-panel-basic-config
.workspace-directory-panel
{
.settings-panel-basic-config
.workspace-directory-panel
{
display
:
flex
;
align-items
:
center
;
}
.settings-readonly-field
{
display
:
flex
;
align-items
:
center
;
align-items
:
center
;
}
}
...
@@ -559,8 +668,8 @@
...
@@ -559,8 +668,8 @@
.workspace-directory-path
{
.workspace-directory-path
{
color
:
#173056
;
color
:
#173056
;
font-size
:
12px
;
font-size
:
12px
;
line-height
:
1.
5
5
;
line-height
:
1.
4
5
;
word-break
:
break-word
;
word-break
:
normal
;
}
}
.workspace-directory-hint
{
.workspace-directory-hint
{
...
@@ -575,18 +684,23 @@
...
@@ -575,18 +684,23 @@
gap
:
6px
;
gap
:
6px
;
}
}
@media
(
max-width
:
1180px
)
{
.model-config-grid-runtime
.settings-runtime-actions-panel
{
.settings-console-grid
{
min-height
:
0
;
grid-template-columns
:
repeat
(
2
,
minmax
(
0
,
1
fr
));
height
:
100%
;
grid-template-rows
:
minmax
(
164px
,
auto
)
minmax
(
0
,
1
fr
);
display
:
flex
;
grid-template-areas
:
align-items
:
flex-end
;
"basic-config xhs-feishu"
justify-content
:
flex-end
;
"models models"
;
}
}
.model-config-grid-runtime
.settings-runtime-actions
{
width
:
100%
;
justify-content
:
flex-end
;
}
@media
(
max-width
:
1180px
)
{
.model-config-grid-four
{
.model-config-grid-four
{
grid-template-columns
:
repeat
(
2
,
minmax
(
0
,
1
fr
));
grid-template-columns
:
repeat
(
2
,
minmax
(
0
,
1
fr
));
grid-auto-rows
:
minmax
(
188
px
,
auto
);
grid-auto-rows
:
minmax
(
214
px
,
auto
);
}
}
}
}
...
@@ -615,12 +729,11 @@
...
@@ -615,12 +729,11 @@
.settings-console-grid
{
.settings-console-grid
{
flex
:
0
0
auto
;
flex
:
0
0
auto
;
height
:
auto
;
height
:
auto
;
grid-template-columns
:
1
fr
;
overflow
:
visible
;
grid-template-rows
:
auto
;
}
grid-template-areas
:
"basic-config"
.settings-tabs-layout
{
"xhs-feishu"
height
:
auto
;
"models"
;
overflow
:
visible
;
overflow
:
visible
;
}
}
...
@@ -639,15 +752,27 @@
...
@@ -639,15 +752,27 @@
grid-template-columns
:
minmax
(
0
,
1
fr
);
grid-template-columns
:
minmax
(
0
,
1
fr
);
}
}
.settings-basic-config-form
{
padding-top
:
0
;
}
.settings-basic-config-field
{
width
:
100%
;
}
.settings-inline-save-button
,
.settings-inline-save-button
,
.settings-actions-row-inline-save
,
.settings-basic-directory-actions
{
.settings-basic-directory-actions
{
justify-self
:
stretch
;
justify-self
:
stretch
;
}
}
.settings-basic-directory-actions
{
.settings-basic-directory-actions
{
grid-auto-flow
:
row
;
width
:
100%
;
grid-auto-columns
:
unset
;
}
grid-template-columns
:
repeat
(
2
,
minmax
(
0
,
1
fr
));
.model-config-grid-runtime
.settings-runtime-actions-panel
{
height
:
auto
;
align-items
:
stretch
;
}
}
.settings-field-grid-digital-human
{
.settings-field-grid-digital-human
{
...
@@ -656,6 +781,19 @@
...
@@ -656,6 +781,19 @@
}
}
@media
(
max-width
:
720px
)
{
@media
(
max-width
:
720px
)
{
.settings-tabs-toolbar
{
align-items
:
stretch
;
gap
:
8px
;
}
.settings-config-source-toggle
{
align-self
:
flex-start
;
}
.settings-config-source-option
{
padding-inline
:
9px
;
}
.plugin-page-topbar
,
.plugin-page-topbar
,
.settings-page-topbar
{
.settings-page-topbar
{
padding
:
0
;
padding
:
0
;
...
@@ -683,16 +821,12 @@
...
@@ -683,16 +821,12 @@
flex-direction
:
column
;
flex-direction
:
column
;
}
}
.settings-panel
.status-chip
{
.settings-actions-row
.settings-action-button
,
align-self
:
flex-start
;
.workspace-directory-actions
.settings-action-button
{
}
.settings-actions
button
,
.workspace-directory-actions
button
{
width
:
100%
;
width
:
100%
;
}
}
.settings-basic-directory-actions
{
.settings-basic-directory-actions
.settings-action-button
{
grid-template-columns
:
1
fr
;
min-width
:
0
;
}
}
}
}
apps/ui/test/settingsDrafts.test.ts
0 → 100644
View file @
c9ab97c3
import
test
from
"node:test"
import
assert
from
"node:assert/strict"
import
type
{
AppConfig
}
from
"@qjclaw/shared-types"
import
{
getHasPendingSettingsChange
,
getResetBasicSettingsDrafts
,
getResetCopywritingSettingsDrafts
,
getResetDigitalHumanSettingsDrafts
,
getResetDouyinRuntimeSettingsDrafts
,
getResetImageSettingsDrafts
,
getResetVideoSettingsDrafts
,
getResetXhsFeishuSettingsDrafts
}
from
"../src/features/settings/settingsDrafts.ts"
const
config
=
{
setupMode
:
"employee-key"
,
provider
:
"openai"
,
baseUrl
:
"https://example.test/v1"
,
apiKeyConfigured
:
true
,
gatewayTokenConfigured
:
false
,
authTokenConfigured
:
false
,
defaultModel
:
"qwen-max"
,
workspacePath
:
"/workspace/current"
,
gatewayUrl
:
"ws://127.0.0.1:8765"
,
cloudApiBaseUrl
:
"https://cloud.example.test"
,
runtimeCloudApiBaseUrl
:
"https://runtime.example.test"
,
runtimeMode
:
"bundled-runtime"
,
expertModelConfig
:
{
image
:
{
baseUrl
:
"https://image.example.test"
,
modelId
:
"image-model"
,
apiKeyConfigured
:
true
},
video
:
{
baseUrl
:
"https://video.example.test"
,
modelId
:
"video-model"
,
apiKeyConfigured
:
true
},
copywriting
:
{
baseUrl
:
"https://copy.example.test"
,
modelId
:
"copy-model"
,
apiKeyConfigured
:
true
},
digitalHuman
:
{
volcRegion
:
"cn-north-1"
,
volcService
:
"cv"
,
volcHost
:
"visual.volcengineapi.com"
,
volcScheme
:
"https"
,
ttsVoice
:
"zh-CN-YunxiNeural"
,
qiniuBucket
:
"bucket"
,
qiniuDomain
:
"https://cdn.example.test"
,
qiniuKeyPrefix
:
"omnihuman"
,
volcAccessKeyConfigured
:
true
,
volcSecretKeyConfigured
:
true
,
qiniuAccessKeyConfigured
:
true
,
qiniuSecretKeyConfigured
:
true
}
},
douyinRuntimeConfig
:
{
videoAnalyzer
:
{
baseUrl
:
"https://video-analyzer.example.test"
,
modelId
:
"va-model"
,
apiKeyConfigured
:
true
},
replicationBrief
:
{
baseUrl
:
"https://brief.example.test"
,
modelId
:
"brief-model"
,
apiKeyConfigured
:
true
},
vectcut
:
{
baseUrl
:
"https://vectcut.example.test"
,
fileBaseUrl
:
"https://files.example.test"
,
apiKeyConfigured
:
true
}
},
xhsFeishuConfig
:
{
appIdConfigured
:
true
,
appSecretConfigured
:
true
,
appTokenConfigured
:
true
,
tableIdConfigured
:
true
}
}
satisfies
AppConfig
test
(
"detects any pending settings change"
,
()
=>
{
assert
.
equal
(
getHasPendingSettingsChange
({
hasPendingBasicConfig
:
false
,
hasPendingXhsFeishuConfig
:
false
,
hasPendingCopywritingConfig
:
false
,
hasPendingImageConfig
:
false
,
hasPendingVideoConfig
:
false
,
hasPendingDigitalHumanConfig
:
false
,
hasPendingDouyinRuntimeConfig
:
false
}),
false
)
assert
.
equal
(
getHasPendingSettingsChange
({
hasPendingBasicConfig
:
false
,
hasPendingXhsFeishuConfig
:
false
,
hasPendingCopywritingConfig
:
false
,
hasPendingImageConfig
:
false
,
hasPendingVideoConfig
:
false
,
hasPendingDigitalHumanConfig
:
false
,
hasPendingDouyinRuntimeConfig
:
true
}),
true
)
})
test
(
"basic reset only affects lobster key and workspace path"
,
()
=>
{
assert
.
deepEqual
(
getResetBasicSettingsDrafts
(
config
),
{
lobsterKey
:
""
,
workspacePath
:
"/workspace/current"
})
})
test
(
"single model resets only clear the current module key"
,
()
=>
{
assert
.
deepEqual
(
getResetCopywritingSettingsDrafts
(),
{
copywritingModelApiKey
:
""
,
})
assert
.
deepEqual
(
getResetImageSettingsDrafts
(),
{
imageModelApiKey
:
""
,
})
assert
.
deepEqual
(
getResetVideoSettingsDrafts
(),
{
videoModelApiKey
:
""
,
})
})
test
(
"xhs feishu reset only clears its own drafts"
,
()
=>
{
assert
.
deepEqual
(
getResetXhsFeishuSettingsDrafts
(),
{
xhsFeishuAppId
:
""
,
xhsFeishuAppSecret
:
""
,
xhsFeishuAppToken
:
""
,
xhsFeishuTableId
:
""
})
})
test
(
"digital human reset only clears its own secrets"
,
()
=>
{
assert
.
deepEqual
(
getResetDigitalHumanSettingsDrafts
(),
{
digitalHumanVolcAccessKey
:
""
,
digitalHumanVolcSecretKey
:
""
,
digitalHumanQiniuAccessKey
:
""
,
digitalHumanQiniuSecretKey
:
""
})
})
test
(
"douyin runtime reset restores saved base urls and model ids while clearing runtime secrets"
,
()
=>
{
assert
.
deepEqual
(
getResetDouyinRuntimeSettingsDrafts
(
config
),
{
videoAnalyzerBaseUrl
:
"https://video-analyzer.example.test"
,
videoAnalyzerModelId
:
"va-model"
,
videoAnalyzerApiKey
:
""
,
replicationBriefBaseUrl
:
"https://brief.example.test"
,
replicationBriefModelId
:
"brief-model"
,
replicationBriefApiKey
:
""
,
vectcutBaseUrl
:
"https://vectcut.example.test"
,
vectcutFileBaseUrl
:
"https://files.example.test"
,
vectcutApiKey
:
""
})
})
apps/ui/test/settingsPanelsSource.test.ts
0 → 100644
View file @
c9ab97c3
import
test
from
"node:test"
import
assert
from
"node:assert/strict"
import
{
readFileSync
}
from
"node:fs"
const
settingsPanelsSource
=
readFileSync
(
new
URL
(
"../src/features/settings/SettingsPanels.tsx"
,
import
.
meta
.
url
),
"utf8"
)
const
settingsStylesSource
=
readFileSync
(
new
URL
(
"../src/styles/settings.css"
,
import
.
meta
.
url
),
"utf8"
)
test
(
"settings panels remove per-module status chips"
,
()
=>
{
assert
.
doesNotMatch
(
settingsPanelsSource
,
/StatusChip/
)
assert
.
doesNotMatch
(
settingsPanelsSource
,
/已配置/
)
assert
.
doesNotMatch
(
settingsPanelsSource
,
/未配置/
)
})
test
(
"settings page buttons use the unified action button classes"
,
()
=>
{
assert
.
match
(
settingsPanelsSource
,
/settings-action-button settings-action-button-secondary/
)
assert
.
match
(
settingsPanelsSource
,
/settings-action-button settings-action-button-primary/
)
assert
.
doesNotMatch
(
settingsPanelsSource
,
/settings-card-action-button/
)
assert
.
doesNotMatch
(
settingsPanelsSource
,
/settings-secondary-button/
)
assert
.
doesNotMatch
(
settingsStylesSource
,
/
\.
settings-panel
\.
status-chip/
)
assert
.
doesNotMatch
(
settingsStylesSource
,
/
\.
settings-card-action-button/
)
assert
.
match
(
settingsStylesSource
,
/
\.
settings-action-button
\b
/
)
assert
.
match
(
settingsStylesSource
,
/
\.
settings-actions-row
\b
/
)
})
test
(
"basic config action buttons share the same width rule"
,
()
=>
{
assert
.
match
(
settingsStylesSource
,
/
\.
settings-inline-save-button,
\s
*
\n\.
settings-basic-directory-actions
\.
settings-action-button
\s
*
\{\s
*
\n\s
*width:
\s
*96px;
\s
*
\n\s
*min-width:
\s
*96px;/m
)
})
test
(
"workspace directory keeps only export diagnostics and change directory actions stacked"
,
()
=>
{
assert
.
doesNotMatch
(
settingsPanelsSource
,
/>
\s
*恢复当前
\s
*</
)
assert
.
doesNotMatch
(
settingsPanelsSource
,
/>
\s
*保存目录
\s
*</
)
assert
.
match
(
settingsPanelsSource
,
/onClick=
\{\(\)
=> void exportDiagnostics
\(\)\}
/
)
assert
.
match
(
settingsPanelsSource
,
/onClick=
\{\(\)
=> void pickWorkspaceDirectory
\(\)\}
/
)
assert
.
match
(
settingsStylesSource
,
/
\.
settings-basic-directory-actions
\s
*
\{[\s\S]
*
?
flex-direction:
\s
*column;
[\s\S]
*
?
align-items:
\s
*stretch;/m
)
})
test
(
"xhs feishu actions are anchored inside the input block"
,
()
=>
{
assert
.
match
(
settingsPanelsSource
,
/className="settings-xhs-feishu-form"/
)
assert
.
match
(
settingsPanelsSource
,
/renderActions
\(\{\s
*hasPending:
\s
*hasPendingXhsFeishuConfig,
\s
*onReset:
\s
*
\(\)\s
*=>
\s
*void onResetXhsFeishuConfig
\(\)
,
\s
*onSave:
\s
*
\(\)\s
*=>
\s
*void onSaveXhsFeishuConfig
\(\)
,
\s
*className:
\s
*"settings-xhs-feishu-actions"/m
)
assert
.
match
(
settingsStylesSource
,
/
\.
settings-xhs-feishu-form
\s
*
\{[\s\S]
*
?
display:
\s
*grid;
[\s\S]
*
?
gap:
\s
*8px;/m
)
assert
.
match
(
settingsStylesSource
,
/
\.
settings-xhs-feishu-actions
\s
*
\{[\s\S]
*
?
justify-content:
\s
*flex-end;
[\s\S]
*
?
margin-top:
\s
*0;/m
)
})
test
(
"settings panel actions are wired per section instead of global handlers"
,
()
=>
{
assert
.
doesNotMatch
(
settingsPanelsSource
,
/
\b
hasPendingSettingsChange
\b
/
)
assert
.
doesNotMatch
(
settingsPanelsSource
,
/
\b
onSaveAll
\b
/
)
assert
.
match
(
settingsPanelsSource
,
/
\b
onSaveLobsterKey
\b
/
)
assert
.
doesNotMatch
(
settingsPanelsSource
,
/
\b
saveWorkspaceDirectory
\b
/
)
assert
.
doesNotMatch
(
settingsPanelsSource
,
/
\b
restoreWorkspaceDirectory
\b
/
)
assert
.
match
(
settingsPanelsSource
,
/
\b
onSaveCopywritingConfig
\b
/
)
assert
.
match
(
settingsPanelsSource
,
/
\b
onSaveImageConfig
\b
/
)
assert
.
match
(
settingsPanelsSource
,
/
\b
onSaveVideoConfig
\b
/
)
assert
.
match
(
settingsPanelsSource
,
/
\b
onSaveDigitalHumanConfig
\b
/
)
assert
.
match
(
settingsPanelsSource
,
/
\b
onSaveDouyinRuntimeConfig
\b
/
)
})
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