Commit 657746b3 authored by AI-甘富林's avatar AI-甘富林

feat(client): add xhs feishu runtime settings

parent 48d00eba
...@@ -536,7 +536,11 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc ...@@ -536,7 +536,11 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc
digitalHumanQiniuSecretKey, digitalHumanQiniuSecretKey,
videoAnalyzerApiKey, videoAnalyzerApiKey,
replicationBriefApiKey, replicationBriefApiKey,
vectCutApiKey vectCutApiKey,
xhsFeishuAppId,
xhsFeishuAppSecret,
xhsFeishuAppToken,
xhsFeishuTableId
] = await Promise.all([ ] = await Promise.all([
secretManager.getApiKey(), secretManager.getApiKey(),
getEffectiveGatewayToken(config), getEffectiveGatewayToken(config),
...@@ -550,7 +554,11 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc ...@@ -550,7 +554,11 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc
secretManager.getDigitalHumanQiniuSecretKey(), secretManager.getDigitalHumanQiniuSecretKey(),
secretManager.getVideoAnalyzerApiKey(), secretManager.getVideoAnalyzerApiKey(),
secretManager.getReplicationBriefApiKey(), secretManager.getReplicationBriefApiKey(),
secretManager.getVectCutApiKey() secretManager.getVectCutApiKey(),
secretManager.getXhsFeishuAppId(),
secretManager.getXhsFeishuAppSecret(),
secretManager.getXhsFeishuAppToken(),
secretManager.getXhsFeishuTableId()
]); ]);
const nextConfig: AppConfig = { const nextConfig: AppConfig = {
...@@ -593,6 +601,12 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc ...@@ -593,6 +601,12 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc
...config.douyinRuntimeConfig.vectcut, ...config.douyinRuntimeConfig.vectcut,
apiKeyConfigured: Boolean(vectCutApiKey) apiKeyConfigured: Boolean(vectCutApiKey)
} }
},
xhsFeishuConfig: {
appIdConfigured: Boolean(xhsFeishuAppId),
appSecretConfigured: Boolean(xhsFeishuAppSecret),
appTokenConfigured: Boolean(xhsFeishuAppToken),
tableIdConfigured: Boolean(xhsFeishuTableId)
} }
}; };
...@@ -608,7 +622,11 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc ...@@ -608,7 +622,11 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc
|| nextConfig.expertModelConfig.digitalHuman.qiniuSecretKeyConfigured !== config.expertModelConfig.digitalHuman.qiniuSecretKeyConfigured || nextConfig.expertModelConfig.digitalHuman.qiniuSecretKeyConfigured !== config.expertModelConfig.digitalHuman.qiniuSecretKeyConfigured
|| nextConfig.douyinRuntimeConfig.videoAnalyzer.apiKeyConfigured !== config.douyinRuntimeConfig.videoAnalyzer.apiKeyConfigured || nextConfig.douyinRuntimeConfig.videoAnalyzer.apiKeyConfigured !== config.douyinRuntimeConfig.videoAnalyzer.apiKeyConfigured
|| nextConfig.douyinRuntimeConfig.replicationBrief.apiKeyConfigured !== config.douyinRuntimeConfig.replicationBrief.apiKeyConfigured || nextConfig.douyinRuntimeConfig.replicationBrief.apiKeyConfigured !== config.douyinRuntimeConfig.replicationBrief.apiKeyConfigured
|| nextConfig.douyinRuntimeConfig.vectcut.apiKeyConfigured !== config.douyinRuntimeConfig.vectcut.apiKeyConfigured; || nextConfig.douyinRuntimeConfig.vectcut.apiKeyConfigured !== config.douyinRuntimeConfig.vectcut.apiKeyConfigured
|| nextConfig.xhsFeishuConfig.appIdConfigured !== config.xhsFeishuConfig.appIdConfigured
|| nextConfig.xhsFeishuConfig.appSecretConfigured !== config.xhsFeishuConfig.appSecretConfigured
|| nextConfig.xhsFeishuConfig.appTokenConfigured !== config.xhsFeishuConfig.appTokenConfigured
|| nextConfig.xhsFeishuConfig.tableIdConfigured !== config.xhsFeishuConfig.tableIdConfigured;
if (secretStateChanged) { if (secretStateChanged) {
await configService.persist({ await configService.persist({
...@@ -636,7 +654,11 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc ...@@ -636,7 +654,11 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc
digitalHumanQiniuSecretKey, digitalHumanQiniuSecretKey,
videoAnalyzerApiKey, videoAnalyzerApiKey,
replicationBriefApiKey, replicationBriefApiKey,
vectCutApiKey vectCutApiKey,
xhsFeishuAppId,
xhsFeishuAppSecret,
xhsFeishuAppToken,
xhsFeishuTableId
] = await Promise.all([ ] = await Promise.all([
secretManager.getCopywritingModelApiKey(), secretManager.getCopywritingModelApiKey(),
secretManager.getImageModelApiKey(), secretManager.getImageModelApiKey(),
...@@ -647,7 +669,11 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc ...@@ -647,7 +669,11 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc
secretManager.getDigitalHumanQiniuSecretKey(), secretManager.getDigitalHumanQiniuSecretKey(),
secretManager.getVideoAnalyzerApiKey(), secretManager.getVideoAnalyzerApiKey(),
secretManager.getReplicationBriefApiKey(), secretManager.getReplicationBriefApiKey(),
secretManager.getVectCutApiKey() secretManager.getVectCutApiKey(),
secretManager.getXhsFeishuAppId(),
secretManager.getXhsFeishuAppSecret(),
secretManager.getXhsFeishuAppToken(),
secretManager.getXhsFeishuTableId()
]); ]);
const runtime = buildProjectModelRuntime(projectId, config, { const runtime = buildProjectModelRuntime(projectId, config, {
copywritingApiKey, copywritingApiKey,
...@@ -659,7 +685,11 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc ...@@ -659,7 +685,11 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc
digitalHumanQiniuSecretKey, digitalHumanQiniuSecretKey,
videoAnalyzerApiKey, videoAnalyzerApiKey,
replicationBriefApiKey, replicationBriefApiKey,
vectCutApiKey vectCutApiKey,
xhsFeishuAppId,
xhsFeishuAppSecret,
xhsFeishuAppToken,
xhsFeishuTableId
}); });
const envFilePath = await materializeProjectModelRuntime(projectRoot, runtime); const envFilePath = await materializeProjectModelRuntime(projectRoot, runtime);
...@@ -1033,6 +1063,18 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc ...@@ -1033,6 +1063,18 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc
if (typeof input.douyinRuntimeConfig?.vectcut?.apiKey === "string") { if (typeof input.douyinRuntimeConfig?.vectcut?.apiKey === "string") {
await secretManager.setVectCutApiKey(input.douyinRuntimeConfig.vectcut.apiKey || undefined); await secretManager.setVectCutApiKey(input.douyinRuntimeConfig.vectcut.apiKey || undefined);
} }
if (typeof input.xhsFeishuConfig?.appId === "string") {
await secretManager.setXhsFeishuAppId(input.xhsFeishuConfig.appId || undefined);
}
if (typeof input.xhsFeishuConfig?.appSecret === "string") {
await secretManager.setXhsFeishuAppSecret(input.xhsFeishuConfig.appSecret || undefined);
}
if (typeof input.xhsFeishuConfig?.appToken === "string") {
await secretManager.setXhsFeishuAppToken(input.xhsFeishuConfig.appToken || undefined);
}
if (typeof input.xhsFeishuConfig?.tableId === "string") {
await secretManager.setXhsFeishuTableId(input.xhsFeishuConfig.tableId || undefined);
}
if ( if (
config.setupMode === "direct-provider" config.setupMode === "direct-provider"
|| previousConfig.setupMode !== config.setupMode || previousConfig.setupMode !== config.setupMode
...@@ -2360,5 +2402,3 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc ...@@ -2360,5 +2402,3 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc
...@@ -12,7 +12,8 @@ import { ...@@ -12,7 +12,8 @@ import {
type ModelEndpointConfig, type ModelEndpointConfig,
type RuntimeModePreference, type RuntimeModePreference,
type SaveConfigInput, type SaveConfigInput,
type SetupMode type SetupMode,
type XhsFeishuConfig
} from "@qjclaw/shared-types"; } from "@qjclaw/shared-types";
export type RuntimeCloudApiBaseUrlSource = "config" | "env" | "default"; export type RuntimeCloudApiBaseUrlSource = "config" | "env" | "default";
...@@ -74,6 +75,7 @@ interface LegacyConfig { ...@@ -74,6 +75,7 @@ interface LegacyConfig {
apiKeyConfigured?: boolean; apiKeyConfigured?: boolean;
}; };
}; };
xhsFeishuConfig?: Partial<XhsFeishuConfig>;
} }
function normalizeGatewayUrl(raw: string): string { function normalizeGatewayUrl(raw: string): string {
...@@ -206,6 +208,15 @@ function createDefaultDouyinRuntimeConfig(): DouyinRuntimeConfig { ...@@ -206,6 +208,15 @@ function createDefaultDouyinRuntimeConfig(): DouyinRuntimeConfig {
}; };
} }
function createDefaultXhsFeishuConfig(): XhsFeishuConfig {
return {
appIdConfigured: false,
appSecretConfigured: false,
appTokenConfigured: false,
tableIdConfigured: false
};
}
function mergeExpertModelConfig( function mergeExpertModelConfig(
current: ExpertModelConfig, current: ExpertModelConfig,
input?: SaveConfigInput["expertModelConfig"] input?: SaveConfigInput["expertModelConfig"]
...@@ -277,6 +288,18 @@ function mergeDouyinRuntimeConfig( ...@@ -277,6 +288,18 @@ function mergeDouyinRuntimeConfig(
}; };
} }
function mergeXhsFeishuConfig(
current: XhsFeishuConfig,
input?: SaveConfigInput["xhsFeishuConfig"]
): XhsFeishuConfig {
return {
appIdConfigured: typeof input?.appId === "string" ? Boolean(input.appId.trim()) : current.appIdConfigured,
appSecretConfigured: typeof input?.appSecret === "string" ? Boolean(input.appSecret.trim()) : current.appSecretConfigured,
appTokenConfigured: typeof input?.appToken === "string" ? Boolean(input.appToken.trim()) : current.appTokenConfigured,
tableIdConfigured: typeof input?.tableId === "string" ? Boolean(input.tableId.trim()) : current.tableIdConfigured
};
}
export function getRuntimeCloudApiTarget(config: Pick<AppConfig, "runtimeCloudApiBaseUrl">): RuntimeCloudApiTarget { export function getRuntimeCloudApiTarget(config: Pick<AppConfig, "runtimeCloudApiBaseUrl">): RuntimeCloudApiTarget {
return resolveRuntimeCloudApiTarget(config.runtimeCloudApiBaseUrl); return resolveRuntimeCloudApiTarget(config.runtimeCloudApiBaseUrl);
} }
...@@ -310,7 +333,8 @@ export class AppConfigService { ...@@ -310,7 +333,8 @@ export class AppConfigService {
runtimeCloudApiBaseUrl: migrateDeprecatedRuntimeCloudApiBaseUrl(input.runtimeCloudApiBaseUrl), runtimeCloudApiBaseUrl: migrateDeprecatedRuntimeCloudApiBaseUrl(input.runtimeCloudApiBaseUrl),
runtimeMode: normalizeRuntimeMode(input.runtimeMode), runtimeMode: normalizeRuntimeMode(input.runtimeMode),
expertModelConfig: mergeExpertModelConfig(current.expertModelConfig, input.expertModelConfig), expertModelConfig: mergeExpertModelConfig(current.expertModelConfig, input.expertModelConfig),
douyinRuntimeConfig: mergeDouyinRuntimeConfig(current.douyinRuntimeConfig, input.douyinRuntimeConfig) douyinRuntimeConfig: mergeDouyinRuntimeConfig(current.douyinRuntimeConfig, input.douyinRuntimeConfig),
xhsFeishuConfig: mergeXhsFeishuConfig(current.xhsFeishuConfig, input.xhsFeishuConfig)
}; };
await this.writeConfig(config); await this.writeConfig(config);
...@@ -347,13 +371,15 @@ export class AppConfigService { ...@@ -347,13 +371,15 @@ export class AppConfigService {
runtimeCloudApiBaseUrl: "", runtimeCloudApiBaseUrl: "",
runtimeMode: normalizeRuntimeMode(process.env.QJCLAW_RUNTIME_MODE), runtimeMode: normalizeRuntimeMode(process.env.QJCLAW_RUNTIME_MODE),
expertModelConfig: createDefaultExpertModelConfig(), expertModelConfig: createDefaultExpertModelConfig(),
douyinRuntimeConfig: createDefaultDouyinRuntimeConfig() douyinRuntimeConfig: createDefaultDouyinRuntimeConfig(),
xhsFeishuConfig: createDefaultXhsFeishuConfig()
}; };
} }
private normalizeConfig(config: LegacyConfig): AppConfig { private normalizeConfig(config: LegacyConfig): AppConfig {
const defaultExpertModelConfig = createDefaultExpertModelConfig(); const defaultExpertModelConfig = createDefaultExpertModelConfig();
const defaultDouyinRuntimeConfig = createDefaultDouyinRuntimeConfig(); const defaultDouyinRuntimeConfig = createDefaultDouyinRuntimeConfig();
const defaultXhsFeishuConfig = createDefaultXhsFeishuConfig();
return { return {
setupMode: normalizeSetupMode(config.setupMode), setupMode: normalizeSetupMode(config.setupMode),
provider: config.provider ?? "openai", provider: config.provider ?? "openai",
...@@ -415,6 +441,12 @@ export class AppConfigService { ...@@ -415,6 +441,12 @@ export class AppConfigService {
: defaultDouyinRuntimeConfig.vectcut.fileBaseUrl, : defaultDouyinRuntimeConfig.vectcut.fileBaseUrl,
apiKeyConfigured: Boolean(config.douyinRuntimeConfig?.vectcut?.apiKeyConfigured) apiKeyConfigured: Boolean(config.douyinRuntimeConfig?.vectcut?.apiKeyConfigured)
} }
},
xhsFeishuConfig: {
appIdConfigured: Boolean(config.xhsFeishuConfig?.appIdConfigured ?? defaultXhsFeishuConfig.appIdConfigured),
appSecretConfigured: Boolean(config.xhsFeishuConfig?.appSecretConfigured ?? defaultXhsFeishuConfig.appSecretConfigured),
appTokenConfigured: Boolean(config.xhsFeishuConfig?.appTokenConfigured ?? defaultXhsFeishuConfig.appTokenConfigured),
tableIdConfigured: Boolean(config.xhsFeishuConfig?.tableIdConfigured ?? defaultXhsFeishuConfig.tableIdConfigured)
} }
}; };
} }
......
...@@ -13,6 +13,10 @@ export interface ProjectModelRuntimeSecrets { ...@@ -13,6 +13,10 @@ export interface ProjectModelRuntimeSecrets {
videoAnalyzerApiKey?: string; videoAnalyzerApiKey?: string;
replicationBriefApiKey?: string; replicationBriefApiKey?: string;
vectCutApiKey?: string; vectCutApiKey?: string;
xhsFeishuAppId?: string;
xhsFeishuAppSecret?: string;
xhsFeishuAppToken?: string;
xhsFeishuTableId?: string;
} }
export interface ProjectModelRuntimePreparation { export interface ProjectModelRuntimePreparation {
...@@ -107,7 +111,7 @@ export function validateProjectModelRuntime( ...@@ -107,7 +111,7 @@ export function validateProjectModelRuntime(
const copywritingBaseUrl = normalizeOpenAiCompatibleBaseUrl(config.expertModelConfig.copywriting.baseUrl); const copywritingBaseUrl = normalizeOpenAiCompatibleBaseUrl(config.expertModelConfig.copywriting.baseUrl);
const copywritingModelId = normalizeValue(config.expertModelConfig.copywriting.modelId); const copywritingModelId = normalizeValue(config.expertModelConfig.copywriting.modelId);
const copywritingApiKey = normalizeValue(secrets.copywritingApiKey); const copywritingApiKey = normalizeValue(secrets.copywritingApiKey);
const imageBaseUrl = withoutTrailingSlash(config.expertModelConfig.image.baseUrl); const imageBaseUrl = normalizeArkBaseUrl(config.expertModelConfig.image.baseUrl);
const imageModelId = normalizeValue(config.expertModelConfig.image.modelId); const imageModelId = normalizeValue(config.expertModelConfig.image.modelId);
const imageApiKey = normalizeValue(secrets.imageApiKey); const imageApiKey = normalizeValue(secrets.imageApiKey);
...@@ -219,7 +223,7 @@ export function buildProjectModelRuntime( ...@@ -219,7 +223,7 @@ export function buildProjectModelRuntime(
const copywritingBaseUrl = normalizeOpenAiCompatibleBaseUrl(config.expertModelConfig.copywriting.baseUrl); const copywritingBaseUrl = normalizeOpenAiCompatibleBaseUrl(config.expertModelConfig.copywriting.baseUrl);
const copywritingModelId = normalizeValue(config.expertModelConfig.copywriting.modelId); const copywritingModelId = normalizeValue(config.expertModelConfig.copywriting.modelId);
const copywritingApiKey = normalizeValue(secrets.copywritingApiKey); const copywritingApiKey = normalizeValue(secrets.copywritingApiKey);
const imageBaseUrl = withoutTrailingSlash(config.expertModelConfig.image.baseUrl); const imageBaseUrl = normalizeArkBaseUrl(config.expertModelConfig.image.baseUrl);
const imageModelId = normalizeValue(config.expertModelConfig.image.modelId); const imageModelId = normalizeValue(config.expertModelConfig.image.modelId);
const imageApiKey = normalizeValue(secrets.imageApiKey); const imageApiKey = normalizeValue(secrets.imageApiKey);
const videoBaseUrl = withoutTrailingSlash(config.expertModelConfig.video.baseUrl); const videoBaseUrl = withoutTrailingSlash(config.expertModelConfig.video.baseUrl);
...@@ -238,6 +242,10 @@ export function buildProjectModelRuntime( ...@@ -238,6 +242,10 @@ export function buildProjectModelRuntime(
const vectCutBaseUrl = withoutTrailingSlash(config.douyinRuntimeConfig.vectcut.baseUrl); const vectCutBaseUrl = withoutTrailingSlash(config.douyinRuntimeConfig.vectcut.baseUrl);
const vectCutFileBaseUrl = withoutTrailingSlash(config.douyinRuntimeConfig.vectcut.fileBaseUrl); const vectCutFileBaseUrl = withoutTrailingSlash(config.douyinRuntimeConfig.vectcut.fileBaseUrl);
const vectCutApiKey = normalizeValue(secrets.vectCutApiKey); const vectCutApiKey = normalizeValue(secrets.vectCutApiKey);
const xhsFeishuAppId = normalizeValue(secrets.xhsFeishuAppId);
const xhsFeishuAppSecret = normalizeValue(secrets.xhsFeishuAppSecret);
const xhsFeishuAppToken = normalizeValue(secrets.xhsFeishuAppToken);
const xhsFeishuTableId = normalizeValue(secrets.xhsFeishuTableId);
const env: Record<string, string> = {}; const env: Record<string, string> = {};
if (XHS_PROJECT_IDS.has(normalizedProjectId)) { if (XHS_PROJECT_IDS.has(normalizedProjectId)) {
...@@ -263,6 +271,18 @@ export function buildProjectModelRuntime( ...@@ -263,6 +271,18 @@ export function buildProjectModelRuntime(
if (imageModelId) { if (imageModelId) {
env.XHS_IMAGE_MODEL = imageModelId; env.XHS_IMAGE_MODEL = imageModelId;
} }
if (xhsFeishuAppId) {
env.FEISHU_APP_ID = xhsFeishuAppId;
}
if (xhsFeishuAppSecret) {
env.FEISHU_APP_SECRET = xhsFeishuAppSecret;
}
if (xhsFeishuAppToken) {
env.FEISHU_APP_TOKEN = xhsFeishuAppToken;
}
if (xhsFeishuTableId) {
env.FEISHU_TABLE_ID = xhsFeishuTableId;
}
} }
if (DOUYIN_PROJECT_IDS.has(normalizedProjectId)) { if (DOUYIN_PROJECT_IDS.has(normalizedProjectId)) {
......
...@@ -17,6 +17,10 @@ interface SecretRecord { ...@@ -17,6 +17,10 @@ interface SecretRecord {
videoAnalyzerApiKey?: string; videoAnalyzerApiKey?: string;
replicationBriefApiKey?: string; replicationBriefApiKey?: string;
vectCutApiKey?: string; vectCutApiKey?: string;
xhsFeishuAppId?: string;
xhsFeishuAppSecret?: string;
xhsFeishuAppToken?: string;
xhsFeishuTableId?: string;
} }
interface SecretAccessor { interface SecretAccessor {
...@@ -38,7 +42,11 @@ type SecretName = ...@@ -38,7 +42,11 @@ type SecretName =
| "digitalHumanQiniuSecretKey" | "digitalHumanQiniuSecretKey"
| "videoAnalyzerApiKey" | "videoAnalyzerApiKey"
| "replicationBriefApiKey" | "replicationBriefApiKey"
| "vectCutApiKey"; | "vectCutApiKey"
| "xhsFeishuAppId"
| "xhsFeishuAppSecret"
| "xhsFeishuAppToken"
| "xhsFeishuTableId";
type KeytarModule = typeof import("keytar"); type KeytarModule = typeof import("keytar");
const KEYTAR_SERVICE = "QianjiangClaw"; const KEYTAR_SERVICE = "QianjiangClaw";
...@@ -57,7 +65,11 @@ const KEYTAR_ACCOUNT_MAP: Record<SecretName, string> = { ...@@ -57,7 +65,11 @@ const KEYTAR_ACCOUNT_MAP: Record<SecretName, string> = {
digitalHumanQiniuSecretKey: "digital-human-qiniu-secret-key", digitalHumanQiniuSecretKey: "digital-human-qiniu-secret-key",
videoAnalyzerApiKey: "douyin-video-analyzer-api-key", videoAnalyzerApiKey: "douyin-video-analyzer-api-key",
replicationBriefApiKey: "douyin-replication-brief-api-key", replicationBriefApiKey: "douyin-replication-brief-api-key",
vectCutApiKey: "douyin-vectcut-api-key" vectCutApiKey: "douyin-vectcut-api-key",
xhsFeishuAppId: "xhs-feishu-app-id",
xhsFeishuAppSecret: "xhs-feishu-app-secret",
xhsFeishuAppToken: "xhs-feishu-app-token",
xhsFeishuTableId: "xhs-feishu-table-id"
}; };
class FileSecretStore implements SecretAccessor { class FileSecretStore implements SecretAccessor {
...@@ -277,6 +289,38 @@ export class SecretManager { ...@@ -277,6 +289,38 @@ export class SecretManager {
return this.store.get("vectCutApiKey"); return this.store.get("vectCutApiKey");
} }
async setXhsFeishuAppId(value?: string): Promise<void> {
await this.store.set("xhsFeishuAppId", value);
}
async getXhsFeishuAppId(): Promise<string | undefined> {
return this.store.get("xhsFeishuAppId");
}
async setXhsFeishuAppSecret(value?: string): Promise<void> {
await this.store.set("xhsFeishuAppSecret", value);
}
async getXhsFeishuAppSecret(): Promise<string | undefined> {
return this.store.get("xhsFeishuAppSecret");
}
async setXhsFeishuAppToken(value?: string): Promise<void> {
await this.store.set("xhsFeishuAppToken", value);
}
async getXhsFeishuAppToken(): Promise<string | undefined> {
return this.store.get("xhsFeishuAppToken");
}
async setXhsFeishuTableId(value?: string): Promise<void> {
await this.store.set("xhsFeishuTableId", value);
}
async getXhsFeishuTableId(): Promise<string | undefined> {
return this.store.get("xhsFeishuTableId");
}
private async tryLoadKeytar(): Promise<KeytarModule | null> { private async tryLoadKeytar(): Promise<KeytarModule | null> {
try { try {
const imported = await import("keytar"); const imported = await import("keytar");
...@@ -301,7 +345,11 @@ export class SecretManager { ...@@ -301,7 +345,11 @@ export class SecretManager {
"digitalHumanQiniuSecretKey", "digitalHumanQiniuSecretKey",
"videoAnalyzerApiKey", "videoAnalyzerApiKey",
"replicationBriefApiKey", "replicationBriefApiKey",
"vectCutApiKey" "vectCutApiKey",
"xhsFeishuAppId",
"xhsFeishuAppSecret",
"xhsFeishuAppToken",
"xhsFeishuTableId"
] as const) { ] as const) {
const existing = await this.store.get(secretName); const existing = await this.store.get(secretName);
if (existing) { if (existing) {
......
...@@ -1253,10 +1253,12 @@ declare global { ...@@ -1253,10 +1253,12 @@ declare global {
workspacePath?: string; workspacePath?: string;
expertModelConfig?: SaveConfigInput["expertModelConfig"]; expertModelConfig?: SaveConfigInput["expertModelConfig"];
douyinRuntimeConfig?: SaveConfigInput["douyinRuntimeConfig"]; douyinRuntimeConfig?: SaveConfigInput["douyinRuntimeConfig"];
xhsFeishuConfig?: SaveConfigInput["xhsFeishuConfig"];
}): Promise<{ }): Promise<{
workspacePath: string; workspacePath: string;
expertModelConfig: AppConfig["expertModelConfig"]; expertModelConfig: AppConfig["expertModelConfig"];
douyinRuntimeConfig: AppConfig["douyinRuntimeConfig"]; douyinRuntimeConfig: AppConfig["douyinRuntimeConfig"];
xhsFeishuConfig: AppConfig["xhsFeishuConfig"];
apiKeyConfigured: boolean; apiKeyConfigured: boolean;
}>; }>;
createProjectSession(projectId?: string, title?: string): Promise<SessionSummary>; createProjectSession(projectId?: string, title?: string): Promise<SessionSummary>;
...@@ -2093,6 +2095,10 @@ export default function App() { ...@@ -2093,6 +2095,10 @@ export default function App() {
const [vectcutBaseUrlDraft, setVectcutBaseUrlDraft] = useState(""); const [vectcutBaseUrlDraft, setVectcutBaseUrlDraft] = useState("");
const [vectcutFileBaseUrlDraft, setVectcutFileBaseUrlDraft] = useState(""); const [vectcutFileBaseUrlDraft, setVectcutFileBaseUrlDraft] = useState("");
const [vectcutApiKeyDraft, setVectcutApiKeyDraft] = useState(""); const [vectcutApiKeyDraft, setVectcutApiKeyDraft] = useState("");
const [xhsFeishuAppIdDraft, setXhsFeishuAppIdDraft] = useState("");
const [xhsFeishuAppSecretDraft, setXhsFeishuAppSecretDraft] = useState("");
const [xhsFeishuAppTokenDraft, setXhsFeishuAppTokenDraft] = useState("");
const [xhsFeishuTableIdDraft, setXhsFeishuTableIdDraft] = useState("");
const [refreshing, setRefreshing] = useState(false); const [refreshing, setRefreshing] = useState(false);
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const [sendPhase, setSendPhase] = useState<SendPhase>("idle"); const [sendPhase, setSendPhase] = useState<SendPhase>("idle");
...@@ -2222,6 +2228,12 @@ export default function App() { ...@@ -2222,6 +2228,12 @@ export default function App() {
const sending = sendPhase !== "idle"; const sending = sendPhase !== "idle";
const canSend = isBound && hasConversationProject && (prompt.trim().length > 0 || composerAttachments.length > 0) && !sending && !saving; const canSend = isBound && hasConversationProject && (prompt.trim().length > 0 || composerAttachments.length > 0) && !sending && !saving;
const hasPendingLobsterKey = lobsterKeyDraft.trim().length > 0; const hasPendingLobsterKey = lobsterKeyDraft.trim().length > 0;
const hasPendingXhsFeishuConfig = Boolean(
xhsFeishuAppIdDraft.trim()
|| xhsFeishuAppSecretDraft.trim()
|| xhsFeishuAppTokenDraft.trim()
|| xhsFeishuTableIdDraft.trim()
);
const hasPendingModelKeys = Boolean( const hasPendingModelKeys = Boolean(
imageModelApiKeyDraft.trim() imageModelApiKeyDraft.trim()
|| videoModelApiKeyDraft.trim() || videoModelApiKeyDraft.trim()
...@@ -2949,10 +2961,12 @@ export default function App() { ...@@ -2949,10 +2961,12 @@ export default function App() {
workspacePath?: string; workspacePath?: string;
expertModelConfig?: SaveConfigInput["expertModelConfig"]; expertModelConfig?: SaveConfigInput["expertModelConfig"];
douyinRuntimeConfig?: SaveConfigInput["douyinRuntimeConfig"]; douyinRuntimeConfig?: SaveConfigInput["douyinRuntimeConfig"];
xhsFeishuConfig?: SaveConfigInput["xhsFeishuConfig"];
}) => { }) => {
const nextWorkspacePath = options?.workspacePath; const nextWorkspacePath = options?.workspacePath;
const nextExpertModelConfig = options?.expertModelConfig; const nextExpertModelConfig = options?.expertModelConfig;
const nextDouyinRuntimeConfig = options?.douyinRuntimeConfig; const nextDouyinRuntimeConfig = options?.douyinRuntimeConfig;
const nextXhsFeishuConfig = options?.xhsFeishuConfig;
if (typeof nextWorkspacePath === "string") { if (typeof nextWorkspacePath === "string") {
setWorkspacePathDraft(nextWorkspacePath); setWorkspacePathDraft(nextWorkspacePath);
} }
...@@ -2986,6 +3000,12 @@ export default function App() { ...@@ -2986,6 +3000,12 @@ export default function App() {
setVectcutFileBaseUrlDraft(nextDouyinRuntimeConfig.vectcut.fileBaseUrl ?? ""); setVectcutFileBaseUrlDraft(nextDouyinRuntimeConfig.vectcut.fileBaseUrl ?? "");
setVectcutApiKeyDraft(nextDouyinRuntimeConfig.vectcut.apiKey ?? ""); setVectcutApiKeyDraft(nextDouyinRuntimeConfig.vectcut.apiKey ?? "");
} }
if (nextXhsFeishuConfig) {
setXhsFeishuAppIdDraft(nextXhsFeishuConfig.appId ?? "");
setXhsFeishuAppSecretDraft(nextXhsFeishuConfig.appSecret ?? "");
setXhsFeishuAppTokenDraft(nextXhsFeishuConfig.appToken ?? "");
setXhsFeishuTableIdDraft(nextXhsFeishuConfig.tableId ?? "");
}
if (typeof options?.lobsterKey === "string") { if (typeof options?.lobsterKey === "string") {
setLobsterKeyDraft(options.lobsterKey); setLobsterKeyDraft(options.lobsterKey);
} }
...@@ -2994,7 +3014,8 @@ export default function App() { ...@@ -2994,7 +3014,8 @@ export default function App() {
lobsterKey: options?.lobsterKey, lobsterKey: options?.lobsterKey,
workspacePath: nextWorkspacePath, workspacePath: nextWorkspacePath,
expertModelConfig: nextExpertModelConfig, expertModelConfig: nextExpertModelConfig,
douyinRuntimeConfig: nextDouyinRuntimeConfig douyinRuntimeConfig: nextDouyinRuntimeConfig,
xhsFeishuConfig: nextXhsFeishuConfig
}); });
const latestConfig = await desktopApi.config.load(); const latestConfig = await desktopApi.config.load();
...@@ -3010,12 +3031,17 @@ export default function App() { ...@@ -3010,12 +3031,17 @@ export default function App() {
setVideoAnalyzerApiKeyDraft(""); setVideoAnalyzerApiKeyDraft("");
setReplicationBriefApiKeyDraft(""); setReplicationBriefApiKeyDraft("");
setVectcutApiKeyDraft(""); setVectcutApiKeyDraft("");
setXhsFeishuAppIdDraft("");
setXhsFeishuAppSecretDraft("");
setXhsFeishuAppTokenDraft("");
setXhsFeishuTableIdDraft("");
await waitForSmokeConfigPublish(latestConfig.expertModelConfig); await waitForSmokeConfigPublish(latestConfig.expertModelConfig);
return { return {
workspacePath: latestConfig.workspacePath, workspacePath: latestConfig.workspacePath,
expertModelConfig: latestConfig.expertModelConfig, expertModelConfig: latestConfig.expertModelConfig,
douyinRuntimeConfig: latestConfig.douyinRuntimeConfig, douyinRuntimeConfig: latestConfig.douyinRuntimeConfig,
xhsFeishuConfig: latestConfig.xhsFeishuConfig,
apiKeyConfigured: latestConfig.apiKeyConfigured apiKeyConfigured: latestConfig.apiKeyConfigured
}; };
}, },
...@@ -3517,6 +3543,7 @@ export default function App() { ...@@ -3517,6 +3543,7 @@ export default function App() {
workspacePath?: string; workspacePath?: string;
expertModelConfig?: SaveConfigInput["expertModelConfig"]; expertModelConfig?: SaveConfigInput["expertModelConfig"];
douyinRuntimeConfig?: SaveConfigInput["douyinRuntimeConfig"]; douyinRuntimeConfig?: SaveConfigInput["douyinRuntimeConfig"];
xhsFeishuConfig?: SaveConfigInput["xhsFeishuConfig"];
successMessage?: string; successMessage?: string;
}) { }) {
if (!config) { if (!config) {
...@@ -3559,6 +3586,12 @@ export default function App() { ...@@ -3559,6 +3586,12 @@ export default function App() {
apiKey: vectcutApiKeyDraft.trim() || undefined apiKey: vectcutApiKeyDraft.trim() || undefined
} }
}; };
const resolvedXhsFeishuConfig = options?.xhsFeishuConfig ?? {
appId: xhsFeishuAppIdDraft.trim() || undefined,
appSecret: xhsFeishuAppSecretDraft.trim() || undefined,
appToken: xhsFeishuAppTokenDraft.trim() || undefined,
tableId: xhsFeishuTableIdDraft.trim() || undefined
};
const input: SaveConfigInput = { const input: SaveConfigInput = {
setupMode: config.setupMode, setupMode: config.setupMode,
provider: config.provider, provider: config.provider,
...@@ -3571,6 +3604,7 @@ export default function App() { ...@@ -3571,6 +3604,7 @@ export default function App() {
runtimeMode: "bundled-runtime", runtimeMode: "bundled-runtime",
expertModelConfig: resolvedExpertModelConfig, expertModelConfig: resolvedExpertModelConfig,
douyinRuntimeConfig: resolvedDouyinRuntimeConfig, douyinRuntimeConfig: resolvedDouyinRuntimeConfig,
xhsFeishuConfig: resolvedXhsFeishuConfig,
...(trimmedLobsterKey ? { apiKey: trimmedLobsterKey } : {}) ...(trimmedLobsterKey ? { apiKey: trimmedLobsterKey } : {})
}; };
...@@ -3593,6 +3627,10 @@ export default function App() { ...@@ -3593,6 +3627,10 @@ export default function App() {
setVideoAnalyzerApiKeyDraft(""); setVideoAnalyzerApiKeyDraft("");
setReplicationBriefApiKeyDraft(""); setReplicationBriefApiKeyDraft("");
setVectcutApiKeyDraft(""); setVectcutApiKeyDraft("");
setXhsFeishuAppIdDraft("");
setXhsFeishuAppSecretDraft("");
setXhsFeishuAppTokenDraft("");
setXhsFeishuTableIdDraft("");
setInfoText(options?.successMessage ?? (trimmedLobsterKey ? ui.saveSuccessPending : ui.saveSuccessApplied)); setInfoText(options?.successMessage ?? (trimmedLobsterKey ? ui.saveSuccessPending : ui.saveSuccessApplied));
void refresh(false); void refresh(false);
} catch (error) { } catch (error) {
...@@ -4913,8 +4951,8 @@ export default function App() { ...@@ -4913,8 +4951,8 @@ export default function App() {
) : null} ) : null}
</div> </div>
</aside> </aside>
<div className={"main-shell" + (isConversationView ? " conversation-main-layout" : "")}> <div className={"main-shell" + (isConversationView ? " conversation-main-layout" : "") + (viewMode === "settings" ? " settings-main-shell" : "")}>
{!isConversationView && viewMode !== "knowledge" ? ( {viewMode === "plugins" ? (
<div className="page-topbar"> <div className="page-topbar">
<div className="page-copy"> <div className="page-copy">
<h2>{pageTitle}</h2> <h2>{pageTitle}</h2>
...@@ -5079,6 +5117,51 @@ export default function App() { ...@@ -5079,6 +5117,51 @@ export default function App() {
</div> </div>
</div> </div>
</section> </section>
<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-headline">
<div>
<span className="settings-section-kicker">小红书飞书配置</span>
</div>
<StatusChip tone={
config?.xhsFeishuConfig.appIdConfigured
&& config?.xhsFeishuConfig.appSecretConfigured
&& config?.xhsFeishuConfig.appTokenConfigured
&& config?.xhsFeishuConfig.tableIdConfigured
? "positive"
: "warning"
}>
{config?.xhsFeishuConfig.appIdConfigured
&& config?.xhsFeishuConfig.appSecretConfigured
&& config?.xhsFeishuConfig.appTokenConfigured
&& config?.xhsFeishuConfig.tableIdConfigured
? "已配置"
: "未配置"}
</StatusChip>
</div>
<div className="settings-field-grid settings-field-grid-xhs-feishu">
<label className="settings-input-label">
<span className="settings-input-label-text">FEISHU_APP_ID</span>
<input type="password" value={xhsFeishuAppIdDraft} placeholder={config?.xhsFeishuConfig.appIdConfigured ? "留空则保持当前已保存密钥" : "请输入 App ID"} onChange={(event) => setXhsFeishuAppIdDraft(event.target.value)} />
</label>
<label className="settings-input-label">
<span className="settings-input-label-text">FEISHU_APP_SECRET</span>
<input type="password" value={xhsFeishuAppSecretDraft} placeholder={config?.xhsFeishuConfig.appSecretConfigured ? "留空则保持当前已保存密钥" : "请输入 App Secret"} onChange={(event) => setXhsFeishuAppSecretDraft(event.target.value)} />
</label>
<label className="settings-input-label">
<span className="settings-input-label-text">FEISHU_APP_TOKEN</span>
<input type="password" value={xhsFeishuAppTokenDraft} placeholder={config?.xhsFeishuConfig.appTokenConfigured ? "留空则保持当前已保存密钥" : "请输入 App Token"} onChange={(event) => setXhsFeishuAppTokenDraft(event.target.value)} />
</label>
<label className="settings-input-label">
<span className="settings-input-label-text">FEISHU_TABLE_ID</span>
<input type="password" value={xhsFeishuTableIdDraft} placeholder={config?.xhsFeishuConfig.tableIdConfigured ? "留空则保持当前已保存密钥" : "请输入 Table ID"} onChange={(event) => setXhsFeishuTableIdDraft(event.target.value)} />
</label>
</div>
<div className="button-row settings-actions">
<button className="settings-primary-button" disabled={saving || !hasPendingXhsFeishuConfig} onClick={() => void saveConfig()}>{saving ? ui.saving : "保存"}</button>
</div>
</div>
</section>
<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="settings-section-headline settings-section-headline-minimal">
......
...@@ -3251,6 +3251,11 @@ button.secondary { ...@@ -3251,6 +3251,11 @@ button.secondary {
gap: 14px; gap: 14px;
} }
.main-shell.settings-main-shell {
padding-top: 8px;
gap: 0;
}
.page-topbar { .page-topbar {
align-items: center; align-items: center;
min-height: 72px; min-height: 72px;
...@@ -4108,11 +4113,11 @@ button.secondary { ...@@ -4108,11 +4113,11 @@ button.secondary {
min-height: 0; min-height: 0;
display: grid; display: grid;
gap: 10px; gap: 10px;
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(3, minmax(0, 1fr));
grid-template-rows: minmax(164px, 0.43fr) minmax(0, 1.57fr); grid-template-rows: minmax(236px, 0.58fr) minmax(0, 1.42fr);
grid-template-areas: grid-template-areas:
"connection diagnostics" "connection diagnostics xhs-feishu"
"models models"; "models models models";
overflow: hidden; overflow: hidden;
} }
...@@ -4124,6 +4129,26 @@ button.secondary { ...@@ -4124,6 +4129,26 @@ button.secondary {
grid-area: diagnostics; grid-area: diagnostics;
} }
.settings-panel-xhs-feishu {
grid-area: xhs-feishu;
}
.settings-panel-xhs-feishu .settings-section-card-compact {
grid-template-rows: auto auto auto;
align-content: start;
gap: 7px;
padding: 8px;
}
.settings-panel-xhs-feishu .settings-section-headline {
align-items: center;
}
.settings-panel-xhs-feishu .settings-section-kicker {
min-height: 22px;
padding-inline: 9px;
}
.settings-panel-models { .settings-panel-models {
grid-area: models; grid-area: models;
} }
...@@ -4403,6 +4428,32 @@ button.secondary { ...@@ -4403,6 +4428,32 @@ button.secondary {
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
} }
.settings-field-grid-xhs-feishu {
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 7px;
align-content: start;
}
.settings-field-grid-xhs-feishu .settings-input-label {
gap: 3px;
}
.settings-field-grid-xhs-feishu .settings-input-label input {
min-height: 34px;
padding: 7px 10px;
}
.settings-panel-xhs-feishu .settings-actions {
margin-top: 0;
align-items: center;
}
.settings-panel-xhs-feishu .settings-primary-button {
min-height: 30px;
padding: 6px 14px;
font-size: 12px;
}
.settings-panel .status-chip { .settings-panel .status-chip {
min-height: 24px; min-height: 24px;
padding: 0 9px; padding: 0 9px;
...@@ -4500,6 +4551,15 @@ button.secondary { ...@@ -4500,6 +4551,15 @@ button.secondary {
} }
@media (max-width: 1180px) { @media (max-width: 1180px) {
.settings-console-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
grid-template-rows: minmax(164px, auto) minmax(210px, auto) minmax(0, 1fr);
grid-template-areas:
"connection diagnostics"
"xhs-feishu xhs-feishu"
"models models";
}
.model-config-grid-four { .model-config-grid-four {
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
grid-auto-rows: minmax(188px, auto); grid-auto-rows: minmax(188px, auto);
...@@ -4536,6 +4596,7 @@ button.secondary { ...@@ -4536,6 +4596,7 @@ button.secondary {
grid-template-areas: grid-template-areas:
"connection" "connection"
"diagnostics" "diagnostics"
"xhs-feishu"
"models"; "models";
overflow: visible; overflow: visible;
} }
......
...@@ -589,6 +589,13 @@ export interface DouyinRuntimeConfig { ...@@ -589,6 +589,13 @@ export interface DouyinRuntimeConfig {
vectcut: VectCutModelConfig; vectcut: VectCutModelConfig;
} }
export interface XhsFeishuConfig {
appIdConfigured: boolean;
appSecretConfigured: boolean;
appTokenConfigured: boolean;
tableIdConfigured: boolean;
}
export const FIXED_EXPERT_MODEL_ENDPOINTS = { export const FIXED_EXPERT_MODEL_ENDPOINTS = {
copywriting: { copywriting: {
baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1", baseUrl: "https://dashscope.aliyuncs.com/compatible-mode/v1",
...@@ -645,6 +652,7 @@ export interface AppConfig { ...@@ -645,6 +652,7 @@ export interface AppConfig {
runtimeMode: RuntimeModePreference; runtimeMode: RuntimeModePreference;
expertModelConfig: ExpertModelConfig; expertModelConfig: ExpertModelConfig;
douyinRuntimeConfig: DouyinRuntimeConfig; douyinRuntimeConfig: DouyinRuntimeConfig;
xhsFeishuConfig: XhsFeishuConfig;
} }
export interface DiagnosticsExportResult { export interface DiagnosticsExportResult {
...@@ -678,6 +686,13 @@ export interface VectCutModelInput { ...@@ -678,6 +686,13 @@ export interface VectCutModelInput {
apiKey?: string; apiKey?: string;
} }
export interface XhsFeishuConfigInput {
appId?: string;
appSecret?: string;
appToken?: string;
tableId?: string;
}
export interface SaveConfigInput { export interface SaveConfigInput {
setupMode: SetupMode; setupMode: SetupMode;
provider: string; provider: string;
...@@ -702,6 +717,7 @@ export interface SaveConfigInput { ...@@ -702,6 +717,7 @@ export interface SaveConfigInput {
replicationBrief?: DouyinTextModelInput; replicationBrief?: DouyinTextModelInput;
vectcut?: VectCutModelInput; vectcut?: VectCutModelInput;
}; };
xhsFeishuConfig?: XhsFeishuConfigInput;
} }
export interface AuthSessionSummary { export interface AuthSessionSummary {
......
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