401 lines
12 KiB
JavaScript
401 lines
12 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
|
import { useParams } from 'next/navigation';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { Box, Grid, Card, CardContent } from '@mui/material';
|
|
import { fetchWithRetry } from '@/lib/util/request';
|
|
import { useSnackbar } from '@/hooks/useSnackbar';
|
|
|
|
// 导入拆分后的组件
|
|
import CategoryTabs from './CategoryTabs';
|
|
import PromptList from './PromptList';
|
|
import PromptDetail from './PromptDetail';
|
|
import PromptEditDialog from './PromptEditDialog';
|
|
import { getLanguageFromPromptKey, shouldShowPrompt } from './promptUtils';
|
|
|
|
/**
|
|
* 提示词设置主组件
|
|
*/
|
|
export default function PromptSettings() {
|
|
const { projectId } = useParams();
|
|
const { i18n, t } = useTranslation();
|
|
const { showSuccess, showErrorMessage, SnackbarComponent } = useSnackbar();
|
|
|
|
// 基础状态
|
|
const [currentLanguage, setCurrentLanguage] = useState(i18n.language === 'en' ? 'en' : 'zh-CN');
|
|
const [loading, setLoading] = useState(false);
|
|
const [templates, setTemplates] = useState({});
|
|
const [customPrompts, setCustomPrompts] = useState([]);
|
|
|
|
// 当前选中状态
|
|
const [selectedCategory, setSelectedCategory] = useState(null);
|
|
const [selectedPrompt, setSelectedPrompt] = useState(null);
|
|
const [promptContent, setPromptContent] = useState('');
|
|
|
|
// 编辑对话框状态
|
|
const [editDialog, setEditDialog] = useState({
|
|
open: false,
|
|
promptType: '',
|
|
promptKey: '',
|
|
language: '',
|
|
content: '',
|
|
defaultContent: '',
|
|
isNew: false
|
|
});
|
|
|
|
// ======= 数据加载与初始化 =======
|
|
|
|
// 加载提示词数据
|
|
useEffect(() => {
|
|
loadPromptData();
|
|
}, [projectId, currentLanguage]);
|
|
|
|
// 监听语言变化
|
|
useEffect(() => {
|
|
const newLang = i18n.language === 'en' ? 'en' : 'zh-CN';
|
|
if (newLang !== currentLanguage) {
|
|
setCurrentLanguage(newLang);
|
|
}
|
|
}, [i18n.language, currentLanguage]);
|
|
|
|
// 监听选中提示词变化
|
|
useEffect(() => {
|
|
if (selectedPrompt) {
|
|
loadPromptContent();
|
|
}
|
|
}, [selectedPrompt]);
|
|
|
|
// 初始化选择第一个分类和提示词
|
|
useEffect(() => {
|
|
if (Object.keys(templates).length > 0 && currentLanguage && !selectedCategory) {
|
|
const firstCategory = Object.keys(templates)[0];
|
|
setSelectedCategory(firstCategory);
|
|
|
|
// 根据当前语言环境选择第一个匹配的提示词
|
|
const promptEntries = Object.keys(templates[firstCategory]?.prompts || {});
|
|
const firstPrompt = promptEntries.find(promptKey => shouldShowPrompt(promptKey, currentLanguage));
|
|
|
|
if (firstPrompt) {
|
|
setSelectedPrompt(firstPrompt);
|
|
}
|
|
}
|
|
}, [templates, selectedCategory, currentLanguage]);
|
|
|
|
// ======= API 操作函数 =======
|
|
|
|
// 加载提示词数据
|
|
const loadPromptData = async () => {
|
|
try {
|
|
setLoading(true);
|
|
const response = await fetchWithRetry(`/api/projects/${projectId}/custom-prompts?language=${currentLanguage}`);
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
setTemplates(data.templates);
|
|
setCustomPrompts(data.customPrompts);
|
|
} else {
|
|
showErrorMessage(data.message || '加载提示词数据失败');
|
|
}
|
|
} catch (error) {
|
|
console.error('加载提示词数据出错:', error);
|
|
showErrorMessage('加载提示词数据失败');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
// 加载提示词内容
|
|
const loadPromptContent = async (forceRefresh = false) => {
|
|
if (!selectedPrompt) return;
|
|
try {
|
|
setLoading(true);
|
|
const content = await getCurrentPromptContent(selectedPrompt, forceRefresh);
|
|
setPromptContent(content);
|
|
} catch (error) {
|
|
console.error('加载提示词内容出错:', error);
|
|
showErrorMessage('加载提示词内容失败');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
// 加载默认提示词内容
|
|
const loadDefaultContent = async (promptType, promptKey) => {
|
|
if (i18n.language === 'en' && !promptKey.endsWith('_EN')) {
|
|
promptKey += '_EN';
|
|
}
|
|
try {
|
|
const response = await fetchWithRetry(
|
|
`/api/projects/${projectId}/default-prompts?promptType=${promptType}&promptKey=${promptKey}`
|
|
);
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
return data.content;
|
|
}
|
|
return '';
|
|
} catch (error) {
|
|
console.error('加载默认提示词内容出错:', error);
|
|
return '';
|
|
}
|
|
};
|
|
|
|
// ======= 交互处理函数 =======
|
|
|
|
// 处理编辑提示词
|
|
const handleEditPrompt = async (promptType, promptKey, language) => {
|
|
const existingPrompt = customPrompts.find(
|
|
p => p.promptType === promptType && p.promptKey === promptKey && p.language === language
|
|
);
|
|
|
|
const defaultContent = await loadDefaultContent(promptType, promptKey);
|
|
|
|
setEditDialog({
|
|
open: true,
|
|
promptType,
|
|
promptKey,
|
|
language,
|
|
content: existingPrompt?.content || defaultContent,
|
|
defaultContent,
|
|
isNew: !existingPrompt
|
|
});
|
|
};
|
|
|
|
// 处理删除提示词
|
|
const handleDeletePrompt = async (promptType, promptKey, language) => {
|
|
try {
|
|
setLoading(true);
|
|
const query = new URLSearchParams({
|
|
promptType,
|
|
promptKey,
|
|
language
|
|
}).toString();
|
|
|
|
const response = await fetchWithRetry(`/api/projects/${projectId}/custom-prompts?${query}`, {
|
|
method: 'DELETE'
|
|
});
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
showSuccess(t('settings.prompts.restoreSuccess'));
|
|
// 先重新加载数据,然后强制刷新内容
|
|
await loadPromptData();
|
|
await loadPromptContent(true); // 强制刷新
|
|
} else {
|
|
showErrorMessage(data.message || t('settings.prompts.restoreFailed'));
|
|
}
|
|
} catch (error) {
|
|
console.error(t('settings.prompts.deleteError'), error);
|
|
showErrorMessage(t('settings.prompts.restoreFailed'));
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
// 处理保存提示词
|
|
const handleSavePrompt = async () => {
|
|
try {
|
|
setLoading(true);
|
|
const { promptType, promptKey, language, content } = editDialog;
|
|
|
|
const response = await fetchWithRetry(`/api/projects/${projectId}/custom-prompts`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ promptType, promptKey, language, content })
|
|
});
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
showSuccess(t('settings.prompts.saveSuccess'));
|
|
setEditDialog({ ...editDialog, open: false });
|
|
// 先重新加载数据,然后强制刷新内容
|
|
await loadPromptData();
|
|
await loadPromptContent(true); // 强制刷新
|
|
} else {
|
|
showErrorMessage(data.message || t('settings.prompts.saveFailed'));
|
|
}
|
|
} catch (error) {
|
|
console.error(t('settings.prompts.saveError'), error);
|
|
showErrorMessage(t('settings.prompts.saveFailed'));
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
// 恢复默认内容
|
|
const handleRestoreDefault = () => {
|
|
setEditDialog(prev => ({
|
|
...prev,
|
|
content: prev.defaultContent
|
|
}));
|
|
};
|
|
|
|
// ======= 工具函数 =======
|
|
|
|
// 检查提示词是否已自定义
|
|
const isCustomized = promptKey => {
|
|
if (!selectedCategory || !promptKey || !templates[selectedCategory]) return false;
|
|
|
|
const language = getLanguageFromPromptKey(promptKey);
|
|
const promptType = templates[selectedCategory]?.prompts?.[promptKey]?.type;
|
|
|
|
if (!promptType) return false;
|
|
|
|
return customPrompts.some(p => p.promptType === promptType && p.promptKey === promptKey && p.language === language);
|
|
};
|
|
|
|
// 获取当前提示词内容(直接从服务器获取最新数据)
|
|
const getCurrentPromptContent = async (promptKey, forceRefresh = false) => {
|
|
if (!selectedCategory || !promptKey || !templates[selectedCategory]) return '';
|
|
|
|
const language = getLanguageFromPromptKey(promptKey);
|
|
const promptType = templates[selectedCategory]?.prompts?.[promptKey]?.type;
|
|
|
|
if (!promptType) {
|
|
return '';
|
|
}
|
|
|
|
// 如果需要强制刷新,直接从服务器获取
|
|
if (forceRefresh) {
|
|
try {
|
|
const response = await fetchWithRetry(
|
|
`/api/projects/${projectId}/custom-prompts?promptType=${promptType}&language=${language}`
|
|
);
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
const existingPrompt = data.customPrompts.find(
|
|
p => p.promptType === promptType && p.promptKey === promptKey && p.language === language
|
|
);
|
|
|
|
if (existingPrompt) {
|
|
return existingPrompt.content;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error(t('settings.prompts.fetchContentError'), error);
|
|
}
|
|
} else {
|
|
// 使用缓存的状态
|
|
const existingPrompt = customPrompts.find(
|
|
p => p.promptType === promptType && p.promptKey === promptKey && p.language === language
|
|
);
|
|
|
|
if (existingPrompt) {
|
|
return existingPrompt.content;
|
|
}
|
|
}
|
|
|
|
// 回退到默认内容
|
|
return await loadDefaultContent(promptType, promptKey);
|
|
};
|
|
|
|
// ======= 数据准备 =======
|
|
|
|
// 当前分类的配置
|
|
const currentCategoryConfig = templates[selectedCategory];
|
|
|
|
// 当前提示词的配置
|
|
const currentPromptConfig = currentCategoryConfig?.prompts?.[selectedPrompt];
|
|
|
|
// 分类配置项
|
|
const categoryEntries = Object.entries(templates);
|
|
|
|
// 处理分类变更
|
|
const handleCategoryChange = newCategory => {
|
|
setSelectedCategory(newCategory);
|
|
// 根据当前语言环境选择第一个匹配的提示词
|
|
const promptEntries = Object.keys(templates[newCategory]?.prompts || {});
|
|
console.log('所有提示词:', promptEntries);
|
|
|
|
const firstPrompt = promptEntries.find(promptKey => shouldShowPrompt(promptKey, currentLanguage));
|
|
|
|
setSelectedPrompt(firstPrompt);
|
|
};
|
|
|
|
// 处理编辑按钮点击
|
|
const handleEditButtonClick = () => {
|
|
const promptType = templates[selectedCategory]?.prompts?.[selectedPrompt]?.type;
|
|
// 使用当前界面语言而不是从 promptKey 推断的语言
|
|
const language = currentLanguage;
|
|
|
|
if (promptType) {
|
|
handleEditPrompt(promptType, selectedPrompt, language);
|
|
}
|
|
};
|
|
|
|
// 处理删除按钮点击
|
|
const handleDeleteButtonClick = () => {
|
|
const promptType = templates[selectedCategory]?.prompts?.[selectedPrompt]?.type;
|
|
// 使用当前界面语言而不是从 promptKey 推断的语言
|
|
const language = currentLanguage;
|
|
|
|
if (promptType) {
|
|
handleDeletePrompt(promptType, selectedPrompt, language);
|
|
}
|
|
};
|
|
|
|
// 处理对话框内容变更
|
|
const handleDialogContentChange = newContent => {
|
|
setEditDialog({ ...editDialog, content: newContent });
|
|
};
|
|
|
|
return (
|
|
<Box>
|
|
<SnackbarComponent />
|
|
|
|
{/* 主要分类选择 */}
|
|
<CategoryTabs
|
|
categoryEntries={categoryEntries}
|
|
selectedCategory={selectedCategory}
|
|
currentLanguage={currentLanguage}
|
|
onCategoryChange={handleCategoryChange}
|
|
/>
|
|
|
|
{/* 左右布局:左侧垂直提示词选择,右侧内容展示 */}
|
|
<Grid container spacing={3}>
|
|
{/* 左侧:垂直 TAB 选择具体提示词 */}
|
|
<Grid item xs={12} md={4} lg={3}>
|
|
<Card>
|
|
<CardContent>
|
|
<PromptList
|
|
currentCategory={selectedCategory}
|
|
currentCategoryConfig={currentCategoryConfig}
|
|
selectedPrompt={selectedPrompt}
|
|
currentLanguage={currentLanguage}
|
|
isCustomized={isCustomized}
|
|
onPromptSelect={setSelectedPrompt}
|
|
/>
|
|
</CardContent>
|
|
</Card>
|
|
</Grid>
|
|
|
|
{/* 右侧:提示词内容展示和操作 */}
|
|
<Grid item xs={12} md={8} lg={9}>
|
|
<PromptDetail
|
|
currentPromptConfig={currentPromptConfig}
|
|
selectedPrompt={selectedPrompt}
|
|
promptContent={promptContent}
|
|
isCustomized={isCustomized}
|
|
onEditClick={handleEditButtonClick}
|
|
onDeleteClick={handleDeleteButtonClick}
|
|
/>
|
|
</Grid>
|
|
</Grid>
|
|
|
|
{/* 编辑提示词对话框 */}
|
|
<PromptEditDialog
|
|
open={editDialog.open}
|
|
title={editDialog.isNew ? t('settings.prompts.createCustomPrompt') : t('settings.prompts.editPrompt')}
|
|
promptType={editDialog.promptType}
|
|
promptKey={editDialog.promptKey}
|
|
content={editDialog.content}
|
|
loading={loading}
|
|
onClose={() => setEditDialog({ ...editDialog, open: false })}
|
|
onSave={handleSavePrompt}
|
|
onRestore={handleRestoreDefault}
|
|
onContentChange={handleDialogContentChange}
|
|
/>
|
|
</Box>
|
|
);
|
|
}
|