Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

新增问题手工添加 #48

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 67 additions & 1 deletion app/api/projects/[projectId]/questions/route.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { NextResponse } from 'next/server';
import { getQuestions } from '@/lib/db/questions';
import { getDatasets } from '@/lib/db/datasets';

import getQuestionEnPrompt from '@/lib/llm/prompts/questionEn';
import getAddLabelPrompt from '@/lib/llm/prompts/addLabel';
import getAddLabelEnPrompt from '@/lib/llm/prompts/addLabelEn';
import { extractJsonFromLLMOutput } from '@/lib/llm/common/util';
import { addQuestionsManul } from '@/lib/db/questions';
import { getTags } from '@/lib/db/tags';
import LLMClient from '@/lib/llm/core/index';

// 获取项目的所有问题
export async function GET(request, { params }) {
Expand Down Expand Up @@ -43,3 +49,63 @@ export async function GET(request, { params }) {
return NextResponse.json({ error: error.message || '获取问题列表失败' }, { status: 500 });
}
}

// 手动创建问题
export async function POST(request, { params }) {
try {
const { projectId } = params;
console.log("请求信息:",request);
// 获取请求体
const { model, language = '中文', question, chunkId } = await request.json();

if (!model) {
return NextResponse.json({ error: '请选择模型' }, { status: 400 });
}
// 验证必要的字段
if (!projectId || !question || !chunkId) {
return Response.json({ error: '项目ID,问题,文本块不能为空' }, { status: 400 });
}

// 创建LLM客户端
const llmClient = new LLMClient({
provider: model.provider,
endpoint: model.endpoint,
apiKey: model.apiKey,
model: model.name,
});
// 打标签
const tags = await getTags(projectId);
console.log('Tags:', tags);
// 根据语言选择相应的标签提示词函数
const labelPromptFunc = language === 'en' ? getAddLabelEnPrompt : getAddLabelPrompt;
const labelPrompt = labelPromptFunc(JSON.stringify(tags), JSON.stringify(question));
const llmLabelRes = await llmClient.chat(labelPrompt);
const labelResponse = llmLabelRes.choices?.[0]?.message?.content ||
llmLabelRes.response ||
'';
// let data = [
// {
// "question": "storybook能干什么111?",
// "label": "2.2 RN Storybook (ondevice)"
// }
// ];

// console.log('LLM Label Response:', labelResponse);
// 从LLM输出中提取JSON格式的问题列表
const labelQuestions = extractJsonFromLLMOutput(labelResponse);
console.log(projectId, chunkId, 'Label Questions:', labelQuestions);

// 保存问题到数据库
await addQuestionsManul(projectId, chunkId, labelQuestions);

// 返回生成的问题
return NextResponse.json({
chunkId,
labelQuestions,
total: labelQuestions.length
});
} catch (error) {
console.error('创建问题出错:', error);
return Response.json({ error: error.message }, { status: 500 });
}
}
238 changes: 237 additions & 1 deletion app/projects/[projectId]/questions/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh';
import DeleteIcon from '@mui/icons-material/Delete';
import i18n from '@/lib/i18n';
import SearchIcon from '@mui/icons-material/Search';
import AddIcon from '@mui/icons-material/Add';
import QuestionListView from '@/components/questions/QuestionListView';
import QuestionTreeView from '@/components/questions/QuestionTreeView';
import TabPanel from '@/components/text-split/components/TabPanel';
Expand Down Expand Up @@ -67,6 +68,14 @@ export default function QuestionsPage({ params }) {
confirmAction: null
});

// 添加新问题对话框状态
const [addQuestionDialog, setAddQuestionDialog] = useState({
open: false,
question: '',
selectedChunkId: '',
error: ''
});

