'use client'; import { useState, useEffect } from 'react'; import { Box, Typography, Button, Card, CardContent, Switch, FormControlLabel, TextField, IconButton, Tooltip, Divider, Alert, CircularProgress, Dialog, DialogTitle, DialogContent, DialogActions, Grid } from '@mui/material'; import { Add as AddIcon, Delete as DeleteIcon, AutoFixHigh as AutoFixHighIcon, Save as SaveIcon } from '@mui/icons-material'; import { useTranslation } from 'react-i18next'; import i18n from '@/lib/i18n'; /** * GA Pairs Manager Component * @param {Object} props * @param {string} props.projectId - Project ID * @param {string} props.fileId - File ID * @param {Function} props.onGaPairsChange - Callback when GA pairs change */ export default function GaPairsManager({ projectId, fileId, onGaPairsChange }) { const { t } = useTranslation(); const [gaPairs, setGaPairs] = useState([]); const [backupGaPairs, setBackupGaPairs] = useState([]); // 备份状态 const [loading, setLoading] = useState(false); const [generating, setGenerating] = useState(false); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); const [success, setSuccess] = useState(null); const [addDialogOpen, setAddDialogOpen] = useState(false); const [newGaPair, setNewGaPair] = useState({ genreTitle: '', genreDesc: '', audienceTitle: '', audienceDesc: '', isActive: true }); useEffect(() => { loadGaPairs(); }, [projectId, fileId]); const loadGaPairs = async () => { try { setLoading(true); setError(null); const response = await fetch(`/api/projects/${projectId}/files/${fileId}/ga-pairs`); // 检查响应状态 if (!response.ok) { if (response.status === 404) { console.warn('GA Pairs API not found, using empty data'); setGaPairs([]); setBackupGaPairs([]); return; } throw new Error(`HTTP ${response.status}: Failed to load GA pairs`); } const result = await response.json(); console.log('Load GA pairs result:', result); if (result.success) { const loadedData = result.data || []; setGaPairs(loadedData); setBackupGaPairs([...loadedData]); // 创建备份 onGaPairsChange?.(loadedData); } else { throw new Error(result.error || 'Failed to load GA pairs'); } } catch (error) { console.error('Load GA pairs error:', error); setError(t('gaPairs.loadError', { error: error.message })); } finally { setLoading(false); } }; const generateGaPairs = async () => { try { setGenerating(true); setError(null); console.log('Starting GA pairs generation...'); // Get current language from i18n const currentLanguage = i18n.language === 'en' ? 'en' : '中文'; const response = await fetch(`/api/projects/${projectId}/files/${fileId}/ga-pairs`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ regenerate: false, appendMode: true, // 新增:启用追加模式 language: currentLanguage }) }); if (!response.ok) { let errorMessage = t('gaPairs.generateError'); if (response.status === 404) { errorMessage = t('gaPairs.serviceNotAvailable'); } else if (response.status === 400) { try { const errorResult = await response.json(); if (errorResult.error?.includes('No active AI model')) { errorMessage = t('gaPairs.noActiveModel'); } else if (errorResult.error?.includes('content might be too short')) { errorMessage = t('gaPairs.contentTooShort'); } else { errorMessage = errorResult.error || errorMessage; } } catch (parseError) { errorMessage = t('gaPairs.requestFailed', { status: response.status }); } } else if (response.status === 500) { try { const errorResult = await response.json(); if (errorResult.error?.includes('model configuration') || errorResult.error?.includes('Module not found')) { errorMessage = t('gaPairs.configError'); } else { errorMessage = errorResult.error || 'Internal server error occurred.'; } } catch (parseError) { console.error('Failed to parse error response:', parseError); errorMessage = errorResult.error || t('gaPairs.internalServerError'); } } throw new Error(errorMessage); } // 处理成功响应 const responseText = await response.text(); if (!responseText || responseText.trim() === '') { throw new Error(t('gaPairs.emptyResponse')); } const result = JSON.parse(responseText); console.log('Generate GA pairs result:', result); if (result.success) { // 在追加模式下,后端只返回新生成的GA对 const newGaPairs = result.data || []; // 将新生成的GA对追加到现有的GA对 const updatedGaPairs = [...gaPairs, ...newGaPairs]; setGaPairs(updatedGaPairs); setBackupGaPairs([...updatedGaPairs]); // 更新备份 onGaPairsChange?.(updatedGaPairs); setSuccess( t('gaPairs.additionalPairsGenerated', { count: newGaPairs.length, total: updatedGaPairs.length }) ); } else { throw new Error(result.error || t('gaPairs.generationFailed')); } } catch (error) { console.error('Generate GA pairs error:', error); setError(error.message); } finally { setGenerating(false); } }; const saveGaPairs = async () => { try { setSaving(true); setError(null); // 验证GA对数据 const validatedGaPairs = gaPairs.map((pair, index) => { // 处理不同的数据格式 let genreTitle, genreDesc, audienceTitle, audienceDesc; if (pair.genre && typeof pair.genre === 'object') { genreTitle = pair.genre.title; genreDesc = pair.genre.description; } else { genreTitle = pair.genreTitle || pair.genre; genreDesc = pair.genreDesc || ''; } if (pair.audience && typeof pair.audience === 'object') { audienceTitle = pair.audience.title; audienceDesc = pair.audience.description; } else { audienceTitle = pair.audienceTitle || pair.audience; audienceDesc = pair.audienceDesc || ''; } // 验证必填字段 if (!genreTitle || !audienceTitle) { throw new Error(t('gaPairs.validationError', { number: index + 1 })); } return { id: pair.id, genreTitle: genreTitle.trim(), genreDesc: genreDesc.trim(), audienceTitle: audienceTitle.trim(), audienceDesc: audienceDesc.trim(), isActive: pair.isActive !== undefined ? pair.isActive : true }; }); console.log('Saving validated GA pairs:', validatedGaPairs); const response = await fetch(`/api/projects/${projectId}/files/${fileId}/ga-pairs`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ updates: validatedGaPairs }) }); if (!response.ok) { let errorMessage = t('gaPairs.saveError'); if (response.status === 404) { errorMessage = 'GA Pairs save service is not available.'; } else { try { const errorResult = await response.json(); errorMessage = errorResult.error || errorMessage; } catch (parseError) { errorMessage = t('gaPairs.serverError', { status: response.status }); } } throw new Error(errorMessage); } const responseText = await response.text(); const result = responseText ? JSON.parse(responseText) : { success: true }; if (result.success) { // 更新本地状态为服务器返回的数据 const savedData = result.data || validatedGaPairs; setGaPairs(savedData); // 根据保存的GA对数量显示不同的成功消息 if (savedData.length === 0) { setSuccess(t('gaPairs.allPairsDeleted')); } else { setSuccess(t('gaPairs.pairsSaved', { count: savedData.length })); } onGaPairsChange?.(savedData); } else { throw new Error(result.error || t('gaPairs.saveOperationFailed')); } } catch (error) { console.error('Save GA pairs error:', error); setError(error.message); } finally { setSaving(false); } }; const handleGaPairChange = (index, field, value) => { const updatedGaPairs = [...gaPairs]; // 确保对象存在 if (!updatedGaPairs[index]) { console.error(`GA pair at index ${index} does not exist`); return; } updatedGaPairs[index] = { ...updatedGaPairs[index], [field]: value }; setGaPairs(updatedGaPairs); // 不立即调用 onGaPairsChange,等用户点击保存时再调用 }; const handleDeleteGaPair = index => { const updatedGaPairs = gaPairs.filter((_, i) => i !== index); setGaPairs(updatedGaPairs); onGaPairsChange?.(updatedGaPairs); }; const handleAddGaPair = () => { // 验证输入 if (!newGaPair.genreTitle?.trim() || !newGaPair.audienceTitle?.trim()) { setError(t('gaPairs.requiredFields')); return; } // 创建新的GA对对象 const newPair = { id: `temp_${Date.now()}`, // 临时ID genreTitle: newGaPair.genreTitle.trim(), genreDesc: newGaPair.genreDesc?.trim() || '', audienceTitle: newGaPair.audienceTitle.trim(), audienceDesc: newGaPair.audienceDesc?.trim() || '', isActive: true }; const updatedGaPairs = [...gaPairs, newPair]; setGaPairs(updatedGaPairs); onGaPairsChange?.(updatedGaPairs); // 重置表单并关闭对话框 setNewGaPair({ genreTitle: '', genreDesc: '', audienceTitle: '', audienceDesc: '', isActive: true }); setAddDialogOpen(false); setError(null); }; const resetMessages = () => { setError(null); setSuccess(null); }; const recoverFromBackup = () => { setGaPairs([...backupGaPairs]); setError(null); setSuccess(t('gaPairs.restoredFromBackup')); }; useEffect(() => { if (error || success) { const timer = setTimeout(resetMessages, 5000); return () => clearTimeout(timer); } }, [error, success]); if (loading) { return ( {t('gaPairs.loading')} ); } return ( {/* Header with action buttons */} {t('gaPairs.title')} {/* 右上角按钮为手动添加GA对 */} {/* Error/Success Messages */} {error && ( 0 && ( ) } onClose={resetMessages} > {error} )} {success && ( {success} )} {/* Generate GA Pairs Section - 只在没有GA对时显示 */} {gaPairs.length === 0 && ( {t('gaPairs.noGaPairsTitle')} {t('gaPairs.noGaPairsDescription')} )} {/* GA Pairs List */} {gaPairs.length > 0 && ( {t('gaPairs.activePairs', { active: gaPairs.filter(pair => pair.isActive).length, total: gaPairs.length })} {gaPairs.map((pair, index) => ( {t('gaPairs.pairNumber', { number: index + 1 })} handleGaPairChange(index, 'isActive', e.target.checked)} size="small" /> } label={t('gaPairs.active')} /> {/* 添加删除按钮 */} handleDeleteGaPair(index)}> handleGaPairChange(index, 'genreTitle', e.target.value)} multiline rows={2} fullWidth disabled={!pair.isActive} /> handleGaPairChange(index, 'genreDesc', e.target.value)} multiline rows={2} fullWidth disabled={!pair.isActive} /> handleGaPairChange(index, 'audienceTitle', e.target.value)} multiline rows={2} fullWidth disabled={!pair.isActive} /> handleGaPairChange(index, 'audienceDesc', e.target.value)} multiline rows={2} fullWidth disabled={!pair.isActive} /> ))} {/* 在GA对列表下方添加生成按钮 */} )} {/* Add GA Pair Dialog */} setAddDialogOpen(false)} maxWidth="md" fullWidth> {t('gaPairs.addDialogTitle')} setNewGaPair({ ...newGaPair, genreTitle: e.target.value })} fullWidth required placeholder={t('gaPairs.genreTitlePlaceholder')} /> setNewGaPair({ ...newGaPair, genreDesc: e.target.value })} multiline rows={3} fullWidth placeholder={t('gaPairs.genreDescPlaceholder')} /> setNewGaPair({ ...newGaPair, audienceTitle: e.target.value })} fullWidth required placeholder={t('gaPairs.audienceTitlePlaceholder')} /> setNewGaPair({ ...newGaPair, audienceDesc: e.target.value })} multiline rows={3} fullWidth placeholder={t('gaPairs.audienceDescPlaceholder')} /> setNewGaPair({ ...newGaPair, isActive: e.target.checked })} /> } label={t('gaPairs.active')} /> ); }