first-update
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
import { Tabs, Tab } from '@mui/material';
|
||||
|
||||
/**
|
||||
* 顶部分类选择标签页组件
|
||||
*/
|
||||
const CategoryTabs = ({ categoryEntries, selectedCategory, currentLanguage, onCategoryChange }) => {
|
||||
return (
|
||||
<Tabs
|
||||
value={selectedCategory}
|
||||
onChange={(e, newValue) => {
|
||||
onCategoryChange(newValue);
|
||||
}}
|
||||
variant="scrollable"
|
||||
scrollButtons="auto"
|
||||
sx={{ borderBottom: 1, borderColor: 'divider', mb: 3 }}
|
||||
>
|
||||
{categoryEntries.map(([categoryKey, categoryConfig]) => (
|
||||
<Tab key={categoryKey} label={categoryConfig.displayName[currentLanguage]} value={categoryKey} />
|
||||
))}
|
||||
</Tabs>
|
||||
);
|
||||
};
|
||||
|
||||
export default CategoryTabs;
|
||||
@@ -0,0 +1,92 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Card, CardContent, Box, Typography, Chip, Button, Paper } from '@mui/material';
|
||||
import { Edit as EditIcon, Restore as RestoreIcon } from '@mui/icons-material';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
|
||||
import 'github-markdown-css/github-markdown-light.css';
|
||||
|
||||
/**
|
||||
* 右侧提示词详情展示组件
|
||||
*/
|
||||
const PromptDetail = ({
|
||||
currentPromptConfig,
|
||||
selectedPrompt,
|
||||
promptContent,
|
||||
isCustomized,
|
||||
onEditClick,
|
||||
onDeleteClick
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!currentPromptConfig) {
|
||||
return (
|
||||
<Box sx={{ p: 3, textAlign: 'center', color: 'text.secondary' }}>{t('settings.prompts.selectPromptFirst')}</Box>
|
||||
);
|
||||
}
|
||||
|
||||
const handleEditClick = () => {
|
||||
onEditClick();
|
||||
};
|
||||
|
||||
const handleDeleteClick = () => {
|
||||
onDeleteClick();
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardContent>
|
||||
{/* 标题、描述与操作区域 */}
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
gap: 2,
|
||||
flexWrap: 'wrap'
|
||||
}}
|
||||
>
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<Typography variant="h6">{currentPromptConfig.name}</Typography>
|
||||
{isCustomized(selectedPrompt) && (
|
||||
<Chip label={t('settings.prompts.customized')} color="primary" size="small" />
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
<Button startIcon={<EditIcon />} variant="contained" size="small" onClick={handleEditClick}>
|
||||
{t('settings.prompts.editPrompt')}
|
||||
</Button>
|
||||
|
||||
{isCustomized(selectedPrompt) && (
|
||||
<Button startIcon={<RestoreIcon />} color="error" size="small" onClick={handleDeleteClick}>
|
||||
{t('settings.prompts.restoreDefault')}
|
||||
</Button>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
|
||||
{currentPromptConfig.description}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Markdown 渲染提示词内容 */}
|
||||
<Paper
|
||||
variant="outlined"
|
||||
sx={{
|
||||
p: 2,
|
||||
overflow: 'auto'
|
||||
}}
|
||||
>
|
||||
<div className="markdown-body">
|
||||
<ReactMarkdown>{promptContent}</ReactMarkdown>
|
||||
</div>
|
||||
</Paper>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default PromptDetail;
|
||||
@@ -0,0 +1,71 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
TextField,
|
||||
Box,
|
||||
Typography,
|
||||
Chip
|
||||
} from '@mui/material';
|
||||
import SaveIcon from '@mui/icons-material/Save';
|
||||
import RestoreIcon from '@mui/icons-material/Restore';
|
||||
|
||||
/**
|
||||
* 提示词编辑对话框组件
|
||||
*/
|
||||
const PromptEditDialog = ({
|
||||
open,
|
||||
title,
|
||||
promptType,
|
||||
promptKey,
|
||||
content,
|
||||
loading,
|
||||
onClose,
|
||||
onSave,
|
||||
onRestore,
|
||||
onContentChange
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Dialog open={open} onClose={onClose} maxWidth="lg" fullWidth>
|
||||
<DialogTitle>{title}</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{t('settings.prompts.promptType')}: {promptType}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{t('settings.prompts.keyName')}: {promptKey}
|
||||
</Typography>
|
||||
</Box>
|
||||
<TextField
|
||||
fullWidth
|
||||
multiline
|
||||
rows={15}
|
||||
value={content}
|
||||
onChange={e => onContentChange(e.target.value)}
|
||||
placeholder={t('settings.prompts.contentPlaceholder')}
|
||||
variant="outlined"
|
||||
/>
|
||||
|
||||
<Box display="flex" gap={1}>
|
||||
<Button startIcon={<RestoreIcon />} onClick={onRestore} size="small" variant="outlined">
|
||||
{t('settings.prompts.restoreDefaultContent')}
|
||||
</Button>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>{t('common.cancel')}</Button>
|
||||
<Button onClick={onSave} variant="contained" disabled={loading} startIcon={<SaveIcon />}>
|
||||
{t('common.save')}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default PromptEditDialog;
|
||||
@@ -0,0 +1,81 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Box, Tabs, Tab, Typography, Chip } from '@mui/material';
|
||||
import { shouldShowPrompt } from './promptUtils';
|
||||
|
||||
/**
|
||||
* 左侧提示词列表组件
|
||||
*/
|
||||
const PromptList = ({
|
||||
currentCategory,
|
||||
currentCategoryConfig,
|
||||
selectedPrompt,
|
||||
currentLanguage,
|
||||
isCustomized,
|
||||
onPromptSelect
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!currentCategoryConfig?.prompts) {
|
||||
return (
|
||||
<Typography variant="body2" color="text.secondary" align="center">
|
||||
{t('settings.prompts.noPromptsAvailable')}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
orientation="vertical"
|
||||
value={selectedPrompt || ''}
|
||||
onChange={(e, newValue) => onPromptSelect(newValue)}
|
||||
variant="scrollable"
|
||||
scrollButtons="auto"
|
||||
sx={{
|
||||
borderRight: 1,
|
||||
borderColor: 'divider',
|
||||
'& .MuiTabs-indicator': {
|
||||
left: 0,
|
||||
right: 'auto'
|
||||
},
|
||||
'& .MuiTab-root': {
|
||||
alignItems: 'flex-start',
|
||||
textAlign: 'left'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{currentCategoryConfig &&
|
||||
Object.entries(currentCategoryConfig.prompts).map(([promptKey, promptConfig]) => {
|
||||
if (!shouldShowPrompt(promptKey, currentLanguage)) return null;
|
||||
|
||||
const customized = isCustomized(promptKey);
|
||||
|
||||
return (
|
||||
<Tab
|
||||
key={promptKey}
|
||||
value={promptKey}
|
||||
label={
|
||||
<Box sx={{ textAlign: 'left', width: '100%' }}>
|
||||
<Typography variant="body2" sx={{ fontWeight: 500 }}>
|
||||
{promptConfig.name}
|
||||
</Typography>
|
||||
{customized && (
|
||||
<Chip label={t('settings.prompts.customized')} color="primary" size="small" sx={{ mt: 0.5 }} />
|
||||
)}
|
||||
</Box>
|
||||
}
|
||||
sx={{
|
||||
alignItems: 'flex-start',
|
||||
minHeight: 60,
|
||||
px: 2,
|
||||
justifyContent: 'flex-start',
|
||||
width: '100%'
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Tabs>
|
||||
);
|
||||
};
|
||||
|
||||
export default PromptList;
|
||||
@@ -0,0 +1,400 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* 提示词设置相关工具函数
|
||||
*/
|
||||
|
||||
/**
|
||||
* 从提示词键名解析语言
|
||||
* @param {string} promptKey 提示词键名
|
||||
* @returns {string} 语言代码 ('zh-CN' 或 'en')
|
||||
*/
|
||||
export const getLanguageFromPromptKey = promptKey => {
|
||||
return promptKey?.endsWith('_EN') ? 'en' : 'zh-CN';
|
||||
};
|
||||
|
||||
/**
|
||||
* 判断是否应该显示当前提示词(基于语言)
|
||||
* @param {string} promptKey 提示词键名
|
||||
* @param {string} currentLanguage 当前界面语言
|
||||
* @returns {boolean} 是否应该显示
|
||||
*/
|
||||
export const shouldShowPrompt = (promptKey, currentLanguage) => {
|
||||
const promptLang = getLanguageFromPromptKey(promptKey);
|
||||
return promptLang === currentLanguage;
|
||||
};
|
||||
|
||||
/**
|
||||
* 构建提示词标题显示组件
|
||||
* @param {Object} options 配置项
|
||||
* @param {string} options.name 提示词名称
|
||||
* @param {boolean} options.customized 是否已自定义
|
||||
* @returns {Object} 包含名称和自定义标记的显示配置
|
||||
*/
|
||||
export const buildPromptTitle = ({ name, customized }) => {
|
||||
return { name, customized };
|
||||
};
|
||||
Reference in New Issue
Block a user