Commit c591100b authored by edy's avatar edy

feat(settings): improve secret reveal and config copying

parent c9ab97c3
...@@ -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(),
......
...@@ -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),
......
...@@ -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>;
......
...@@ -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,
......
...@@ -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;
......
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\)/)
})
...@@ -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, /\bonSaveDigitalHumanConfig\b/) assert.match(settingsPanelsSource, /\bonSaveDigitalHumanConfig\b/)
assert.match(settingsPanelsSource, /\bonSaveDouyinRuntimeConfig\b/) assert.match(settingsPanelsSource, /\bonSaveDouyinRuntimeConfig\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*\{/)
})
...@@ -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[]>;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment