'use client'; import React, { useState, useEffect } from 'react'; import { KeyIcon, CogIcon, GlobeAltIcon, SpeakerWaveIcon, CheckIcon, ExclamationTriangleIcon, EyeIcon, EyeSlashIcon } from '@heroicons/react/24/outline'; import { getItem, setItem } from '@/lib/storage'; import { useTranslation } from '../i18n/client'; // 导入 useTranslation // 存储键名 const SETTINGS_STORAGE_KEY = 'podcast-settings'; // 设置表单数据类型 interface SettingsFormData { apikey: string; model: string; baseurl: string; index: { api_url: string; }; edge: { api_url: string; }; doubao: { 'X-Api-App-Id': string; 'X-Api-Access-Key': string; }; fish: { api_key: string; }; minimax: { group_id: string; api_key: string; }; gemini: { api_key: string; }; } // 模型选项 const MODEL_OPTIONS = [ 'gpt-4-turbo', 'gpt-4o-mini', 'gpt-4o', ]; // TTS服务配置 const TTS_SERVICES = [ { key: 'doubao', name: 'Doubao TTS', icon: SpeakerWaveIcon }, { key: 'fish', name: 'Fish TTS', icon: SpeakerWaveIcon }, { key: 'minimax', name: 'Minimax TTS', icon: SpeakerWaveIcon }, { key: 'gemini', name: 'Gemini TTS', icon: SpeakerWaveIcon }, { key: 'edge', name: 'Edge TTS', icon: SpeakerWaveIcon }, // Edge TTS 仍在网络 API 服务中 { key: 'index', name: 'Index TTS', icon: SpeakerWaveIcon }, // Index TTS 保持本地 API ]; interface SettingsFormProps { onSave?: (data: SettingsFormData) => void; onSuccess?: (message: string) => void; onError?: (message: string) => void; lang: string; // 新增 lang 属性 } export default function SettingsForm({ onSave, onSuccess, onError, lang }: SettingsFormProps) { const { t } = useTranslation(lang, 'components'); // 初始化 useTranslation 并指定命名空间 const [formData, setFormData] = useState({ apikey: '', model: '', baseurl: '', index: { api_url: '' }, edge: { api_url: '' }, doubao: { 'X-Api-App-Id': '', 'X-Api-Access-Key': '' }, fish: { api_key: '' }, minimax: { group_id: '', api_key: '' }, gemini: { api_key: '' }, }); const [isLoading, setIsLoading] = useState(false); const [showPasswords, setShowPasswords] = useState>({}); const [isModelDropdownOpen, setIsModelDropdownOpen] = useState(false); // 组件挂载时从 localStorage 加载设置 useEffect(() => { const savedSettings = getItem(SETTINGS_STORAGE_KEY); if (savedSettings) { // 确保从 localStorage 加载的设置中,所有预期的字符串字段都为字符串 // 防止出现受控组件变为非受控组件的警告 const normalizedSettings = { apikey: savedSettings.apikey || '', model: savedSettings.model || '', baseurl: savedSettings.baseurl || '', index: { api_url: savedSettings.index?.api_url || '', }, edge: { api_url: savedSettings.edge?.api_url || '', }, doubao: { 'X-Api-App-Id': savedSettings.doubao?.['X-Api-App-Id'] || '', 'X-Api-Access-Key': savedSettings.doubao?.['X-Api-Access-Key'] || '', }, fish: { api_key: savedSettings.fish?.api_key || '', }, minimax: { group_id: savedSettings.minimax?.group_id || '', api_key: savedSettings.minimax?.api_key || '', }, gemini: { api_key: savedSettings.gemini?.api_key || '', }, }; setFormData(normalizedSettings); } }, []); const togglePasswordVisibility = (fieldKey: string) => { setShowPasswords(prev => ({ ...prev, [fieldKey]: !prev[fieldKey], })); }; const handleInputChange = (path: string, value: string) => { setFormData(prev => { const newData = { ...prev }; const keys = path.split('.'); if (keys.length === 1) { (newData as any)[keys[0]] = value; } else if (keys.length === 2) { (newData as any)[keys[0]][keys[1]] = value; } return newData; }); }; const handleReset = () => { const defaultData: SettingsFormData = { apikey: '', model: '', baseurl: '', index: { api_url: '' }, edge: { api_url: '' }, doubao: { 'X-Api-App-Id': '', 'X-Api-Access-Key': '' }, fish: { api_key: '' }, minimax: { group_id: '', api_key: '' }, gemini: { api_key: '' }, }; setFormData(defaultData); setShowPasswords({}); }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setIsLoading(true); try { // 转换空字符串为 null const processedData = JSON.parse(JSON.stringify(formData), (key, value) => { return value === '' ? null : value; }); // 保存到 localStorage setItem(SETTINGS_STORAGE_KEY, processedData); // 触发自定义事件,通知其他组件设置已更新 window.dispatchEvent(new CustomEvent('settingsUpdated')); // 调用保存回调 if (onSave) { onSave(processedData); } onSuccess?.(t('settingsForm.settingsSavedSuccessfully')); } catch (error) { console.error('Error saving settings:', error); onError?.(t('settingsForm.errorSavingSettings')); } finally { setIsLoading(false); } }; const renderPasswordInput = ( label: string, path: string, value: string, placeholder?: string, required?: boolean ) => { const fieldKey = path.replace('.', '_'); const isVisible = showPasswords[fieldKey]; return (
handleInputChange(path, e.target.value)} placeholder={placeholder} className="input-primary pr-10 w-full" required={required} />
); }; const renderTextInput = ( label: string, path: string, value: string, placeholder?: string, required?: boolean, wrapperClassName?: string // 新增 wrapperClassName 参数 ) => (
{/* 应用 wrapperClassName */} handleInputChange(path, e.target.value)} placeholder={placeholder} className="input-primary w-full" required={required} />
); const renderSelectInput = ( label: string, path: string, value: string, options: { value: string; label: string }[], required?: boolean ) => (
); return (

{t('settingsForm.settings')}

{t('settingsForm.apiSettingsDescription')}

{/* 通用设置 */}

{t('settingsForm.generalSettings')}

{renderPasswordInput( t('settingsForm.apiKey'), 'apikey', formData.apikey, t('settingsForm.inputYourOpenAIAPIKey'), true )}
handleInputChange('model', e.target.value)} onFocus={() => setIsModelDropdownOpen(true)} placeholder={t('settingsForm.selectOrEnterModelName')} className="input-primary w-full pr-8" required /> {isModelDropdownOpen && (
{MODEL_OPTIONS.map((model) => ( ))}
)}
{/* 点击外部关闭下拉菜单 */} {isModelDropdownOpen && (
setIsModelDropdownOpen(false)} /> )}
{renderTextInput( t('settingsForm.baseURL'), 'baseurl', formData.baseurl, t('settingsForm.optionalCustomBaseURL'), false, // required 'md:col-span-2' // wrapperClassName )}
{/* TTS服务设置 */}

{t('settingsForm.ttsServiceSettings')}

{/* 网络 API TTS 服务 */}

{t('settingsForm.webAPITTSServices')}

{/* Edge TTS */}

{t('settingsForm.edgeTTS')}

{t('settingsForm.edgeTTSDescription')}

{renderTextInput( 'API URL', 'edge.api_url', formData.edge.api_url, 'http://localhost:8001' )}
{/* Doubao TTS */}

{t('settingsForm.doubaoTTS')}

{t('settingsForm.doubaoTTSDescription')}

{renderTextInput( t('settingsForm.appID'), 'doubao.X-Api-App-Id', formData.doubao['X-Api-App-Id'], t('settingsForm.inputDoubaoAppID') )} {renderPasswordInput( t('settingsForm.accessKey'), 'doubao.X-Api-Access-Key', formData.doubao['X-Api-Access-Key'], t('settingsForm.inputDoubaoAccessKey') )}
{/* Minimax TTS */}

{t('settingsForm.minimaxTTS')}

{t('settingsForm.minimaxTTSDescription')}

{renderTextInput( t('settingsForm.groupID'), 'minimax.group_id', formData.minimax.group_id, t('settingsForm.inputMinimaxGroupID') )} {renderPasswordInput( t('settingsForm.apiKey'), 'minimax.api_key', formData.minimax.api_key, t('settingsForm.inputMinimaxAPIKey') )}
{/* Fish TTS */}

{t('settingsForm.fishTTS')}

{t('settingsForm.fishTTSDescription')}

{renderPasswordInput( t('settingsForm.apiKey'), 'fish.api_key', formData.fish.api_key, t('settingsForm.inputFishTTSAPIKey') )}
{/* Gemini TTS */}

{t('settingsForm.geminiTTS')}

{t('settingsForm.geminiTTSDescription')}

{renderPasswordInput( t('settingsForm.apiKey'), 'gemini.api_key', formData.gemini.api_key, t('settingsForm.inputGeminiAPIKey') )}
{/* 本地 API TTS 服务 */}

{t('settingsForm.localAPITTSServices')}

{/* Index TTS */}

{t('settingsForm.indexTTS')}

{t('settingsForm.indexTTSDescription')}

{renderTextInput( 'API URL', 'index.api_url', formData.index.api_url, 'http://localhost:8000' )}
{/* 提交按钮 */}
{/* 帮助信息 */}

{t('settingsForm.configurationNotes')}

  • • {t('settingsForm.apiKeyRequired')}
  • • {t('settingsForm.ttsOptional')}
  • • {t('settingsForm.emptyFieldsNull')}
  • • {t('settingsForm.settingsApplyImmediately')}
); }