ManimCat / frontend /src /components /settings-modal /use-settings-modal.ts
Bin29's picture
Sync from main: 68df783 feat: support multimodal studio reference images
d47b053
import { useEffect, useRef, useState } from 'react';
import type { SettingsConfig } from '../../types/api';
import { loadSettings, saveSettings } from '../../lib/settings';
import { getActiveProvider, providerToCustomApiConfig } from '../../lib/ai-providers';
import type { TabType, TestResult } from './types';
import { useI18n } from '../../i18n';
interface UseSettingsModalParams {
onSave: (config: SettingsConfig) => void;
}
interface UseSettingsModalResult {
config: SettingsConfig;
activeTab: TabType;
testResult: TestResult;
setActiveTab: (tab: TabType) => void;
updateManimcatApiKey: (value: string) => void;
updateVideoConfig: (updates: Partial<SettingsConfig['video']>) => void;
handleTestBackend: () => Promise<void>;
}
export function useSettingsModal({ onSave }: UseSettingsModalParams): UseSettingsModalResult {
const { t } = useI18n();
const [config, setConfig] = useState<SettingsConfig>(() => loadSettings());
const [testResult, setTestResult] = useState<TestResult>({ status: 'idle', message: '' });
const [activeTab, setActiveTab] = useState<TabType>('api');
const autoSaveTimerRef = useRef<number | null>(null);
useEffect(() => {
if (autoSaveTimerRef.current) {
clearTimeout(autoSaveTimerRef.current);
}
autoSaveTimerRef.current = window.setTimeout(() => {
saveSettings(config);
onSave(config);
}, 500);
return () => {
if (autoSaveTimerRef.current) {
clearTimeout(autoSaveTimerRef.current);
autoSaveTimerRef.current = null;
}
};
}, [config, onSave]);
const updateManimcatApiKey = (value: string) => {
setConfig((prev) => ({ ...prev, api: { ...prev.api, manimcatApiKey: value } }));
};
const updateVideoConfig = (updates: Partial<SettingsConfig['video']>) => {
setConfig((prev) => ({ ...prev, video: { ...prev.video, ...updates } }));
};
const handleTestBackend = async () => {
const manimcatKey = config.api.manimcatApiKey.trim();
if (!manimcatKey) {
setTestResult({ status: 'error', message: t('settings.test.needManimcatKey') });
return;
}
const activeProvider = getActiveProvider(config.api);
const customApiConfig = providerToCustomApiConfig(activeProvider);
setTestResult({ status: 'testing', message: t('settings.test.testing'), details: {} });
const startTime = performance.now();
try {
const response = await fetch('/api/ai/test', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${manimcatKey}`,
},
body: JSON.stringify(customApiConfig ? { customApiConfig } : {}),
});
const duration = Math.round(performance.now() - startTime);
if (response.ok) {
const payload = (await response.json().catch(() => null)) as { warning?: unknown } | null;
const warning = payload && typeof payload.warning === 'string' ? payload.warning : '';
setTestResult({
status: 'success',
message: warning ? `${t('settings.test.success', { duration })}${warning}` : t('settings.test.success', { duration }),
details: { statusCode: response.status, statusText: response.statusText, duration },
});
return;
}
const responseBody = await response.text();
setTestResult({
status: 'error',
message: `HTTP ${response.status}: ${response.statusText}`,
details: {
statusCode: response.status,
statusText: response.statusText,
responseBody: responseBody.slice(0, 2000),
duration,
},
});
} catch (error) {
const duration = Math.round(performance.now() - startTime);
setTestResult({
status: 'error',
message: error instanceof Error ? error.message : t('settings.test.failed'),
details: { error: error instanceof Error ? `${error.name}: ${error.message}` : String(error), duration },
});
}
};
return {
config,
activeTab,
testResult,
setActiveTab,
updateManimcatApiKey,
updateVideoConfig,
handleTestBackend,
};
}