Commit 9d7f1c0a authored by edy's avatar edy

feat(ui): port Windows settings panel to Mac, add Feishu Mobile config

- Add Feishu Mobile Config feature across all layers (types, secrets, config, IPC, UI)
- Replace Mac glassmorphism design with Windows flat design (settings.css)
- Improve secret reveal UX: loading spinner, error handling, touched state tracking
- Change basic tab layout from single compact card to 3-card bento layout
- Add Modal component (apps/ui/src/components/ui/Modal.tsx)
- Start editable sections in edit mode by default
- Add workspace directory save/restore buttons to SettingsPanels
- Add reveal error display in settings toolbar
- Clean up dead CSS classes from old basic tab layout
- Fix modal panel to use flat design matching settings style
Co-Authored-By: 's avatarClaude Opus 4.8 <noreply@anthropic.com>
parent 703f338b
...@@ -609,7 +609,9 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc ...@@ -609,7 +609,9 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc
xhsFeishuAppId, xhsFeishuAppId,
xhsFeishuAppSecret, xhsFeishuAppSecret,
xhsFeishuAppToken, xhsFeishuAppToken,
xhsFeishuTableId xhsFeishuTableId,
feishuMobileAppId,
feishuMobileAppSecret
] = await Promise.all([ ] = await Promise.all([
secretManager.getApiKey(), secretManager.getApiKey(),
getEffectiveGatewayToken(config), getEffectiveGatewayToken(config),
...@@ -627,7 +629,9 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc ...@@ -627,7 +629,9 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc
secretManager.getXhsFeishuAppId(), secretManager.getXhsFeishuAppId(),
secretManager.getXhsFeishuAppSecret(), secretManager.getXhsFeishuAppSecret(),
secretManager.getXhsFeishuAppToken(), secretManager.getXhsFeishuAppToken(),
secretManager.getXhsFeishuTableId() secretManager.getXhsFeishuTableId(),
secretManager.getFeishuMobileAppId(),
secretManager.getFeishuMobileAppSecret()
]); ]);
const nextConfig: AppConfig = { const nextConfig: AppConfig = {
...@@ -676,6 +680,10 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc ...@@ -676,6 +680,10 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc
appSecretConfigured: Boolean(xhsFeishuAppSecret), appSecretConfigured: Boolean(xhsFeishuAppSecret),
appTokenConfigured: Boolean(xhsFeishuAppToken), appTokenConfigured: Boolean(xhsFeishuAppToken),
tableIdConfigured: Boolean(xhsFeishuTableId) tableIdConfigured: Boolean(xhsFeishuTableId)
},
feishuMobileConfig: {
appIdConfigured: Boolean(feishuMobileAppId),
appSecretConfigured: Boolean(feishuMobileAppSecret)
} }
}; };
...@@ -695,7 +703,9 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc ...@@ -695,7 +703,9 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc
|| nextConfig.xhsFeishuConfig.appIdConfigured !== config.xhsFeishuConfig.appIdConfigured || nextConfig.xhsFeishuConfig.appIdConfigured !== config.xhsFeishuConfig.appIdConfigured
|| nextConfig.xhsFeishuConfig.appSecretConfigured !== config.xhsFeishuConfig.appSecretConfigured || nextConfig.xhsFeishuConfig.appSecretConfigured !== config.xhsFeishuConfig.appSecretConfigured
|| nextConfig.xhsFeishuConfig.appTokenConfigured !== config.xhsFeishuConfig.appTokenConfigured || nextConfig.xhsFeishuConfig.appTokenConfigured !== config.xhsFeishuConfig.appTokenConfigured
|| nextConfig.xhsFeishuConfig.tableIdConfigured !== config.xhsFeishuConfig.tableIdConfigured; || nextConfig.xhsFeishuConfig.tableIdConfigured !== config.xhsFeishuConfig.tableIdConfigured
|| nextConfig.feishuMobileConfig.appIdConfigured !== config.feishuMobileConfig.appIdConfigured
|| nextConfig.feishuMobileConfig.appSecretConfigured !== config.feishuMobileConfig.appSecretConfigured;
if (secretStateChanged) { if (secretStateChanged) {
await configService.persist({ await configService.persist({
...@@ -760,6 +770,12 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc ...@@ -760,6 +770,12 @@ export function registerDesktopIpc(services: MainServices): RegisteredDesktopIpc
case "xhsFeishuTableId": case "xhsFeishuTableId":
value = await secretManager.getXhsFeishuTableId(); value = await secretManager.getXhsFeishuTableId();
break; break;
case "feishuMobileAppId":
value = await secretManager.getFeishuMobileAppId();
break;
case "feishuMobileAppSecret":
value = await secretManager.getFeishuMobileAppSecret();
break;
default: default:
throw new Error("Unsupported config secret id."); throw new Error("Unsupported config secret id.");
} }
......
...@@ -13,7 +13,8 @@ import { ...@@ -13,7 +13,8 @@ import {
type RuntimeModePreference, type RuntimeModePreference,
type SaveConfigInput, type SaveConfigInput,
type SetupMode, type SetupMode,
type XhsFeishuConfig type XhsFeishuConfig,
type FeishuMobileConfig
} from "@qjclaw/shared-types"; } from "@qjclaw/shared-types";
export type RuntimeCloudApiBaseUrlSource = "config" | "env" | "default"; export type RuntimeCloudApiBaseUrlSource = "config" | "env" | "default";
...@@ -76,6 +77,7 @@ interface LegacyConfig { ...@@ -76,6 +77,7 @@ interface LegacyConfig {
}; };
}; };
xhsFeishuConfig?: Partial<XhsFeishuConfig>; xhsFeishuConfig?: Partial<XhsFeishuConfig>;
feishuMobileConfig?: Partial<FeishuMobileConfig>;
} }
function normalizeGatewayUrl(raw: string): string { function normalizeGatewayUrl(raw: string): string {
...@@ -217,6 +219,13 @@ function createDefaultXhsFeishuConfig(): XhsFeishuConfig { ...@@ -217,6 +219,13 @@ function createDefaultXhsFeishuConfig(): XhsFeishuConfig {
}; };
} }
function createDefaultFeishuMobileConfig(): FeishuMobileConfig {
return {
appIdConfigured: false,
appSecretConfigured: false
};
}
function mergeExpertModelConfig( function mergeExpertModelConfig(
current: ExpertModelConfig, current: ExpertModelConfig,
input?: SaveConfigInput["expertModelConfig"] input?: SaveConfigInput["expertModelConfig"]
...@@ -300,6 +309,16 @@ function mergeXhsFeishuConfig( ...@@ -300,6 +309,16 @@ function mergeXhsFeishuConfig(
}; };
} }
function mergeFeishuMobileConfig(
current: FeishuMobileConfig,
input?: SaveConfigInput["feishuMobileConfig"]
): FeishuMobileConfig {
return {
appIdConfigured: typeof input?.appId === "string" ? Boolean(input.appId.trim()) : current.appIdConfigured,
appSecretConfigured: typeof input?.appSecret === "string" ? Boolean(input.appSecret.trim()) : current.appSecretConfigured
};
}
export function getRuntimeCloudApiTarget(config: Pick<AppConfig, "runtimeCloudApiBaseUrl">): RuntimeCloudApiTarget { export function getRuntimeCloudApiTarget(config: Pick<AppConfig, "runtimeCloudApiBaseUrl">): RuntimeCloudApiTarget {
return resolveRuntimeCloudApiTarget(config.runtimeCloudApiBaseUrl); return resolveRuntimeCloudApiTarget(config.runtimeCloudApiBaseUrl);
} }
...@@ -334,7 +353,8 @@ export class AppConfigService { ...@@ -334,7 +353,8 @@ export class AppConfigService {
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) xhsFeishuConfig: mergeXhsFeishuConfig(current.xhsFeishuConfig, input.xhsFeishuConfig),
feishuMobileConfig: mergeFeishuMobileConfig(current.feishuMobileConfig, input.feishuMobileConfig)
}; };
await this.writeConfig(config); await this.writeConfig(config);
...@@ -372,7 +392,8 @@ export class AppConfigService { ...@@ -372,7 +392,8 @@ export class AppConfigService {
runtimeMode: normalizeRuntimeMode(process.env.QJCLAW_RUNTIME_MODE), runtimeMode: normalizeRuntimeMode(process.env.QJCLAW_RUNTIME_MODE),
expertModelConfig: createDefaultExpertModelConfig(), expertModelConfig: createDefaultExpertModelConfig(),
douyinRuntimeConfig: createDefaultDouyinRuntimeConfig(), douyinRuntimeConfig: createDefaultDouyinRuntimeConfig(),
xhsFeishuConfig: createDefaultXhsFeishuConfig() xhsFeishuConfig: createDefaultXhsFeishuConfig(),
feishuMobileConfig: createDefaultFeishuMobileConfig()
}; };
} }
...@@ -380,6 +401,7 @@ export class AppConfigService { ...@@ -380,6 +401,7 @@ export class AppConfigService {
const defaultExpertModelConfig = createDefaultExpertModelConfig(); const defaultExpertModelConfig = createDefaultExpertModelConfig();
const defaultDouyinRuntimeConfig = createDefaultDouyinRuntimeConfig(); const defaultDouyinRuntimeConfig = createDefaultDouyinRuntimeConfig();
const defaultXhsFeishuConfig = createDefaultXhsFeishuConfig(); const defaultXhsFeishuConfig = createDefaultXhsFeishuConfig();
const defaultFeishuMobileConfig = createDefaultFeishuMobileConfig();
return { return {
setupMode: normalizeSetupMode(config.setupMode), setupMode: normalizeSetupMode(config.setupMode),
provider: config.provider ?? "openai", provider: config.provider ?? "openai",
...@@ -453,6 +475,10 @@ export class AppConfigService { ...@@ -453,6 +475,10 @@ export class AppConfigService {
appSecretConfigured: Boolean(config.xhsFeishuConfig?.appSecretConfigured ?? defaultXhsFeishuConfig.appSecretConfigured), appSecretConfigured: Boolean(config.xhsFeishuConfig?.appSecretConfigured ?? defaultXhsFeishuConfig.appSecretConfigured),
appTokenConfigured: Boolean(config.xhsFeishuConfig?.appTokenConfigured ?? defaultXhsFeishuConfig.appTokenConfigured), appTokenConfigured: Boolean(config.xhsFeishuConfig?.appTokenConfigured ?? defaultXhsFeishuConfig.appTokenConfigured),
tableIdConfigured: Boolean(config.xhsFeishuConfig?.tableIdConfigured ?? defaultXhsFeishuConfig.tableIdConfigured) tableIdConfigured: Boolean(config.xhsFeishuConfig?.tableIdConfigured ?? defaultXhsFeishuConfig.tableIdConfigured)
},
feishuMobileConfig: {
appIdConfigured: Boolean(config.feishuMobileConfig?.appIdConfigured ?? defaultFeishuMobileConfig.appIdConfigured),
appSecretConfigured: Boolean(config.feishuMobileConfig?.appSecretConfigured ?? defaultFeishuMobileConfig.appSecretConfigured)
} }
}; };
} }
......
...@@ -21,6 +21,8 @@ interface SecretRecord { ...@@ -21,6 +21,8 @@ interface SecretRecord {
xhsFeishuAppSecret?: string; xhsFeishuAppSecret?: string;
xhsFeishuAppToken?: string; xhsFeishuAppToken?: string;
xhsFeishuTableId?: string; xhsFeishuTableId?: string;
feishuMobileAppId?: string;
feishuMobileAppSecret?: string;
} }
interface SecretAccessor { interface SecretAccessor {
...@@ -46,7 +48,9 @@ type SecretName = ...@@ -46,7 +48,9 @@ type SecretName =
| "xhsFeishuAppId" | "xhsFeishuAppId"
| "xhsFeishuAppSecret" | "xhsFeishuAppSecret"
| "xhsFeishuAppToken" | "xhsFeishuAppToken"
| "xhsFeishuTableId"; | "xhsFeishuTableId"
| "feishuMobileAppId"
| "feishuMobileAppSecret";
type KeytarModule = typeof import("keytar"); type KeytarModule = typeof import("keytar");
const KEYTAR_SERVICE = "QianjiangClaw"; const KEYTAR_SERVICE = "QianjiangClaw";
...@@ -69,7 +73,9 @@ const KEYTAR_ACCOUNT_MAP: Record<SecretName, string> = { ...@@ -69,7 +73,9 @@ const KEYTAR_ACCOUNT_MAP: Record<SecretName, string> = {
xhsFeishuAppId: "xhs-feishu-app-id", xhsFeishuAppId: "xhs-feishu-app-id",
xhsFeishuAppSecret: "xhs-feishu-app-secret", xhsFeishuAppSecret: "xhs-feishu-app-secret",
xhsFeishuAppToken: "xhs-feishu-app-token", xhsFeishuAppToken: "xhs-feishu-app-token",
xhsFeishuTableId: "xhs-feishu-table-id" xhsFeishuTableId: "xhs-feishu-table-id",
feishuMobileAppId: "feishu-mobile-app-id",
feishuMobileAppSecret: "feishu-mobile-app-secret"
}; };
class FileSecretStore implements SecretAccessor { class FileSecretStore implements SecretAccessor {
...@@ -321,6 +327,22 @@ export class SecretManager { ...@@ -321,6 +327,22 @@ export class SecretManager {
return this.store.get("xhsFeishuTableId"); return this.store.get("xhsFeishuTableId");
} }
async setFeishuMobileAppId(value?: string): Promise<void> {
await this.store.set("feishuMobileAppId", value);
}
async getFeishuMobileAppId(): Promise<string | undefined> {
return this.store.get("feishuMobileAppId");
}
async setFeishuMobileAppSecret(value?: string): Promise<void> {
await this.store.set("feishuMobileAppSecret", value);
}
async getFeishuMobileAppSecret(): Promise<string | undefined> {
return this.store.get("feishuMobileAppSecret");
}
private async tryLoadKeytar(): Promise<KeytarModule | null> { private async tryLoadKeytar(): Promise<KeytarModule | null> {
try { try {
const imported = await import("keytar"); const imported = await import("keytar");
...@@ -349,7 +371,9 @@ export class SecretManager { ...@@ -349,7 +371,9 @@ export class SecretManager {
"xhsFeishuAppId", "xhsFeishuAppId",
"xhsFeishuAppSecret", "xhsFeishuAppSecret",
"xhsFeishuAppToken", "xhsFeishuAppToken",
"xhsFeishuTableId" "xhsFeishuTableId",
"feishuMobileAppId",
"feishuMobileAppSecret"
] as const) { ] as const) {
const existing = await this.store.get(secretName); const existing = await this.store.get(secretName);
if (existing) { if (existing) {
......
...@@ -82,7 +82,8 @@ import { ...@@ -82,7 +82,8 @@ import {
getResetDouyinRuntimeSettingsDrafts, getResetDouyinRuntimeSettingsDrafts,
getResetImageSettingsDrafts, getResetImageSettingsDrafts,
getResetVideoSettingsDrafts, getResetVideoSettingsDrafts,
getResetXhsFeishuSettingsDrafts getResetXhsFeishuSettingsDrafts,
getResetFeishuMobileSettingsDrafts
} from "./features/settings/settingsDrafts"; } 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";
...@@ -425,7 +426,12 @@ export default function App() { ...@@ -425,7 +426,12 @@ export default function App() {
saving, saving,
setSaving, setSaving,
hasPendingLobsterKey, hasPendingLobsterKey,
hasPendingXhsFeishuConfig hasPendingXhsFeishuConfig,
feishuMobileAppIdDraft,
setFeishuMobileAppIdDraft,
feishuMobileAppSecretDraft,
setFeishuMobileAppSecretDraft,
hasPendingFeishuMobileConfig
} = useSettingsState(config); } = useSettingsState(config);
const { const {
messageTraces, messageTraces,
...@@ -445,6 +451,9 @@ export default function App() { ...@@ -445,6 +451,9 @@ export default function App() {
saveVideoConfig, saveVideoConfig,
saveDigitalHumanConfig, saveDigitalHumanConfig,
saveDouyinRuntimeConfig, saveDouyinRuntimeConfig,
saveFeishuMobileConfig,
saveWorkspaceDirectory,
restoreWorkspaceDirectory,
pickWorkspaceDirectory pickWorkspaceDirectory
} = useSaveSettings({ } = useSaveSettings({
config, config,
...@@ -478,7 +487,9 @@ export default function App() { ...@@ -478,7 +487,9 @@ export default function App() {
xhsFeishuAppId: xhsFeishuAppIdDraft, xhsFeishuAppId: xhsFeishuAppIdDraft,
xhsFeishuAppSecret: xhsFeishuAppSecretDraft, xhsFeishuAppSecret: xhsFeishuAppSecretDraft,
xhsFeishuAppToken: xhsFeishuAppTokenDraft, xhsFeishuAppToken: xhsFeishuAppTokenDraft,
xhsFeishuTableId: xhsFeishuTableIdDraft xhsFeishuTableId: xhsFeishuTableIdDraft,
feishuMobileAppId: feishuMobileAppIdDraft,
feishuMobileAppSecret: feishuMobileAppSecretDraft
}, },
setters: { setters: {
setConfig, setConfig,
...@@ -512,7 +523,9 @@ export default function App() { ...@@ -512,7 +523,9 @@ export default function App() {
setXhsFeishuAppIdDraft, setXhsFeishuAppIdDraft,
setXhsFeishuAppSecretDraft, setXhsFeishuAppSecretDraft,
setXhsFeishuAppTokenDraft, setXhsFeishuAppTokenDraft,
setXhsFeishuTableIdDraft setXhsFeishuTableIdDraft,
setFeishuMobileAppIdDraft,
setFeishuMobileAppSecretDraft
}, },
labels: { labels: {
saveSuccessPending: ui.saveSuccessPending, saveSuccessPending: ui.saveSuccessPending,
...@@ -581,7 +594,8 @@ export default function App() { ...@@ -581,7 +594,8 @@ export default function App() {
hasPendingImageConfig, hasPendingImageConfig,
hasPendingVideoConfig, hasPendingVideoConfig,
hasPendingDigitalHumanConfig, hasPendingDigitalHumanConfig,
hasPendingDouyinRuntimeConfig hasPendingDouyinRuntimeConfig,
hasPendingFeishuMobileConfig
}); });
void hasPendingSettingsChange; void hasPendingSettingsChange;
const resetXhsFeishuSettingsDrafts = useCallback(() => { const resetXhsFeishuSettingsDrafts = useCallback(() => {
...@@ -591,6 +605,11 @@ export default function App() { ...@@ -591,6 +605,11 @@ export default function App() {
setXhsFeishuAppTokenDraft(drafts.xhsFeishuAppToken); setXhsFeishuAppTokenDraft(drafts.xhsFeishuAppToken);
setXhsFeishuTableIdDraft(drafts.xhsFeishuTableId); setXhsFeishuTableIdDraft(drafts.xhsFeishuTableId);
}, []); }, []);
const resetFeishuMobileSettingsDrafts = useCallback(() => {
const drafts = getResetFeishuMobileSettingsDrafts();
setFeishuMobileAppIdDraft(drafts.feishuMobileAppId);
setFeishuMobileAppSecretDraft(drafts.feishuMobileAppSecret);
}, []);
const resetCopywritingSettingsDrafts = useCallback(() => { const resetCopywritingSettingsDrafts = useCallback(() => {
if (!config) { if (!config) {
return; return;
...@@ -1562,6 +1581,7 @@ export default function App() { ...@@ -1562,6 +1581,7 @@ export default function App() {
hasPendingVideoConfig, hasPendingVideoConfig,
hasPendingDigitalHumanConfig, hasPendingDigitalHumanConfig,
hasPendingDouyinRuntimeConfig, hasPendingDouyinRuntimeConfig,
hasPendingFeishuMobileConfig,
labels: { export: ui.export }, labels: { export: ui.export },
drafts: { drafts: {
lobsterKey: lobsterKeyDraft, lobsterKey: lobsterKeyDraft,
...@@ -1590,7 +1610,9 @@ export default function App() { ...@@ -1590,7 +1610,9 @@ export default function App() {
replicationBriefApiKey: replicationBriefApiKeyDraft, replicationBriefApiKey: replicationBriefApiKeyDraft,
vectcutBaseUrl: vectcutBaseUrlDraft, vectcutBaseUrl: vectcutBaseUrlDraft,
vectcutFileBaseUrl: vectcutFileBaseUrlDraft, vectcutFileBaseUrl: vectcutFileBaseUrlDraft,
vectcutApiKey: vectcutApiKeyDraft vectcutApiKey: vectcutApiKeyDraft,
feishuMobileAppId: feishuMobileAppIdDraft,
feishuMobileAppSecret: feishuMobileAppSecretDraft
}, },
setters: { setters: {
setLobsterKey: setLobsterKeyDraft, setLobsterKey: setLobsterKeyDraft,
...@@ -1619,7 +1641,9 @@ export default function App() { ...@@ -1619,7 +1641,9 @@ export default function App() {
setReplicationBriefApiKey: setReplicationBriefApiKeyDraft, setReplicationBriefApiKey: setReplicationBriefApiKeyDraft,
setVectcutBaseUrl: setVectcutBaseUrlDraft, setVectcutBaseUrl: setVectcutBaseUrlDraft,
setVectcutFileBaseUrl: setVectcutFileBaseUrlDraft, setVectcutFileBaseUrl: setVectcutFileBaseUrlDraft,
setVectcutApiKey: setVectcutApiKeyDraft setVectcutApiKey: setVectcutApiKeyDraft,
setFeishuMobileAppId: setFeishuMobileAppIdDraft,
setFeishuMobileAppSecret: setFeishuMobileAppSecretDraft
}, },
onSaveLobsterKey: saveLobsterKey, onSaveLobsterKey: saveLobsterKey,
onSaveXhsFeishuConfig: saveXhsFeishuConfig, onSaveXhsFeishuConfig: saveXhsFeishuConfig,
...@@ -1634,6 +1658,10 @@ export default function App() { ...@@ -1634,6 +1658,10 @@ export default function App() {
onResetDigitalHumanConfig: resetDigitalHumanSettingsDrafts, onResetDigitalHumanConfig: resetDigitalHumanSettingsDrafts,
onSaveDouyinRuntimeConfig: saveDouyinRuntimeConfig, onSaveDouyinRuntimeConfig: saveDouyinRuntimeConfig,
onResetDouyinRuntimeConfig: resetDouyinRuntimeSettingsDrafts, onResetDouyinRuntimeConfig: resetDouyinRuntimeSettingsDrafts,
onSaveFeishuMobileConfig: saveFeishuMobileConfig,
onResetFeishuMobileConfig: resetFeishuMobileSettingsDrafts,
onSaveWorkspaceDirectory: saveWorkspaceDirectory,
onResetWorkspaceDirectory: restoreWorkspaceDirectory,
onRevealSecret: revealConfigSecret, onRevealSecret: revealConfigSecret,
pickWorkspaceDirectory, pickWorkspaceDirectory,
exportDiagnostics exportDiagnostics
......
import type { ReactNode } from "react"
export interface ModalProps {
open: boolean
onClose: () => void
title?: string
children: ReactNode
}
export function Modal({ open, onClose, title, children }: ModalProps) {
if (!open) {
return null
}
return (
<div className="modal-overlay" role="dialog" aria-modal="true" onClick={onClose}>
<div className="modal-panel" onClick={(e) => e.stopPropagation()}>
<div className="modal-header">
{title ? <h2 className="modal-title">{title}</h2> : <span />}
<button
type="button"
className="modal-close-button"
onClick={onClose}
aria-label="关闭"
>
<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">
<path
d="M18 6L6 18M6 6l12 12"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
/>
</svg>
</button>
</div>
<div className="modal-body">
{children}
</div>
</div>
</div>
)
}
...@@ -8,6 +8,7 @@ export interface PendingSettingsFlags { ...@@ -8,6 +8,7 @@ export interface PendingSettingsFlags {
hasPendingVideoConfig: boolean hasPendingVideoConfig: boolean
hasPendingDigitalHumanConfig: boolean hasPendingDigitalHumanConfig: boolean
hasPendingDouyinRuntimeConfig: boolean hasPendingDouyinRuntimeConfig: boolean
hasPendingFeishuMobileConfig: boolean
} }
export interface BasicResetSettingsDrafts { export interface BasicResetSettingsDrafts {
...@@ -59,6 +60,11 @@ export interface XhsFeishuResetSettingsDrafts { ...@@ -59,6 +60,11 @@ export interface XhsFeishuResetSettingsDrafts {
xhsFeishuTableId: string xhsFeishuTableId: string
} }
export interface FeishuMobileResetSettingsDrafts {
feishuMobileAppId: string
feishuMobileAppSecret: string
}
export function getHasPendingSettingsChange(flags: PendingSettingsFlags) { export function getHasPendingSettingsChange(flags: PendingSettingsFlags) {
return flags.hasPendingBasicConfig return flags.hasPendingBasicConfig
|| flags.hasPendingXhsFeishuConfig || flags.hasPendingXhsFeishuConfig
...@@ -67,6 +73,7 @@ export function getHasPendingSettingsChange(flags: PendingSettingsFlags) { ...@@ -67,6 +73,7 @@ export function getHasPendingSettingsChange(flags: PendingSettingsFlags) {
|| flags.hasPendingVideoConfig || flags.hasPendingVideoConfig
|| flags.hasPendingDigitalHumanConfig || flags.hasPendingDigitalHumanConfig
|| flags.hasPendingDouyinRuntimeConfig || flags.hasPendingDouyinRuntimeConfig
|| flags.hasPendingFeishuMobileConfig
} }
export function getResetBasicSettingsDrafts(config: AppConfig): BasicResetSettingsDrafts { export function getResetBasicSettingsDrafts(config: AppConfig): BasicResetSettingsDrafts {
...@@ -131,3 +138,10 @@ export function getResetXhsFeishuSettingsDrafts(): XhsFeishuResetSettingsDrafts ...@@ -131,3 +138,10 @@ export function getResetXhsFeishuSettingsDrafts(): XhsFeishuResetSettingsDrafts
xhsFeishuTableId: "" xhsFeishuTableId: ""
} }
} }
export function getResetFeishuMobileSettingsDrafts(): FeishuMobileResetSettingsDrafts {
return {
feishuMobileAppId: "",
feishuMobileAppSecret: ""
}
}
...@@ -34,6 +34,8 @@ interface UseSaveSettingsOptions { ...@@ -34,6 +34,8 @@ interface UseSaveSettingsOptions {
xhsFeishuAppSecret: string xhsFeishuAppSecret: string
xhsFeishuAppToken: string xhsFeishuAppToken: string
xhsFeishuTableId: string xhsFeishuTableId: string
feishuMobileAppId: string
feishuMobileAppSecret: string
} }
setters: { setters: {
setConfig(config: AppConfig): void setConfig(config: AppConfig): void
...@@ -68,6 +70,8 @@ interface UseSaveSettingsOptions { ...@@ -68,6 +70,8 @@ interface UseSaveSettingsOptions {
setXhsFeishuAppSecretDraft(value: string): void setXhsFeishuAppSecretDraft(value: string): void
setXhsFeishuAppTokenDraft(value: string): void setXhsFeishuAppTokenDraft(value: string): void
setXhsFeishuTableIdDraft(value: string): void setXhsFeishuTableIdDraft(value: string): void
setFeishuMobileAppIdDraft(value: string): void
setFeishuMobileAppSecretDraft(value: string): void
} }
labels: { labels: {
saveSuccessPending: string saveSuccessPending: string
...@@ -94,6 +98,7 @@ export function useSaveSettings({ ...@@ -94,6 +98,7 @@ export function useSaveSettings({
expertModelConfig?: SaveConfigInput["expertModelConfig"] expertModelConfig?: SaveConfigInput["expertModelConfig"]
douyinRuntimeConfig?: SaveConfigInput["douyinRuntimeConfig"] douyinRuntimeConfig?: SaveConfigInput["douyinRuntimeConfig"]
xhsFeishuConfig?: SaveConfigInput["xhsFeishuConfig"] xhsFeishuConfig?: SaveConfigInput["xhsFeishuConfig"]
feishuMobileConfig?: SaveConfigInput["feishuMobileConfig"]
successMessage?: string successMessage?: string
resetDrafts?: (savedConfig: AppConfig) => void resetDrafts?: (savedConfig: AppConfig) => void
}) => { }) => {
...@@ -126,6 +131,9 @@ export function useSaveSettings({ ...@@ -126,6 +131,9 @@ export function useSaveSettings({
if (options?.xhsFeishuConfig) { if (options?.xhsFeishuConfig) {
input.xhsFeishuConfig = options.xhsFeishuConfig input.xhsFeishuConfig = options.xhsFeishuConfig
} }
if (options?.feishuMobileConfig) {
input.feishuMobileConfig = options.feishuMobileConfig
}
setters.setSaving(true) setters.setSaving(true)
setters.setErrorText("") setters.setErrorText("")
...@@ -211,6 +219,19 @@ export function useSaveSettings({ ...@@ -211,6 +219,19 @@ export function useSaveSettings({
}) })
}, [drafts.xhsFeishuAppId, drafts.xhsFeishuAppSecret, drafts.xhsFeishuAppToken, drafts.xhsFeishuTableId, saveConfig, setters]) }, [drafts.xhsFeishuAppId, drafts.xhsFeishuAppSecret, drafts.xhsFeishuAppToken, drafts.xhsFeishuTableId, saveConfig, setters])
const saveFeishuMobileConfig = useCallback(async () => {
return await saveConfig({
feishuMobileConfig: {
appId: drafts.feishuMobileAppId.trim() || undefined,
appSecret: drafts.feishuMobileAppSecret.trim() || undefined
},
resetDrafts: () => {
setters.setFeishuMobileAppIdDraft("")
setters.setFeishuMobileAppSecretDraft("")
}
})
}, [drafts.feishuMobileAppId, drafts.feishuMobileAppSecret, saveConfig, setters])
const saveCopywritingConfig = useCallback(async () => { const saveCopywritingConfig = useCallback(async () => {
return await saveConfig({ return await saveConfig({
expertModelConfig: { expertModelConfig: {
...@@ -332,6 +353,7 @@ export function useSaveSettings({ ...@@ -332,6 +353,7 @@ export function useSaveSettings({
saveWorkspaceDirectory, saveWorkspaceDirectory,
restoreWorkspaceDirectory, restoreWorkspaceDirectory,
saveXhsFeishuConfig, saveXhsFeishuConfig,
saveFeishuMobileConfig,
saveCopywritingConfig, saveCopywritingConfig,
saveImageConfig, saveImageConfig,
saveVideoConfig, saveVideoConfig,
......
...@@ -40,6 +40,8 @@ export function useSettingsState(config: AppConfig | null) { ...@@ -40,6 +40,8 @@ export function useSettingsState(config: AppConfig | null) {
const [xhsFeishuAppSecretDraft, setXhsFeishuAppSecretDraft] = useState(""); const [xhsFeishuAppSecretDraft, setXhsFeishuAppSecretDraft] = useState("");
const [xhsFeishuAppTokenDraft, setXhsFeishuAppTokenDraft] = useState(""); const [xhsFeishuAppTokenDraft, setXhsFeishuAppTokenDraft] = useState("");
const [xhsFeishuTableIdDraft, setXhsFeishuTableIdDraft] = useState(""); const [xhsFeishuTableIdDraft, setXhsFeishuTableIdDraft] = useState("");
const [feishuMobileAppIdDraft, setFeishuMobileAppIdDraft] = useState("");
const [feishuMobileAppSecretDraft, setFeishuMobileAppSecretDraft] = useState("");
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const hasPendingLobsterKey = lobsterKeyDraft.trim().length > 0; const hasPendingLobsterKey = lobsterKeyDraft.trim().length > 0;
...@@ -49,6 +51,10 @@ export function useSettingsState(config: AppConfig | null) { ...@@ -49,6 +51,10 @@ export function useSettingsState(config: AppConfig | null) {
xhsFeishuAppTokenDraft.trim() || xhsFeishuAppTokenDraft.trim() ||
xhsFeishuTableIdDraft.trim() xhsFeishuTableIdDraft.trim()
); );
const hasPendingFeishuMobileConfig = Boolean(
feishuMobileAppIdDraft.trim() ||
feishuMobileAppSecretDraft.trim()
);
const hasPendingModelKeys = Boolean( const hasPendingModelKeys = Boolean(
imageModelApiKeyDraft.trim() imageModelApiKeyDraft.trim()
|| imageModelBaseUrlDraft.trim() !== (config?.expertModelConfig.image.baseUrl ?? "").trim() || imageModelBaseUrlDraft.trim() !== (config?.expertModelConfig.image.baseUrl ?? "").trim()
...@@ -131,10 +137,15 @@ export function useSettingsState(config: AppConfig | null) { ...@@ -131,10 +137,15 @@ export function useSettingsState(config: AppConfig | null) {
setXhsFeishuAppTokenDraft, setXhsFeishuAppTokenDraft,
xhsFeishuTableIdDraft, xhsFeishuTableIdDraft,
setXhsFeishuTableIdDraft, setXhsFeishuTableIdDraft,
feishuMobileAppIdDraft,
setFeishuMobileAppIdDraft,
feishuMobileAppSecretDraft,
setFeishuMobileAppSecretDraft,
saving, saving,
setSaving, setSaving,
hasPendingLobsterKey, hasPendingLobsterKey,
hasPendingXhsFeishuConfig, hasPendingXhsFeishuConfig,
hasPendingFeishuMobileConfig,
hasPendingModelKeys hasPendingModelKeys
}; };
} }
...@@ -1520,3 +1520,128 @@ select:focus-visible { ...@@ -1520,3 +1520,128 @@ select:focus-visible {
.custom-scrollbar::-webkit-scrollbar-thumb:hover { .custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: #94a3b8; background: #94a3b8;
} }
/* Modal */
.modal-overlay {
position: fixed;
inset: 0;
z-index: 100;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.28);
animation: modal-overlay-in 0.15s ease-out;
}
@keyframes modal-overlay-in {
from { opacity: 0; }
to { opacity: 1; }
}
.modal-panel {
background: #ffffff;
border: 1px solid #e8ecf1;
border-radius: 12px;
box-shadow: 0 8px 40px rgba(0, 0, 0, 0.14);
max-width: 560px;
width: calc(100vw - 48px);
max-height: calc(100vh - 80px);
display: flex;
flex-direction: column;
animation: modal-panel-in 0.18s ease-out;
}
@keyframes modal-panel-in {
from { opacity: 0; transform: translateY(8px) scale(0.98); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 18px 22px 0;
}
.modal-title {
font-size: 15px;
font-weight: 600;
color: var(--color-text-primary, #1a1a2e);
margin: 0;
}
.modal-close-button {
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border-radius: 6px;
border: none;
background: transparent;
color: #94a3b8;
cursor: pointer;
flex-shrink: 0;
}
.modal-close-button:hover {
background: rgba(0, 0, 0, 0.06);
color: #1e293b;
}
.modal-body {
padding: 16px 22px 22px;
font-size: 13px;
line-height: 1.7;
color: var(--color-text-secondary, #62759a);
overflow-y: auto;
}
.modal-body ol {
margin: 8px 0 0;
padding-left: 18px;
}
.modal-body li {
margin-bottom: 4px;
}
.modal-body .modal-manual-footer {
margin-top: 16px;
padding-top: 12px;
border-top: 1px solid var(--color-border, rgba(0, 0, 0, 0.08));
color: var(--color-text-tertiary, #94a3b8);
font-size: 12px;
}
.settings-manual-link {
display: inline-flex;
align-items: center;
gap: 4px;
background: none;
border: none;
color: var(--color-accent, #3b82f6);
font-size: 12px;
cursor: pointer;
padding: 2px 6px;
border-radius: 6px;
}
.settings-manual-link:hover {
background: rgba(59, 130, 246, 0.08);
text-decoration: underline;
}
.settings-section-title-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
}
.settings-section-title {
font-size: 13px;
font-weight: 600;
color: var(--color-text-primary, #1a1a2e);
margin: 0;
}
This diff is collapsed.
...@@ -105,7 +105,9 @@ export type ConfigSecretId = ...@@ -105,7 +105,9 @@ export type ConfigSecretId =
| "xhsFeishuAppId" | "xhsFeishuAppId"
| "xhsFeishuAppSecret" | "xhsFeishuAppSecret"
| "xhsFeishuAppToken" | "xhsFeishuAppToken"
| "xhsFeishuTableId"; | "xhsFeishuTableId"
| "feishuMobileAppId"
| "feishuMobileAppSecret";
export interface WorkspaceWarmupResult { export interface WorkspaceWarmupResult {
accepted: boolean; accepted: boolean;
...@@ -660,6 +662,11 @@ export interface XhsFeishuConfig { ...@@ -660,6 +662,11 @@ export interface XhsFeishuConfig {
tableIdConfigured: boolean; tableIdConfigured: boolean;
} }
export interface FeishuMobileConfig {
appIdConfigured: boolean;
appSecretConfigured: 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",
...@@ -717,6 +724,7 @@ export interface AppConfig { ...@@ -717,6 +724,7 @@ export interface AppConfig {
expertModelConfig: ExpertModelConfig; expertModelConfig: ExpertModelConfig;
douyinRuntimeConfig: DouyinRuntimeConfig; douyinRuntimeConfig: DouyinRuntimeConfig;
xhsFeishuConfig: XhsFeishuConfig; xhsFeishuConfig: XhsFeishuConfig;
feishuMobileConfig: FeishuMobileConfig;
} }
export interface DiagnosticsExportResult { export interface DiagnosticsExportResult {
...@@ -757,6 +765,11 @@ export interface XhsFeishuConfigInput { ...@@ -757,6 +765,11 @@ export interface XhsFeishuConfigInput {
tableId?: string; tableId?: string;
} }
export interface FeishuMobileConfigInput {
appId?: string;
appSecret?: string;
}
export interface SaveConfigInput { export interface SaveConfigInput {
setupMode: SetupMode; setupMode: SetupMode;
provider: string; provider: string;
...@@ -782,6 +795,7 @@ export interface SaveConfigInput { ...@@ -782,6 +795,7 @@ export interface SaveConfigInput {
vectcut?: VectCutModelInput; vectcut?: VectCutModelInput;
}; };
xhsFeishuConfig?: XhsFeishuConfigInput; xhsFeishuConfig?: XhsFeishuConfigInput;
feishuMobileConfig?: FeishuMobileConfigInput;
} }
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