const fetchData = async (currentPage) => {
if (!currentPage) {
setLoading(true);
Expand Down Expand Up @@ -633,6 +642,154 @@ export default function QuestionsPage({ params }) {
confirmBatchDeleteQuestions();
};

const selectedModelId = localStorage.getItem('selectedModelId');
let model = null;

// 尝试从 localStorage 获取完整的模型信息
const modelInfoStr = localStorage.getItem('selectedModelInfo');

if (modelInfoStr) {
try {
model = JSON.parse(modelInfoStr);
} catch (e) {
console.error(t('解析模型信息出错:'), e);
// 继续执行,将在下面尝试获取模型信息
}
}

// 如果仍然没有模型信息,抛出错误
if (!selectedModelId) {
throw new Error(t('textSplit.selectModelFirst'));
}

if (!model) {
throw new Error(t('textSplit.modelNotAvailable'));
}


// 处理打开添加问题对话框
const handleOpenAddQuestionDialog = () => {
// 默认选择第一个文本块,如果有的话
const defaultChunkId = chunks.length > 0 ? chunks[0].id : '';
setAddQuestionDialog({
open: true,
question: '',
selectedChunkId: defaultChunkId,
error: ''
});
};

// 处理关闭添加问题对话框
const handleCloseAddQuestionDialog = () => {
setAddQuestionDialog({
...addQuestionDialog,
open: false
});
};

// 处理添加问题表单变化
const handleAddQuestionChange = (field, value) => {
setAddQuestionDialog({
...addQuestionDialog,
[field]: value,
error: ''
});
};

// 处理提交添加问题
const handleSubmitAddQuestion = async () => {
try {
// 验证表单
if (!addQuestionDialog.question.trim()) {
setAddQuestionDialog({
...addQuestionDialog,
error: t('questions.questionRequired')
});
return;
}

if (!addQuestionDialog.selectedChunkId) {
setAddQuestionDialog({
...addQuestionDialog,
error: t('questions.chunkRequired')
});
return;
}

setProcessing(true);
setError(null);

// 重置进度状态
setProgress({
total: 1,
completed: 0,
percentage: 0,
questionCount: 0
});


const currentLanguage = i18n.language === 'zh-CN' ? '中文' : 'en';
// 调用API添加问题
const response = await fetch(`/api/projects/${projectId}/questions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ model, language: currentLanguage,
question: addQuestionDialog.question,
chunkId: addQuestionDialog.selectedChunkId
})
});

if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || t('questions.addFailed'));
}
//setError({ severity: 'success', message: t('questions.questionAddSuccess') });
// 获取新添加的问题数据
const newQuestion = await response.json();
// 更新问题列表
// 将嵌套的问题数据结构拍平
const flattenedQuestions = [];

newQuestion.labelQuestions.forEach(item => {
const { question, label } = item;
flattenedQuestions.push({
question,
label,
chunkId: addQuestionDialog.selectedChunkId,
});
});
setQuestions(prev => [...prev, ...flattenedQuestions]);

// 关闭对话框
handleCloseAddQuestionDialog();
setSnackbar({
open: true,
message: t('question.addSuccess'),
severity: 'success'
});
} catch (error) {
console.error("消息新增失败", error);
setSnackbar({
open: true,
message: error.message || t('questions.addFailed'),
severity: 'error'
});
}finally {
setProcessing(false);
// 重置进度状态
setTimeout(() => {
setProgress({
total: 0,
completed: 0,
percentage: 0,
questionCount: 0
});
}, 1000); // 延迟重置,让用户看到完成的进度
}
};

// 获取文本块内容
const getChunkContent = (chunkId) => {
const chunk = chunks.find(c => c.id === chunkId);
Expand All @@ -650,6 +807,7 @@ export default function QuestionsPage({ params }) {
}

if (error) {
//console.error('Error fetching project:', error);
return (
<Container maxWidth="lg" sx={{ mt: 4 }}>
<Alert severity="error" sx={{ mb: 2 }}>
Expand Down Expand Up @@ -795,7 +953,18 @@ export default function QuestionsPage({ params }) {
</Typography>
</Box>

<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
{/* 添加新问题按钮 */}
<Button
variant="outlined"
color="primary"
startIcon={<AddIcon />}
onClick={handleOpenAddQuestionDialog}
size="small"
>
{t('questions.addNewQuestion')}
</Button>

<TextField
placeholder={t('questions.searchPlaceholder')}
variant="outlined"
Expand Down Expand Up @@ -896,6 +1065,73 @@ export default function QuestionsPage({ params }) {
</Button>
</DialogActions>
</Dialog>
{/* 添加问题对话框 */}
<Dialog
open={addQuestionDialog.open}
onClose={handleCloseAddQuestionDialog}
aria-labelledby="add-question-dialog-title"
maxWidth="sm"
fullWidth
>
<DialogTitle id="add-question-dialog-title">{t('questions.addNewQuestion')}</DialogTitle>
<DialogContent>
<Box sx={{ mt: 2 }}>
<TextField
label={t('questions.questionText')}
fullWidth
multiline
rows={3}
value={addQuestionDialog.question}
onChange={(e) => handleAddQuestionChange('question', e.target.value)}
error={!!addQuestionDialog.error && !addQuestionDialog.question.trim()}
helperText={!addQuestionDialog.question.trim() && addQuestionDialog.error ? addQuestionDialog.error : ''}
sx={{ mb: 3 }}
/>

<TextField
select
label={t('questions.selectChunk')}
fullWidth
value={addQuestionDialog.selectedChunkId}
onChange={(e) => handleAddQuestionChange('selectedChunkId', e.target.value)}
error={!!addQuestionDialog.error && !addQuestionDialog.selectedChunkId}
helperText={!addQuestionDialog.selectedChunkId && addQuestionDialog.error ? t('questions.chunkRequired') : ''}
SelectProps={{
native: true,
}}
>
<option value="">{t('questions.selectChunkPlaceholder')}</option>
{chunks.map((chunk) => (
<option key={chunk.id} value={chunk.id}>
{chunk.title || chunk.id} ({chunk.content.substring(0, 50)}...)
</option>
))}
</TextField>

{addQuestionDialog.selectedChunkId && (
<Box sx={{ mt: 2, p: 2, bgcolor: 'background.paper', borderRadius: 1, border: '1px solid', borderColor: 'divider' }}>
<Typography variant="subtitle2" gutterBottom>
{t('questions.chunkPreview')}:
</Typography>
<Typography variant="body2" sx={{ whiteSpace: 'pre-wrap', maxHeight: '150px', overflow: 'auto' }}>
{getChunkContent(addQuestionDialog.selectedChunkId)}
</Typography>
</Box>
)}
</Box>
</DialogContent>
<DialogActions>
<Button onClick={handleCloseAddQuestionDialog}>{t('common.cancel')}</Button>
<Button
onClick={handleSubmitAddQuestion}
variant="contained"
color="primary"
disabled={!addQuestionDialog.question.trim() || !addQuestionDialog.selectedChunkId}
>
{t('common.add')}
</Button>
</DialogActions>
</Dialog>
</Container>
);
}
27 changes: 27 additions & 0 deletions lib/db/questions.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,30 @@ export async function batchDeleteQuestions(projectId, questionsToDelete) {
// 保存更新后的问题列表
return await saveQuestions(projectId, questions);
}


/**
* 手工添加问题到项目
* @param {string} projectId - 项目ID
* @param {string} chunkId - 文本块ID
* @param {Array} newQuestions - 新问题列表
* @returns {Promise<Array>} - 更新后的问题列表
*/
export async function addQuestionsManul(projectId, chunkId, newQuestions) {
const questions = await getQuestions(projectId);
console.log("newQuestions:",newQuestions);
// 检查是否已存在该文本块的问题
const existingIndex = questions.findIndex(item => item.chunkId === chunkId);
console.log("existingIndex:",questions[existingIndex]);
if (existingIndex >= 0) {
// 更新现有问题
if(!questions[existingIndex].questions){
questions[existingIndex].questions = newQuestions;
}else{
const mergedQuestions = [...new Set([...questions[existingIndex].questions, ...newQuestions])];
questions[existingIndex].questions=mergedQuestions;
}
}

return await saveQuestions(projectId, questions);
}
Loading