Showing preview only (2,972K chars total). Download the full file or copy to clipboard to get everything.
Repository: ConardLi/easy-dataset
Branch: main
Commit: 75bfca751400
Files: 516
Total size: 2.7 MB
Directory structure:
gitextract_cydaglf7/
├── .dockerignore
├── .gitattributes
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ ├── feature-or-enhancement-.md
│ │ └── question.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ └── workflows/
│ └── docker-build.yml
├── .gitignore
├── .husky/
│ ├── commit-msg
│ └── pre-commit
├── .npmrc
├── .prettierrc.js
├── .windsurfrules
├── AGENTS.md
├── ARCHITECTURE.md
├── Dockerfile
├── LICENSE
├── README.md
├── README.tr.md
├── README.zh-CN.md
├── app/
│ ├── api/
│ │ ├── check-update/
│ │ │ └── route.js
│ │ ├── llm/
│ │ │ ├── fetch-models/
│ │ │ │ └── route.js
│ │ │ ├── model/
│ │ │ │ └── route.js
│ │ │ ├── ollama/
│ │ │ │ └── models/
│ │ │ │ └── route.js
│ │ │ └── providers/
│ │ │ └── route.js
│ │ ├── monitoring/
│ │ │ ├── logs/
│ │ │ │ └── route.js
│ │ │ ├── stats/
│ │ │ │ └── route.js
│ │ │ └── summary/
│ │ │ └── route.js
│ │ ├── projects/
│ │ │ ├── [projectId]/
│ │ │ │ ├── batch-add-manual-ga/
│ │ │ │ │ └── route.js
│ │ │ │ ├── batch-delete-files/
│ │ │ │ │ └── route.js
│ │ │ │ ├── batch-generateGA/
│ │ │ │ │ └── route.js
│ │ │ │ ├── blind-test-tasks/
│ │ │ │ │ ├── [taskId]/
│ │ │ │ │ │ ├── current/
│ │ │ │ │ │ │ └── route.js
│ │ │ │ │ │ ├── question/
│ │ │ │ │ │ │ └── route.js
│ │ │ │ │ │ ├── route.js
│ │ │ │ │ │ ├── stream/
│ │ │ │ │ │ │ └── route.js
│ │ │ │ │ │ ├── stream-model/
│ │ │ │ │ │ │ └── route.js
│ │ │ │ │ │ └── vote/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ └── route.js
│ │ │ │ ├── chunks/
│ │ │ │ │ ├── [chunkId]/
│ │ │ │ │ │ ├── clean/
│ │ │ │ │ │ │ └── route.js
│ │ │ │ │ │ ├── eval-questions/
│ │ │ │ │ │ │ └── route.js
│ │ │ │ │ │ ├── questions/
│ │ │ │ │ │ │ └── route.js
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── batch-content/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── batch-edit/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── name/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ └── route.js
│ │ │ │ ├── config/
│ │ │ │ │ └── route.js
│ │ │ │ ├── custom-prompts/
│ │ │ │ │ └── route.js
│ │ │ │ ├── custom-split/
│ │ │ │ │ └── route.js
│ │ │ │ ├── dataset-conversations/
│ │ │ │ │ ├── [conversationId]/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── export/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── route.js
│ │ │ │ │ └── tags/
│ │ │ │ │ └── route.js
│ │ │ │ ├── datasets/
│ │ │ │ │ ├── [datasetId]/
│ │ │ │ │ │ ├── copy-to-eval/
│ │ │ │ │ │ │ └── route.js
│ │ │ │ │ │ ├── evaluate/
│ │ │ │ │ │ │ └── route.js
│ │ │ │ │ │ ├── route.js
│ │ │ │ │ │ └── token-count/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── batch-evaluate/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── export/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── generate-eval-variant/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── import/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── optimize/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── route.js
│ │ │ │ │ └── tags/
│ │ │ │ │ └── route.js
│ │ │ │ ├── default-prompts/
│ │ │ │ │ └── route.js
│ │ │ │ ├── distill/
│ │ │ │ │ ├── questions/
│ │ │ │ │ │ ├── by-tag/
│ │ │ │ │ │ │ └── route.js
│ │ │ │ │ │ └── route.js
│ │ │ │ │ └── tags/
│ │ │ │ │ ├── [tagId]/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── all/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ └── route.js
│ │ │ │ ├── eval-datasets/
│ │ │ │ │ ├── [evalId]/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── count/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── export/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── import/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── route.js
│ │ │ │ │ ├── sample/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ └── tags/
│ │ │ │ │ └── route.js
│ │ │ │ ├── eval-tasks/
│ │ │ │ │ ├── [taskId]/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ └── route.js
│ │ │ │ ├── files/
│ │ │ │ │ ├── [fileId]/
│ │ │ │ │ │ └── ga-pairs/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ └── route.js
│ │ │ │ ├── generate-questions/
│ │ │ │ │ └── route.js
│ │ │ │ ├── huggingface/
│ │ │ │ │ └── upload/
│ │ │ │ │ └── route.js
│ │ │ │ ├── image-datasets/
│ │ │ │ │ ├── [datasetId]/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── export/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── export-zip/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── route.js
│ │ │ │ │ └── tags/
│ │ │ │ │ └── route.js
│ │ │ │ ├── images/
│ │ │ │ │ ├── [imageId]/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── annotations/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── datasets/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── next-unanswered/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── pdf-convert/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── questions/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── route.js
│ │ │ │ │ └── zip-import/
│ │ │ │ │ └── route.js
│ │ │ │ ├── llamaFactory/
│ │ │ │ │ ├── checkConfig/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ └── generate/
│ │ │ │ │ └── route.js
│ │ │ │ ├── model-config/
│ │ │ │ │ ├── [modelConfigId]/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ └── route.js
│ │ │ │ ├── models/
│ │ │ │ │ ├── [modelId]/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ └── route.js
│ │ │ │ ├── playground/
│ │ │ │ │ └── chat/
│ │ │ │ │ ├── route.js
│ │ │ │ │ └── stream/
│ │ │ │ │ └── route.js
│ │ │ │ ├── preview/
│ │ │ │ │ └── [fileId]/
│ │ │ │ │ └── route.js
│ │ │ │ ├── questions/
│ │ │ │ │ ├── [questionId]/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── batch-delete/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── export/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── route.js
│ │ │ │ │ ├── templates/
│ │ │ │ │ │ ├── [templateId]/
│ │ │ │ │ │ │ └── route.js
│ │ │ │ │ │ └── route.js
│ │ │ │ │ └── tree/
│ │ │ │ │ └── route.js
│ │ │ │ ├── route.js
│ │ │ │ ├── split/
│ │ │ │ │ └── route.js
│ │ │ │ ├── tags/
│ │ │ │ │ └── route.js
│ │ │ │ └── tasks/
│ │ │ │ ├── [taskId]/
│ │ │ │ │ └── route.js
│ │ │ │ ├── list/
│ │ │ │ │ └── route.js
│ │ │ │ └── route.js
│ │ │ ├── delete-directory/
│ │ │ │ └── route.js
│ │ │ ├── migrate/
│ │ │ │ └── route.js
│ │ │ ├── open-directory/
│ │ │ │ └── route.js
│ │ │ ├── route.js
│ │ │ └── unmigrated/
│ │ │ └── route.js
│ │ └── update/
│ │ └── route.js
│ ├── dataset-square/
│ │ └── page.js
│ ├── globals.css
│ ├── layout.js
│ ├── monitoring/
│ │ ├── components/
│ │ │ ├── Charts.js
│ │ │ ├── StatsCards.js
│ │ │ └── UsageTable.js
│ │ ├── hooks/
│ │ │ └── useMonitoringData.js
│ │ └── page.js
│ ├── page.js
│ └── projects/
│ └── [projectId]/
│ ├── blind-test-tasks/
│ │ ├── [taskId]/
│ │ │ └── page.js
│ │ ├── components/
│ │ │ ├── BlindTestHeader.js
│ │ │ ├── BlindTestInProgress.js
│ │ │ ├── BlindTestTaskCard.js
│ │ │ ├── CreateBlindTestDialog.js
│ │ │ ├── ResultDetailList.js
│ │ │ └── ResultSummary.js
│ │ ├── hooks/
│ │ │ ├── useBlindTestDetail.js
│ │ │ └── useBlindTestTasks.js
│ │ └── page.js
│ ├── datasets/
│ │ ├── [datasetId]/
│ │ │ ├── page.js
│ │ │ └── useDatasetDetails.js
│ │ ├── components/
│ │ │ ├── ActionBar.js
│ │ │ ├── DatasetList.js
│ │ │ ├── DeleteConfirmDialog.js
│ │ │ ├── FilterDialog.js
│ │ │ └── SearchBar.js
│ │ ├── hooks/
│ │ │ ├── useDatasetEvaluation.js
│ │ │ ├── useDatasetExport.js
│ │ │ └── useDatasetFilters.js
│ │ └── page.js
│ ├── distill/
│ │ ├── autoDistillService.js
│ │ └── page.js
│ ├── eval-datasets/
│ │ ├── [evalId]/
│ │ │ ├── page.js
│ │ │ └── useEvalDatasetDetails.js
│ │ ├── components/
│ │ │ ├── BuiltinDatasetDialog.js
│ │ │ ├── EvalDatasetCard.js
│ │ │ ├── EvalDatasetHeader.js
│ │ │ ├── EvalDatasetList.js
│ │ │ ├── EvalEditableField.js
│ │ │ ├── EvalToolbar.js
│ │ │ ├── EvalToolbar.styles.js
│ │ │ ├── ExportEvalDialog.js
│ │ │ ├── ImportDialog.js
│ │ │ └── ImportDialog.styles.js
│ │ ├── constants.js
│ │ ├── hooks/
│ │ │ ├── useEvalDatasets.js
│ │ │ └── useExportEvalDatasets.js
│ │ └── page.js
│ ├── eval-tasks/
│ │ ├── [taskId]/
│ │ │ ├── components/
│ │ │ │ ├── EvalHeader.js
│ │ │ │ ├── EvalStats.js
│ │ │ │ └── QuestionCard.js
│ │ │ ├── detailStyles.js
│ │ │ └── page.js
│ │ ├── components/
│ │ │ ├── CreateEvalTaskDialog.js
│ │ │ ├── EvalTaskCard.js
│ │ │ ├── ModelSelector.js
│ │ │ ├── QuestionFilter.js
│ │ │ └── ScoreAnchorsForm.js
│ │ ├── hooks/
│ │ │ ├── useEvalTaskDetail.js
│ │ │ ├── useEvalTaskForm.js
│ │ │ └── useEvalTasks.js
│ │ ├── page.js
│ │ └── styles.js
│ ├── image-datasets/
│ │ ├── [datasetId]/
│ │ │ └── page.js
│ │ ├── components/
│ │ │ ├── DatasetContent.js
│ │ │ ├── DatasetSidebar.js
│ │ │ ├── EmptyState.js
│ │ │ ├── ExportImageDatasetDialog.js
│ │ │ ├── ImageDatasetCard.js
│ │ │ ├── ImageDatasetFilterDialog.js
│ │ │ ├── ImageDatasetFilters.js
│ │ │ ├── ImageDatasetHeader.js
│ │ │ ├── MetadataEditor.js
│ │ │ └── MetadataInfo.js
│ │ ├── hooks/
│ │ │ ├── useImageDatasetDetail.js
│ │ │ ├── useImageDatasetDetails.js
│ │ │ ├── useImageDatasetExport.js
│ │ │ ├── useImageDatasetFilters.js
│ │ │ └── useImageDatasets.js
│ │ ├── page.js
│ │ └── styles/
│ │ └── imageDatasetStyles.js
│ ├── images/
│ │ ├── components/
│ │ │ ├── DatasetDialog.js
│ │ │ ├── ImageFilters.js
│ │ │ ├── ImageGrid.js
│ │ │ ├── ImageList.js
│ │ │ ├── ImportDialog.js
│ │ │ ├── QuestionDialog.js
│ │ │ └── annotation/
│ │ │ ├── AIGenerateButton.js
│ │ │ ├── AnnotationDialog.js
│ │ │ ├── AnswerInput.js
│ │ │ └── QuestionSelector.js
│ │ ├── hooks/
│ │ │ └── useAnnotation.js
│ │ ├── page.js
│ │ └── styles/
│ │ └── imageStyles.js
│ ├── layout.js
│ ├── multi-turn/
│ │ ├── [conversationId]/
│ │ │ ├── page.js
│ │ │ └── useConversationDetails.js
│ │ ├── components/
│ │ │ ├── ConversationTable.js
│ │ │ ├── FilterDialog.js
│ │ │ ├── RatingChip.js
│ │ │ └── SearchBar.js
│ │ ├── hooks/
│ │ │ └── useMultiTurnData.js
│ │ └── page.js
│ ├── page.js
│ ├── playground/
│ │ └── page.js
│ ├── questions/
│ │ ├── components/
│ │ │ ├── ConfirmDialog.js
│ │ │ ├── ExportQuestionsDialog.js
│ │ │ ├── QuestionEditDialog.js
│ │ │ ├── QuestionsFilter.js
│ │ │ ├── QuestionsPageHeader.js
│ │ │ ├── TemplateListView.js
│ │ │ └── template/
│ │ │ ├── TemplateFormDialog.js
│ │ │ └── TemplateManagementDialog.js
│ │ ├── hooks/
│ │ │ ├── useQuestionDelete.js
│ │ │ ├── useQuestionEdit.js
│ │ │ ├── useQuestionExport.js
│ │ │ ├── useQuestionGeneration.js
│ │ │ ├── useQuestionTemplates.js
│ │ │ └── useQuestionsFilter.js
│ │ └── page.js
│ ├── settings/
│ │ ├── components/
│ │ │ ├── CategoryTabs.js
│ │ │ ├── PromptDetail.js
│ │ │ ├── PromptEditDialog.js
│ │ │ ├── PromptList.js
│ │ │ ├── PromptSettings.js
│ │ │ └── promptUtils.js
│ │ └── page.js
│ ├── tasks/
│ │ └── page.js
│ └── text-split/
│ ├── page.js
│ ├── useChunks.js
│ ├── useDataCleaning.js
│ ├── useEvalGeneration.js
│ ├── useFileProcessing.js
│ └── useQuestionGeneration.js
├── commitlint.config.mjs
├── components/
│ ├── ExportDatasetDialog.js
│ ├── ExportProgressDialog.js
│ ├── I18nProvider.js
│ ├── LanguageSwitcher.js
│ ├── ModelSelect.js
│ ├── Navbar/
│ │ ├── ActionButtons.js
│ │ ├── ContextBar.js
│ │ ├── DesktopMenus.js
│ │ ├── Logo.js
│ │ ├── MobileDrawer.js
│ │ ├── NavigationTabs.js
│ │ ├── contextBarStyles.js
│ │ ├── index.js
│ │ └── styles.js
│ ├── TaskIcon.js
│ ├── ThemeRegistry.js
│ ├── UpdateChecker.js
│ ├── common/
│ │ └── MessageAlert.js
│ ├── conversations/
│ │ ├── ConversationContent.js
│ │ ├── ConversationHeader.js
│ │ ├── ConversationMetadata.js
│ │ └── ConversationRatingSection.js
│ ├── dataset-square/
│ │ ├── DatasetSearchBar.js
│ │ ├── DatasetSiteCard.js
│ │ └── DatasetSiteList.js
│ ├── datasets/
│ │ ├── DatasetHeader.js
│ │ ├── DatasetMetadata.js
│ │ ├── DatasetRatingSection.js
│ │ ├── EditableField.js
│ │ ├── EvalVariantDialog.js
│ │ ├── ImportDatasetDialog.js
│ │ ├── NoteInput.js
│ │ ├── OptimizeDialog.js
│ │ ├── StarRating.js
│ │ ├── TagSelector.js
│ │ ├── import/
│ │ │ ├── FieldMappingStep.js
│ │ │ ├── FileUploadStep.js
│ │ │ └── ImportProgressStep.js
│ │ └── utils/
│ │ └── ratingUtils.js
│ ├── distill/
│ │ ├── AutoDistillDialog.js
│ │ ├── AutoDistillProgress.js
│ │ ├── ConfirmDialog.js
│ │ ├── DistillTreeView.js
│ │ ├── QuestionGenerationDialog.js
│ │ ├── QuestionListItem.js
│ │ ├── TagEditDialog.js
│ │ ├── TagGenerationDialog.js
│ │ ├── TagMenu.js
│ │ ├── TagTreeItem.js
│ │ └── utils.js
│ ├── export/
│ │ ├── HuggingFaceTab.js
│ │ ├── LlamaFactoryTab.js
│ │ └── LocalExportTab.js
│ ├── home/
│ │ ├── CreateProjectDialog.js
│ │ ├── HeroSection.js
│ │ ├── MigrationDialog.js
│ │ ├── ParticleBackground.js
│ │ ├── ProjectCard.js
│ │ ├── ProjectList.js
│ │ └── StatsCard.js
│ ├── mga/
│ │ ├── GaPairsIndicator.js
│ │ └── GaPairsManager.js
│ ├── playground/
│ │ ├── ChatArea.js
│ │ ├── ChatMessage.js
│ │ ├── MessageInput.js
│ │ ├── ModelSelector.js
│ │ └── PlaygroundHeader.js
│ ├── questions/
│ │ ├── QuestionListView.js
│ │ └── QuestionTreeView.js
│ ├── settings/
│ │ ├── BasicSettings.js
│ │ ├── ModelSettings.js
│ │ └── TaskSettings.js
│ ├── tasks/
│ │ ├── TaskActions.js
│ │ ├── TaskFilters.js
│ │ ├── TaskProgress.js
│ │ ├── TaskStatusChip.js
│ │ └── TasksTable.js
│ └── text-split/
│ ├── BatchEditChunkDialog.js
│ ├── ChunkBatchDeleteDialog.js
│ ├── ChunkCard.js
│ ├── ChunkDeleteDialog.js
│ ├── ChunkFilterDialog.js
│ ├── ChunkList.js
│ ├── ChunkListHeader.js
│ ├── ChunkViewDialog.js
│ ├── DomainAnalysis.js
│ ├── FileUploader.js
│ ├── LoadingBackdrop.js
│ ├── MarkdownViewDialog.js
│ ├── PdfSettings.js
│ └── components/
│ ├── DeleteConfirmDialog.js
│ ├── DirectoryView.js
│ ├── DomainTreeActionDialog.js
│ ├── DomainTreeView.js
│ ├── FileList.js
│ ├── FileLoadingProgress.js
│ ├── PdfProcessingDialog.js
│ ├── TabPanel.js
│ └── UploadArea.js
├── constant/
│ ├── index.js
│ ├── model.js
│ ├── setting.js
│ └── sites.json
├── docker-compose.yml
├── docker-entrypoint.sh
├── electron/
│ ├── entitlements.mac.plist
│ ├── loading.html
│ ├── main.js
│ ├── modules/
│ │ ├── cache.js
│ │ ├── database.js
│ │ ├── db-updater.js
│ │ ├── ipc-handlers.js
│ │ ├── logger.js
│ │ ├── menu.js
│ │ ├── server.js
│ │ ├── updater.js
│ │ └── window-manager.js
│ ├── preload.js
│ └── util.js
├── hooks/
│ ├── useDebounce.js
│ ├── useFileProcessingStatus.js
│ ├── useGenerateDataset.js
│ ├── useModelPlayground.js
│ ├── useSnackbar.js
│ └── useTaskSettings.js
├── jsconfig.json
├── lib/
│ ├── api/
│ │ ├── chunk.js
│ │ ├── file.js
│ │ ├── index.js
│ │ └── task.js
│ ├── db/
│ │ ├── base.js
│ │ ├── chunks.js
│ │ ├── custom-prompts.js
│ │ ├── dataset-conversations.js
│ │ ├── datasets.js
│ │ ├── evalDatasets.js
│ │ ├── evalResults.js
│ │ ├── fileToDb.js
│ │ ├── files.js
│ │ ├── ga-pairs.js
│ │ ├── imageDatasets.js
│ │ ├── images.js
│ │ ├── index.js
│ │ ├── llm-models.js
│ │ ├── llm-providers.js
│ │ ├── model-config.js
│ │ ├── projects.js
│ │ ├── questionTemplates.js
│ │ ├── questions.js
│ │ ├── tags.js
│ │ ├── texts.js
│ │ └── upload-files.js
│ ├── file/
│ │ ├── file-process/
│ │ │ ├── check-file.js
│ │ │ ├── epub/
│ │ │ │ └── index.js
│ │ │ ├── get-content.js
│ │ │ ├── index.js
│ │ │ ├── pdf/
│ │ │ │ ├── default.js
│ │ │ │ ├── index.js
│ │ │ │ ├── mineru-local.js
│ │ │ │ ├── mineru.js
│ │ │ │ ├── prompt/
│ │ │ │ │ ├── optimalTitle.js
│ │ │ │ │ ├── optimalTitleEn.js
│ │ │ │ │ ├── pdfToMarkdown.js
│ │ │ │ │ └── pdfToMarkdownEn.js
│ │ │ │ ├── util.js
│ │ │ │ └── vision.js
│ │ │ └── utils.js
│ │ ├── split-markdown/
│ │ │ ├── core/
│ │ │ │ ├── parser.js
│ │ │ │ ├── splitter.js
│ │ │ │ ├── summary.js
│ │ │ │ └── toc.js
│ │ │ ├── index.js
│ │ │ ├── output/
│ │ │ │ ├── fileWriter.js
│ │ │ │ └── formatter.js
│ │ │ └── utils/
│ │ │ └── common.js
│ │ └── text-splitter.js
│ ├── i18n.js
│ ├── llm/
│ │ ├── common/
│ │ │ ├── prompt-loader.js
│ │ │ ├── question-template.js
│ │ │ └── util.js
│ │ ├── core/
│ │ │ ├── index.js
│ │ │ └── providers/
│ │ │ ├── alibailian.js
│ │ │ ├── base.js
│ │ │ ├── ollama.js
│ │ │ ├── openai.js
│ │ │ ├── openrouter.js
│ │ │ └── zhipu.js
│ │ ├── prompts/
│ │ │ ├── addLabel.js
│ │ │ ├── answer.js
│ │ │ ├── dataClean.js
│ │ │ ├── datasetEvaluation.js
│ │ │ ├── distillQuestions.js
│ │ │ ├── distillTags.js
│ │ │ ├── enhancedAnswer.js
│ │ │ ├── evalQuestion.js
│ │ │ ├── ga-generation.js
│ │ │ ├── imageAnswer.js
│ │ │ ├── imageQuestion.js
│ │ │ ├── label.js
│ │ │ ├── labelRevise.js
│ │ │ ├── llmJudge.js
│ │ │ ├── modelEvaluation.js
│ │ │ ├── multiTurnConversation.js
│ │ │ ├── newAnswer.js
│ │ │ ├── optimizeCot.js
│ │ │ └── question.js
│ │ └── usageLogger.js
│ ├── services/
│ │ ├── clean.js
│ │ ├── datasets/
│ │ │ ├── evaluation.js
│ │ │ └── index.js
│ │ ├── eval/
│ │ │ └── index.js
│ │ ├── evaluation/
│ │ │ └── index.js
│ │ ├── ga/
│ │ │ ├── ga-generation.js
│ │ │ └── ga-pairs.js
│ │ ├── images/
│ │ │ └── index.js
│ │ ├── models.js
│ │ ├── multi-turn/
│ │ │ └── index.js
│ │ ├── questions/
│ │ │ ├── index.js
│ │ │ └── template.js
│ │ └── tasks/
│ │ ├── answer-generation.js
│ │ ├── data-cleaning.js
│ │ ├── data-distillation.js
│ │ ├── dataset-evaluation.js
│ │ ├── eval-generation.js
│ │ ├── file-processing.js
│ │ ├── image-dataset-generation.js
│ │ ├── image-question-generation.js
│ │ ├── index.js
│ │ ├── model-evaluation.js
│ │ ├── multi-turn-generation.js
│ │ ├── question-generation.js
│ │ └── recovery.js
│ ├── store.js
│ └── util/
│ ├── async.js
│ ├── domain-tree.js
│ ├── file.js
│ ├── image.js
│ ├── logger.js
│ ├── modelIcon.js
│ ├── processInParallel.js
│ ├── providerLogo.js
│ └── request.js
├── locales/
│ ├── en/
│ │ └── translation.json
│ ├── pt-BR/
│ │ └── translation.json
│ ├── tr/
│ │ └── translation.json
│ └── zh-CN/
│ └── translation.json
├── next.config.js
├── package.json
├── prisma/
│ ├── generate-template.js
│ ├── schema.prisma
│ └── sql.json
├── public/
│ └── imgs/
│ ├── logo.icns
│ └── logo_old.icns
└── styles/
├── blindTest.js
├── globals.css
├── home.js
└── playground.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .dockerignore
================================================
node_modules
.next
.git
.github
README.md
README.zh-CN.md
.gitignore
.env.local
.env.development.local
.env.test.local
.env.production.local
/test
/local-db
/video
/prisma/*.sqlite
/prisma/*.sqlite-*
================================================
FILE: .gitattributes
================================================
# Ensure shell scripts always use LF line endings
*.sh text eol=lf
docker-entrypoint.sh text eol=lf
# Ensure Dockerfile uses LF
Dockerfile text eol=lf
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: '[Bug]'
labels: bug
assignees: ''
---
**注意:请务必按照此模版填写 ISSUES 信息,否则 ISSUE 将不会得到回复**
**问题描述**
清晰、简洁地描述该问题的具体情况。
**桌面设备(请完善以下信息)**
- 操作系统:[例如:、Window、MAC]
- 浏览器:[例如:谷歌浏览器(Chrome),苹果浏览器(Safari)]
- Easy Dataset 版本:[例如:1.2.2]
**使用模型**
- 模型提供商:例如火山引擎
- 模型名称:例如 DeepSeek R1
**复现步骤**
重现该问题的操作步骤:
1. 进入“……”页面。
2. 点击“……”。
3. 向下滚动到“……”。
4. 这时会看到错误提示。
**预期结果**
清晰、简洁地描述你原本期望出现的情况。
**截图**
如果有必要,请附上截图,以便更好地说明你的问题。
**其他相关信息**
在此处添加关于该问题的其他任何相关背景信息。
================================================
FILE: .github/ISSUE_TEMPLATE/feature-or-enhancement-.md
================================================
---
name: 'Feature or enhancement '
about: Suggest an idea for this project
title: '[Feature]'
labels: enhancement
assignees: ''
---
**你的功能请求是否与某个问题相关?请描述。**
清晰、简洁地描述一下存在的问题是什么。例如:当我[具体情况]时,我总是感到很沮丧。
**描述你期望的解决方案**
清晰、简洁地描述你希望实现的情况。
**描述你考虑过的替代方案**
清晰、简洁地描述你所考虑过的任何其他解决方案或功能。
**其他相关信息**
在此处添加与该功能请求相关的其他任何背景信息或截图。
================================================
FILE: .github/ISSUE_TEMPLATE/question.md
================================================
---
name: Question
about: Ask questions you want to know
title: '[Question]'
labels: question
assignees: ''
---
**注意:请务必按照此模版填写 ISSUES 信息,否则 ISSUE 将不会得到回复**
**问题描述**
清晰、简洁地描述该问题的具体情况。
**桌面设备(请完善以下信息)**
- 操作系统:[例如:、Window、MAC]
- 浏览器:[例如:谷歌浏览器(Chrome),苹果浏览器(Safari)]
- Easy Dataset 版本:[例如:1.2.2]
**使用模型**
- 模型提供商:例如火山引擎
- 模型名称:例如 DeepSeek R1
**复现步骤**
重现该问题的操作步骤:
1. 进入“……”页面。
2. 点击“……”。
3. 向下滚动到“……”。
4. 这时会看到错误提示。
**预期结果**
清晰、简洁地描述你原本期望出现的情况。
**截图**
如果有必要,请附上截图,以便更好地说明你的问题。
**其他相关信息**
在此处添加关于该问题的其他任何相关背景信息。
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
### 变更类型- [ ] 新功能(feat)
- [ ] 修复(fix)
- [ ] 文档(docs)
- [ ] 重构(refactor)
### 变更描述- 简要说明修改内容(关联Issue:#123)
### 文档更新- [ ] README.md
- [ ] 贡献指南
- [ ] 接口文档(如有)
================================================
FILE: .github/workflows/docker-build.yml
================================================
name: Build and Push Docker image on Tag
on:
push:
tags:
- '*'
jobs:
docker-image-release:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository_owner }}/easy-dataset
tags: |
type=ref,event=tag
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
================================================
FILE: .gitignore
================================================
node_modules
build
.vscode
website-local.json
ai-local.json
.next
.DS_Store
tsconfig.tsbuildinfo
mock-login-callback.ts
.env.local
/src/test/crawler
/src/test/mock
/test
/dist
/prisma/*.sqlite
.idea
!local-db/empty.txt
/local-db
prisma/local-db/db.sqlite
/local-db2
.trae
opencode.json
================================================
FILE: .husky/commit-msg
================================================
#!/usr/bin/env sh
npx commitlint --edit "$1"
================================================
FILE: .husky/pre-commit
================================================
npx lint-staged
================================================
FILE: .npmrc
================================================
# 国内用户可使用淘宝源加速 (Chinese users can use Taobao registry for faster downloads)
# registry=https://registry.npmmirror.com
registry=https://registry.npmjs.org
================================================
FILE: .prettierrc.js
================================================
module.exports = {
semi: true,
trailingComma: 'none',
singleQuote: true,
tabWidth: 2,
useTabs: false,
bracketSpacing: true,
arrowParens: 'avoid',
proseWrap: 'preserve',
jsxBracketSameLine: true,
printWidth: 120,
endOfLine: 'auto'
};
================================================
FILE: .windsurfrules
================================================
# Easy DataSet 项目架构设计
## 项目概述
Easy DataSet 是一个用于创建大模型微调数据集的应用程序。用户可以上传文本文件,系统会自动分割文本并生成问题,最终生成用于微调的数据集。
## 技术栈
- **前端框架**: Next.js 14 (App Router)
- **UI 框架**: Material-UI (MUI)
- **数据存储**: fs 文件系统模拟数据库
- **开发语言**: JavaScript
- **依赖管理**: pnpm
## 目录结构
```
easy-dataset/
├── app/ # Next.js 应用目录
│ ├── api/ # API 路由
│ │ └── projects/ # 项目相关 API
│ ├── projects/ # 项目相关页面
│ │ ├── [projectId]/ # 项目详情页面
│ └── page.js # 主页
├── components/ # React 组件
│ ├── home/ # 主页相关组件
│ │ ├── HeroSection.js
│ │ ├── ProjectList.js
│ │ └── StatsCard.js
│ ├── Navbar.js # 导航栏组件
│ └── CreateProjectDialog.js
├── lib/ # 工具库
│ └── db/ # 数据库模块
│ ├── base.js # 基础工具函数
│ ├── projects.js # 项目管理
│ ├── texts.js # 文本处理
│ ├── datasets.js # 数据集管理
│ └── index.js # 模块导出
├── styles/ # 样式文件
│ └── home.js # 主页样式
└── local-db/ # 本地数据库目录
```
## 核心模块设计
### 1. 数据库模块 (`lib/db/`)
#### base.js
- 提供基础的文件操作功能
- 确保数据库目录存在
- 读写 JSON 文件的工具函数
#### projects.js
- 项目的 CRUD 操作
- 项目配置管理
- 项目目录结构维护
#### texts.js
- 文献处理功能
- 文本片段存储和检索
- 文件上传处理
#### datasets.js
- 数据集生成和管理
- 问题列表管理
- 标签树管理
### 2. 前端组件 (`components/`)
#### Navbar.js
- 顶部导航栏
- 项目切换
- 模型选择
- 主题切换
#### home/ 目录组件
- HeroSection.js: 主页顶部展示区
- ProjectList.js: 项目列表展示
- StatsCard.js: 数据统计展示
- CreateProjectDialog.js: 创建项目的对话框
### 3. 页面路由 (`app/`)
#### 主页 (`page.js`)
- 项目列表展示
- 创建项目入口
- 数据统计展示
#### 项目详情页 (`projects/[projectId]/`)
- text-split/: 文献处理页面
- questions/: 问题列表页面
- datasets/: 数据集页面
- settings/: 项目设置页面
#### API 路由 (`api/`)
- projects/: 项目管理 API
- texts/: 文本处理 API
- questions/: 问题生成 API
- datasets/: 数据集管理 API
## 数据流设计
### 项目创建流程
1. 用户通过主页或导航栏创建新项目
2. 填写项目基本信息(名称、描述)
3. 系统创建项目目录和初始配置文件
4. 重定向到项目详情页
### 文献处理流程
1. 用户上传 Markdown 文件
2. 系统保存原始文件到项目目录
3. 调用文本分割服务,生成片段和目录结构
4. 展示分割结果和提取的目录
### 问题生成流程
1. 用户选择需要生成问题的文本片段
2. 系统调用大模型API生成问题
3. 保存问题到问题列表和标签树
### 数据集生成流程
1. 用户选择需要生成答案的问题
2. 系统调用大模型API生成答案
3. 保存数据集结果
4. 提供导出功能
================================================
FILE: AGENTS.md
================================================
# Easy Dataset Agent 指南
## 项目概述
Easy Dataset 是一个专为大型语言模型(LLM)微调数据集创建而设计的应用程序。它提供完整的workflow,从文档处理到数据集导出,支持多种文件格式和AI模型。
## 技术栈
- **前端**: Next.js 14 (App Router), React 18, Material-UI v5
- **后端**: Node.js, Prisma ORM, SQLite
- **AI集成**: OpenAI API, Ollama, 智谱AI, OpenRouter
- **桌面应用**: Electron
- **国际化**: i18next
- **构建工具**: npm/pnpm, Electron Builder
## 核心架构
### 1. 数据流架构
```
文档上传 → 文本分割 → 问题生成 → 答案生成 → 数据集导出
↓ ↓ ↓ ↓ ↓
文件处理 智能分块 LLM生成 LLM生成 格式转换
```
### 2. 模块结构
```
lib/
├── api/ # API接口层
├── db/ # 数据访问层
├── file/ # 文件处理模块
├── llm/ # AI模型集成
├── services/ # 业务逻辑层
└── util/ # 工具函数
```
## 开发指南
### 环境设置
```bash
# 安装依赖
npm install
# 数据库初始化
npm run db:push
# 开发模式
npm run dev
# 构建
npm run build
```
### 代码规范
- 使用ES6+语法
- 模块化开发
- 异步操作使用async/await
- 错误处理使用try/catch
- 注释使用JSDoc格式
### 重要文件路径
- **主入口**: `app/page.js`
- **项目路由**: `app/projects/[projectId]/`
- **API路由**: `app/api/`
- **LLM核心**: `lib/llm/core/index.js`
- **任务处理**: `lib/services/tasks/`
## 功能模块详解
### 1. 文档处理模块 (`lib/file/`)
- **支持的格式**: PDF, Markdown, DOCX, EPUB, TXT
- **核心功能**:
- 智能文本分割
- 目录结构提取
- 自定义分隔符分块
- 多语言支持
### 2. AI模型集成 (`lib/llm/`)
- **支持的提供商**:
- OpenAI (GPT系列)
- Ollama (本地模型)
- 智谱AI (GLM系列)
- OpenRouter (多模型聚合)
- **功能特性**:
- 统一API接口
- 流式输出支持
- 多语言提示词
- 错误重试机制
### 3. 任务系统 (`lib/services/tasks/`)
- **任务类型**:
- 文件处理任务
- 问题生成任务
- 答案生成任务
- 数据清洗任务
- **状态管理**: 待处理、处理中、完成、失败
### 4. 数据管理 (`lib/db/`)
- **数据模型**:
- Project (项目)
- Text/Chunk (文本块)
- Question (问题)
- Dataset (数据集)
- Tag (标签)
## 常用开发任务
### 添加新的AI模型提供商
1. 在 `lib/llm/core/providers/` 创建新的provider文件
2. 实现基础接口 (generate, streamGenerate)
3. 在 `lib/llm/core/index.js` 中注册provider
4. 更新配置文件和UI界面
### 添加新的文件格式支持
1. 在 `lib/file/file-process/` 创建格式处理器
2. 实现内容提取和文本转换逻辑
3. 更新文件类型检测和验证
4. 添加相应的UI组件
### 自定义提示词模板
1. 在 `lib/llm/prompts/` 创建新的提示词文件
2. 使用i18n支持多语言
3. 在设置界面添加配置选项
4. 测试不同模型的效果
### 添加新的导出格式
1. 在 `components/export/` 创建新的导出组件
2. 实现数据格式转换逻辑
3. 更新导出对话框界面
4. 添加格式验证和错误处理
## 调试技巧
### 1. 数据库调试
```bash
# 打开Prisma Studio
npm run db:studio
# 查看数据库文件
sqlite3 prisma/db.sqlite
```
### 2. LLM API调试
```javascript
// 在lib/llm/core/index.js中添加日志
console.log('LLM Request:', { provider, model, prompt });
console.log('LLM Response:', response);
```
### 3. 文件处理调试
```javascript
// 在lib/file/中添加调试信息
console.log('File processing:', fileName, fileType);
console.log('Text chunks:', chunks.length, chunks[0]);
```
## 性能优化建议
### 1. 文件处理优化
- 大文件分片处理
- 异步并发处理
- 内存使用监控
- 进度条显示
### 2. LLM调用优化
- 请求缓存机制
- 批量处理请求
- 重试策略优化
- 并发数控制
### 3. 前端性能优化
- 组件懒加载
- 虚拟滚动列表
- 图片懒加载
- 代码分割
## 常见问题解决
### 1. 数据库相关问题
- **问题**: 数据库连接失败
- **解决**: 检查prisma配置,确保数据库文件存在
### 2. LLM API相关问题
- **问题**: API调用超时
- **解决**: 调整超时时间,检查网络连接,增加重试机制
### 3. 文件处理问题
- **问题**: 大文件处理内存溢出
- **解决**: 使用流式处理,分块读取,增加内存限制
### 4. Electron打包问题
- **问题**: 打包后应用无法启动
- **解决**: 检查依赖项配置,确保native模块正确打包
## 部署指南
### Docker部署
```bash
# 构建镜像
docker build -t easy-dataset .
# 运行容器
docker run -d -p 1717:1717 -v ./local-db:/app/local-db easy-dataset
```
### 桌面应用构建
```bash
# 构建各平台安装包
npm run electron-build-mac # macOS
npm run electron-build-win # Windows
npm run electron-build-linux # Linux
```
## 贡献指南
### 提交规范
- 使用conventional commits格式
- 提交前运行lint检查
- 更新相关文档
- 添加测试用例
### 分支策略
- `main`: 主分支,稳定版本
- `dev`: 开发分支,集成新功能
- `feature/*`: 功能分支
- `fix/*`: 修复分支
---
================================================
FILE: ARCHITECTURE.md
================================================
# Easy DataSet 项目架构设计
## 项目概述
Easy DataSet 是一个用于创建大模型微调数据集的应用程序。用户可以上传文本文件,系统会自动分割文本并生成问题,最终生成用于微调的数据集。
## 技术栈
- **前端框架**: Next.js 14 (App Router)
- **UI 框架**: Material-UI (MUI)
- **数据存储**: fs 文件系统模拟数据库
- **开发语言**: JavaScript
## 目录结构
```
easy-dataset/
├── app/ # Next.js 应用目录
│ ├── api/ # API 路由
│ │ └── projects/ # 项目相关 API
│ ├── projects/ # 项目相关页面
│ │ ├── [projectId]/ # 项目详情页面
│ └── page.js # 主页
├── components/ # React 组件
│ ├── home/ # 主页相关组件
│ │ ├── HeroSection.js
│ │ ├── ProjectList.js
│ │ └── StatsCard.js
│ ├── Navbar.js # 导航栏组件
│ └── CreateProjectDialog.js
├── lib/ # 工具库
│ └── db/ # 数据库模块
│ ├── base.js # 基础工具函数
│ ├── projects.js # 项目管理
│ ├── texts.js # 文本处理
│ ├── datasets.js # 数据集管理
│ └── index.js # 模块导出
├── styles/ # 样式文件
│ └── home.js # 主页样式
└── local-db/ # 本地数据库目录
```
## 核心模块设计
### 1. 数据库模块 (`lib/db/`)
#### base.js
- 提供基础的文件操作功能
- 确保数据库目录存在
- 读写 JSON 文件的工具函数
#### projects.js
- 项目的 CRUD 操作
- 项目配置管理
- 项目目录结构维护
#### texts.js
- 文献处理功能
- 文本片段存储和检索
- 文件上传处理
#### datasets.js
- 数据集生成和管理
- 问题列表管理
- 标签树管理
### 2. 前端组件 (`components/`)
#### Navbar.js
- 顶部导航栏
- 项目切换
- 模型选择
- 主题切换
#### home/ 目录组件
- HeroSection.js: 主页顶部展示区
- ProjectList.js: 项目列表展示
- StatsCard.js: 数据统计展示
- CreateProjectDialog.js: 创建项目的对话框
### 3. 页面路由 (`app/`)
#### 主页 (`page.js`)
- 项目列表展示
- 创建项目入口
- 数据统计展示
#### 项目详情页 (`projects/[projectId]/`)
- text-split/: 文献处理页面
- questions/: 问题列表页面
- datasets/: 数据集页面
- settings/: 项目设置页面
#### API 路由 (`api/`)
- projects/: 项目管理 API
- texts/: 文本处理 API
- questions/: 问题生成 API
- datasets/: 数据集管理 API
## 数据流设计
### 项目创建流程
1. 用户通过主页或导航栏创建新项目
2. 填写项目基本信息(名称、描述)
3. 系统创建项目目录和初始配置文件
4. 重定向到项目详情页
### 文献处理流程
1. 用户上传 Markdown 文件
2. 系统保存原始文件到项目目录
3. 调用文本分割服务,生成片段和目录结构
4. 展示分割结果和提取的目录
### 问题生成流程
1. 用户选择需要生成问题的文本片段
2. 系统调用大模型API生成问题
3. 保存问题到问题列表和标签树
### 数据集生成流程
1. 用户选择需要生成答案的问题
2. 系统调用大模型API生成答案
3. 保存数据集结果
4. 提供导出功能
## 模型配置
支持多种大模型提供商配置:
- Ollama
- OpenAI
- 硅基流动
- 深度求索
- 智谱AI
每个提供商支持配置:
- API 地址
- API 密钥
- 模型名称
## 未来扩展方向
1. 支持更多文件格式(PDF、DOC等)
2. 增加数据集质量评估功能
3. 添加数据集版本管理
4. 实现团队协作功能
5. 增加更多数据集导出格式
## 国际化处理
### 技术选型
- **国际化库**: i18next + react-i18next
- **语言检测**: i18next-browser-languagedetector
- **支持语言**: 英文(en)、简体中文(zh-CN)
### 目录结构
```
easy-dataset/
├── locales/ # 国际化资源目录
│ ├── en/ # 英文翻译
│ │ └── translation.json
│ ├── zh-CN/ # 中文翻译
│ │ └── translation.json
│ └── pt-BR/ # 中文翻译
│ └── translation.json
├── lib/
│ └── i18n.js # i18next 配置
```
================================================
FILE: Dockerfile
================================================
# 创建包含pnpm的基础镜像
FROM node:20-alpine AS pnpm-base
RUN npm install -g pnpm@9
# 构建阶段
FROM pnpm-base AS builder
WORKDIR /app
# 添加构建参数,用于识别目标平台
ARG TARGETPLATFORM
# 安装构建依赖
RUN apk add --no-cache --virtual .build-deps \
python3 \
make \
g++ \
cairo-dev \
pango-dev \
jpeg-dev \
giflib-dev \
librsvg-dev \
build-base \
pixman-dev \
pkgconfig
# 复制依赖文件和npm配置并安装(.npmrc中可配置国内源加速)
COPY package.json pnpm-lock.yaml .npmrc ./
RUN pnpm install
# 复制源代码
COPY . .
# 根据目标平台设置Prisma二进制目标并构建应用
RUN if [ "$TARGETPLATFORM" = "linux/arm64" ]; then \
echo "Configuring for ARM64 platform"; \
sed -i 's/binaryTargets = \[.*\]/binaryTargets = \["linux-musl-arm64-openssl-3.0.x"\]/' prisma/schema.prisma; \
PRISMA_CLI_BINARY_TARGETS="linux-musl-arm64-openssl-3.0.x" pnpm build; \
else \
echo "Configuring for AMD64 platform (default)"; \
sed -i 's/binaryTargets = \[.*\]/binaryTargets = \["linux-musl-openssl-3.0.x"\]/' prisma/schema.prisma; \
PRISMA_CLI_BINARY_TARGETS="linux-musl-openssl-3.0.x" pnpm build; \
fi
# 构建完成后移除开发依赖,只保留生产依赖
RUN pnpm prune --prod
# 运行阶段
FROM pnpm-base AS runner
WORKDIR /app
# 只安装运行时依赖
RUN apk add --no-cache \
cairo \
pango \
jpeg \
giflib \
librsvg \
pixman
# 复制package.json和.env文件
COPY package.json .env ./
# 从构建阶段复制精简后的node_modules(只包含生产依赖)
COPY --from=builder /app/node_modules ./node_modules
# 从构建阶段复制构建产物
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
COPY --from=builder /app/electron ./electron
# 复制 prisma 到模板目录(用于自动初始化)
COPY --from=builder /app/prisma /app/prisma-template
# 复制并设置 entrypoint 脚本(sed 去除 Windows 换行符 \r,防止 CRLF 导致 "no such file or directory")
COPY docker-entrypoint.sh /usr/local/bin/
RUN sed -i 's/\r$//' /usr/local/bin/docker-entrypoint.sh && \
chmod +x /usr/local/bin/docker-entrypoint.sh
# 设置生产环境
ENV NODE_ENV=production
EXPOSE 1717
# 使用 entrypoint 脚本
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
CMD ["pnpm", "start"]
================================================
FILE: LICENSE
================================================
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2025 Easy Dataset Project
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see https://www.gnu.org/licenses/.
Additional Terms for Easy Dataset:
1. Contact Information
If you wish to use Easy Dataset under different terms, please contact the
copyright holders at: 1009903985@qq.com
2. Branding Restrictions
You may not use the names "Easy Dataset" or "EasyDataset" to endorse or
promote products derived from this software without prior written permission.
3. Disclaimer of Warranty
The software is provided "as is", without warranty of any kind, express or
implied, including but not limited to the warranties of merchantability,
fitness for a particular purpose and noninfringement. In no event shall the
authors or copyright holders be liable for any claim, damages or other
liability, whether in an action of contract, tort or otherwise, arising from,
out of or in connection with the software or the use or other dealings in the
software.
4. Compliance with Laws
You are responsible for ensuring your use of the software complies with all
applicable laws, including but not limited to export control regulations.
================================================
FILE: README.md
================================================
<div align="center">

<img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/ConardLi/easy-dataset">
<img alt="GitHub Downloads (all assets, all releases)" src="https://img.shields.io/github/downloads/ConardLi/easy-dataset/total">
<img alt="GitHub Release" src="https://img.shields.io/github/v/release/ConardLi/easy-dataset">
<img src="https://img.shields.io/badge/license-AGPL--3.0-green.svg" alt="AGPL 3.0 License"/>
<img alt="GitHub contributors" src="https://img.shields.io/github/contributors/ConardLi/easy-dataset">
<img alt="GitHub last commit" src="https://img.shields.io/github/last-commit/ConardLi/easy-dataset">
<a href="https://arxiv.org/abs/2507.04009v1" target="_blank">
<img src="https://img.shields.io/badge/arXiv-2507.04009-b31b1b.svg" alt="arXiv:2507.04009">
</a>
<a href="https://trendshift.io/repositories/13944" target="_blank"><img src="https://trendshift.io/api/badge/repositories/13944" alt="ConardLi%2Feasy-dataset | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
**A powerful tool for creating fine-tuning datasets for Large Language Models**
[简体中文](./README.zh-CN.md) | [English](./README.md) | [Türkçe](./README.tr.md)
[Features](#features) • [Quick Start](#local-run) • [Documentation](https://docs.easy-dataset.com/ed/en) • [Contributing](#contributing) • [License](#license)
If you like this project, please give it a Star⭐️, or buy the author a coffee => [Donate](./public/imgs/aw.jpg) ❤️!
</div>
## Overview
Easy Dataset is an application specifically designed for building large language model (LLM) datasets. It features an intuitive interface, along with built-in powerful document parsing tools, intelligent segmentation algorithms, data cleaning and augmentation capabilities. The application can convert domain-specific documents in various formats into high-quality structured datasets, which are applicable to scenarios such as model fine-tuning, retrieval-augmented generation (RAG), and model performance evaluation.

## News
🎉🎉 Easy Dataset Version 1.7.0 launches brand-new evaluation capabilities! You can effortlessly convert domain-specific documents into evaluation datasets (test sets) and automatically run multi-dimensional evaluation tasks. Additionally, it comes with a human blind test system, enabling you to easily meet needs such as vertical domain model evaluation, post-fine-tuning model performance assessment, and RAG recall rate evaluation. Tutorial: [https://www.bilibili.com/video/BV1CRrVB7Eb4/](https://www.bilibili.com/video/BV1CRrVB7Eb4/)
## Features
### 📄 Document Processing & Data Generation
- **Intelligent Document Processing**: Supports PDF, Markdown, DOCX, TXT, EPUB and more formats with intelligent recognition
- **Intelligent Text Splitting**: Multiple splitting algorithms (Markdown structure, recursive separators, fixed length, code-aware chunking), with customizable visual segmentation
- **Intelligent Question Generation**: Auto-extract relevant questions from text segments, with question templates and batch generation
- **Domain Label Tree**: Intelligently builds global domain label trees based on document structure, with auto-tagging capabilities
- **Answer Generation**: Uses LLM API to generate comprehensive answers and Chain of Thought (COT), with AI optimization
- **Data Cleaning**: Intelligent text cleaning to remove noise and improve data quality
### 🔄 Multiple Dataset Types
- **Single-Turn QA Datasets**: Standard question-answer pairs for basic fine-tuning
- **Multi-Turn Dialogue Datasets**: Customizable roles and scenarios for conversational format
- **Image QA Datasets**: Generate visual QA data from images, with multiple import methods (directory, PDF, ZIP)
- **Data Distillation**: Generate label trees and questions directly from domain topics without uploading documents
### 📊 Model Evaluation System
- **Evaluation Datasets**: Generate true/false, single-choice, multiple-choice, short-answer, and open-ended questions
- **Automated Model Evaluation**: Use Judge Model to automatically evaluate model answer quality with customizable scoring rules
- **Human Blind Test (Arena)**: Double-blind comparison of two models' answers for unbiased evaluation
- **AI Quality Assessment**: Automatic quality scoring and filtering of generated datasets
### 🛠️ Advanced Features
- **Custom Prompts**: Project-level customization of all prompt templates (question generation, answer generation, data cleaning, etc.)
- **GA Pair Generation**: Genre-Audience pair generation to enrich data diversity
- **Task Management Center**: Background batch task processing with monitoring and interruption support
- **Resource Monitoring Dashboard**: Token consumption statistics, API call tracking, model performance analysis
- **Model Testing Playground**: Compare up to 3 models simultaneously
### 📤 Export & Integration
- **Multiple Export Formats**: Alpaca, ShareGPT, Multilingual-Thinking formats with JSON/JSONL file types
- **Balanced Export**: Configure export counts per tag for dataset balancing
- **LLaMA Factory Integration**: One-click LLaMA Factory configuration file generation
- **Hugging Face Upload**: Direct upload datasets to Hugging Face Hub
### 🤖 Model Support
- **Wide Model Compatibility**: Compatible with all LLM APIs that follow the OpenAI format
- **Multi-Provider Support**: OpenAI, Ollama (local models), Zhipu AI, Alibaba Bailian, OpenRouter, and more
- **Vision Models**: Support Gemini, Claude, etc. for PDF parsing and image QA
### 🌐 User Experience
- **User-Friendly Interface**: Modern, intuitive UI designed for both technical and non-technical users
- **Multi-Language Support**: Complete Chinese, English, Turkish and Portuguese language support 🇹🇷
- **Dataset Square**: Discover and explore public dataset resources
- **Desktop Clients**: Available for Windows, macOS, and Linux
## Quick Demo
https://github.com/user-attachments/assets/6ddb1225-3d1b-4695-90cd-aa4cb01376a8
## Local Run
### Download Client
<table style="width: 100%">
<tr>
<td width="20%" align="center">
<b>Windows</b>
</td>
<td width="30%" align="center" colspan="2">
<b>MacOS</b>
</td>
<td width="20%" align="center">
<b>Linux</b>
</td>
</tr>
<tr style="text-align: center">
<td align="center" valign="middle">
<a href='https://github.com/ConardLi/easy-dataset/releases/latest'>
<img src='./public/imgs/windows.png' style="height:24px; width: 24px" />
<br />
<b>Setup.exe</b>
</a>
</td>
<td align="center" valign="middle">
<a href='https://github.com/ConardLi/easy-dataset/releases/latest'>
<img src='./public/imgs/mac.png' style="height:24px; width: 24px" />
<br />
<b>Intel</b>
</a>
</td>
<td align="center" valign="middle">
<a href='https://github.com/ConardLi/easy-dataset/releases/latest'>
<img src='./public/imgs/mac.png' style="height:24px; width: 24px" />
<br />
<b>M</b>
</a>
</td>
<td align="center" valign="middle">
<a href='https://github.com/ConardLi/easy-dataset/releases/latest'>
<img src='./public/imgs/linux.png' style="height:24px; width: 24px" />
<br />
<b>AppImage</b>
</a>
</td>
</tr>
</table>
### Install with NPM
1. Clone the repository:
```bash
git clone https://github.com/ConardLi/easy-dataset.git
cd easy-dataset
```
2. Install dependencies:
```bash
npm install
```
3. Start the development server:
```bash
npm run build
npm run start
```
4. Open your browser and visit `http://localhost:1717`
### Using the Official Docker Image
1. Clone the repository:
```bash
git clone https://github.com/ConardLi/easy-dataset.git
cd easy-dataset
```
2. Modify the `docker-compose.yml` file:
```yml
services:
easy-dataset:
image: ghcr.io/conardli/easy-dataset
container_name: easy-dataset
ports:
- '1717:1717'
volumes:
- ./local-db:/app/local-db
- ./prisma:/app/prisma
restart: unless-stopped
```
> **Note:** It is recommended to use the `local-db` and `prisma` folders in the current code repository directory as mount paths to maintain consistency with the database paths when starting via NPM.
> **Note:** The database file will be automatically initialized on first startup, no need to manually run `npm run db:push`.
3. Start with docker-compose:
```bash
docker-compose up -d
```
4. Open a browser and visit `http://localhost:1717`
### Building with a Local Dockerfile
If you want to build the image yourself, use the Dockerfile in the project root directory:
1. Clone the repository:
```bash
git clone https://github.com/ConardLi/easy-dataset.git
cd easy-dataset
```
2. Build the Docker image:
```bash
docker build -t easy-dataset .
```
3. Run the container:
```bash
docker run -d \
-p 1717:1717 \
-v ./local-db:/app/local-db \
-v ./prisma:/app/prisma \
--name easy-dataset \
easy-dataset
```
> **Note:** It is recommended to use the `local-db` and `prisma` folders in the current code repository directory as mount paths to maintain consistency with the database paths when starting via NPM.
> **Note:** The database file will be automatically initialized on first startup, no need to manually run `npm run db:push`.
4. Open a browser and visit `http://localhost:1717`
## Documentation
- View the demo video of this project: [Easy Dataset Demo Video](https://www.bilibili.com/video/BV1y8QpYGE57/)
- For detailed documentation on all features and APIs, visit our [Documentation Site](https://docs.easy-dataset.com/ed/en)
- View the paper of this project: [Easy Dataset: A Unified and Extensible Framework for Synthesizing LLM Fine-Tuning Data from Unstructured Documents](https://arxiv.org/abs/2507.04009v1)
## Community Practice
- [Complete test set generation and model evaluation with Easy Dataset](https://www.bilibili.com/video/BV1CRrVB7Eb4/)
- [Easy Dataset × LLaMA Factory: Enabling LLMs to Efficiently Learn Domain Knowledge](https://buaa-act.feishu.cn/wiki/GVzlwYcRFiR8OLkHbL6cQpYin7g)
- [Easy Dataset Practical Guide: How to Build High-Quality Datasets?](https://www.bilibili.com/video/BV1MRMnz1EGW)
- [Interpretation of Key Feature Updates in Easy Dataset](https://www.bilibili.com/video/BV1fyJhzHEb7/)
- [Foundation Models Fine-tuning Datasets: Basic Knowledge Popularization](https://docs.easy-dataset.com/zhi-shi-ke-pu)
## Contributing
We welcome contributions from the community! If you'd like to contribute to Easy Dataset, please follow these steps:
1. Fork the repository
2. Create a new branch (`git checkout -b feature/amazing-feature`)
3. Make your changes
4. Commit your changes (`git commit -m 'Add some amazing feature'`)
5. Push to the branch (`git push origin feature/amazing-feature`)
6. Open a Pull Request (submit to the DEV branch)
Please ensure that tests are appropriately updated and adhere to the existing coding style.
## Join Discussion Group & Contact the Author
https://docs.easy-dataset.com/geng-duo/lian-xi-wo-men
## License
This project is licensed under the AGPL 3.0 License - see the [LICENSE](LICENSE) file for details.
## Citation
If this work is helpful, please kindly cite as:
```bibtex
@misc{miao2025easydataset,
title={Easy Dataset: A Unified and Extensible Framework for Synthesizing LLM Fine-Tuning Data from Unstructured Documents},
author={Ziyang Miao and Qiyu Sun and Jingyuan Wang and Yuchen Gong and Yaowei Zheng and Shiqi Li and Richong Zhang},
year={2025},
eprint={2507.04009},
archivePrefix={arXiv},
primaryClass={cs.CL},
url={https://arxiv.org/abs/2507.04009}
}
```
## Star History
[](https://www.star-history.com/#ConardLi/easy-dataset&Date)
<div align="center">
<sub>Built with ❤️ by <a href="https://github.com/ConardLi">ConardLi</a> • Follow me: <a href="./public/imgs/weichat.jpg">WeChat Official Account</a>|<a href="https://space.bilibili.com/474921808">Bilibili</a>|<a href="https://juejin.cn/user/3949101466785709">Juejin</a>|<a href="https://www.zhihu.com/people/wen-ti-chao-ji-duo-de-xiao-qi">Zhihu</a>|<a href="https://www.youtube.com/@garden-conard">Youtube</a></sub>
</div>
================================================
FILE: README.tr.md
================================================
<div align="center">

<img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/ConardLi/easy-dataset">
<img alt="GitHub Downloads (all assets, all releases)" src="https://img.shields.io/github/downloads/ConardLi/easy-dataset/total">
<img alt="GitHub Release" src="https://img.shields.io/github/v/release/ConardLi/easy-dataset">
<img src="https://img.shields.io/badge/license-AGPL--3.0-green.svg" alt="AGPL 3.0 License"/>
<img alt="GitHub contributors" src="https://img.shields.io/github/contributors/ConardLi/easy-dataset">
<img alt="GitHub last commit" src="https://img.shields.io/github/last-commit/ConardLi/easy-dataset">
<a href="https://arxiv.org/abs/2507.04009v1" target="_blank">
<img src="https://img.shields.io/badge/arXiv-2507.04009-b31b1b.svg" alt="arXiv:2507.04009">
</a>
<a href="https://trendshift.io/repositories/13944" target="_blank"><img src="https://trendshift.io/api/badge/repositories/13944" alt="ConardLi%2Feasy-dataset | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
**Büyük Dil Modelleri için ince ayar veri setleri oluşturmak için güçlü bir araç**
[简体中文](./README.zh-CN.md) | [English](./README.md) | [Türkçe](./README.tr.md)
[Özellikler](#özellikler) • [Hızlı Başlangıç](#yerel-çalıştırma) • [Dokümantasyon](https://docs.easy-dataset.com/ed/en) • [Katkıda Bulunma](#katkıda-bulunma) • [Lisans](#lisans)
Bu projeyi beğendiyseniz, lütfen bir Yıldız⭐️ verin veya yazara bir kahve ısmarlayın => [Bağış](./public/imgs/aw.jpg) ❤️!
</div>
## Genel Bakış
Easy Dataset, Büyük Dil Modelleri (LLM'ler) için özel olarak tasarlanmış ince ayar veri setleri oluşturmak için bir uygulamadır. Alana özgü dosyaları yüklemek, içeriği akıllıca bölmek, sorular oluşturmak ve model ince ayarı için yüksek kaliteli eğitim verileri üretmek için sezgisel bir arayüz sağlar.
Easy Dataset ile alan bilgisini yapılandırılmış veri setlerine dönüştürebilir, OpenAI formatını takip eden tüm LLM API'leriyle uyumlu çalışabilir ve ince ayar sürecini basit ve verimli hale getirebilirsiniz.

## Özellikler
- **Akıllı Belge İşleme**: PDF, Markdown, DOCX dahil birden fazla formatın akıllı tanınması ve işlenmesi desteği
- **Akıllı Metin Bölme**: Birden fazla akıllı metin bölme algoritması ve özelleştirilebilir görsel segmentasyon desteği
- **Akıllı Soru Üretimi**: Her metin bölümünden ilgili soruları çıkarır
- **Alan Etiketleri**: Veri setleri için global alan etiketlerini akıllıca oluşturur, küresel anlama yeteneklerine sahiptir
- **Cevap Üretimi**: Kapsamlı cevaplar ve Düşünce Zinciri (COT) oluşturmak için LLM API kullanır
- **Esnek Düzenleme**: Sürecin herhangi bir aşamasında soruları, cevapları ve veri setlerini düzenleyin
- **Çoklu Dışa Aktarma Formatları**: Veri setlerini çeşitli formatlarda (Alpaca, ShareGPT, çok dilli düşünme) ve dosya türlerinde (JSON, JSONL) dışa aktarın
- **Geniş Model Desteği**: OpenAI formatını takip eden tüm LLM API'leriyle uyumlu
- **Tam Türkçe Dil Desteği**: Tüm arayüz ve AI işlemleri için eksiksiz Türkçe çeviriler 🇹🇷
- **Kullanıcı Dostu Arayüz**: Hem teknik hem de teknik olmayan kullanıcılar için tasarlanmış sezgisel kullanıcı arayüzü
- **Özel Sistem İstemleri**: Model yanıtlarını yönlendirmek için özel sistem istemleri ekleyin
## Hızlı Demo
https://github.com/user-attachments/assets/6ddb1225-3d1b-4695-90cd-aa4cb01376a8
## Yerel Çalıştırma
### İstemciyi İndirin
<table style="width: 100%">
<tr>
<td width="20%" align="center">
<b>Windows</b>
</td>
<td width="30%" align="center" colspan="2">
<b>MacOS</b>
</td>
<td width="20%" align="center">
<b>Linux</b>
</td>
</tr>
<tr style="text-align: center">
<td align="center" valign="middle">
<a href='https://github.com/ConardLi/easy-dataset/releases/latest'>
<img src='./public/imgs/windows.png' style="height:24px; width: 24px" />
<br />
<b>Setup.exe</b>
</a>
</td>
<td align="center" valign="middle">
<a href='https://github.com/ConardLi/easy-dataset/releases/latest'>
<img src='./public/imgs/mac.png' style="height:24px; width: 24px" />
<br />
<b>Intel</b>
</a>
</td>
<td align="center" valign="middle">
<a href='https://github.com/ConardLi/easy-dataset/releases/latest'>
<img src='./public/imgs/mac.png' style="height:24px; width: 24px" />
<br />
<b>M</b>
</a>
</td>
<td align="center" valign="middle">
<a href='https://github.com/ConardLi/easy-dataset/releases/latest'>
<img src='./public/imgs/linux.png' style="height:24px; width: 24px" />
<br />
<b>AppImage</b>
</a>
</td>
</tr>
</table>
### NPM ile Kurulum
```bash
npm install
npm run db:push
npm run dev
```
### Docker ile Kurulum
```bash
docker-compose up -d
```
Ardından `http://localhost:1717` adresine gidin.
## Desteklenen AI Sağlayıcıları
Easy Dataset, aşağıdakiler dahil olmak üzere birden fazla AI sağlayıcısını destekler:
- **OpenAI**: GPT-4, GPT-3.5-turbo ve diğer modeller
- **Ollama**: Yerel model çalıştırma
- **智谱AI (GLM)**: Çince modeller
- **OpenRouter**: Çoklu model aggregatör
- **Özel API Uç Noktaları**: OpenAI formatını takip eden herhangi bir API
## Proje Yapısı
```
easy-dataset/
├── app/ # Next.js uygulama yönlendiricisi
│ ├── api/ # API rotaları
│ ├── projects/ # Proje sayfaları
│ └── dataset-square/ # Veri seti galerisi
├── components/ # React bileşenleri
├── lib/ # Temel kütüphaneler
│ ├── llm/ # LLM entegrasyonu
│ ├── db/ # Veritabanı erişimi
│ ├── file/ # Dosya işleme
│ └── services/ # İş mantığı
├── locales/ # i18n çevirileri
│ ├── en/ # İngilizce
│ ├── zh-CN/ # Basitleştirilmiş Çince
│ └── tr/ # Türkçe
├── prisma/ # Veritabanı şeması
└── electron/ # Electron masaüstü uygulaması
```
## Kullanım Rehberi
### 1. Proje Oluşturma
İlk olarak, yeni bir proje oluşturun ve proje adını, açıklamasını ve diğer temel bilgileri yapılandırın.
### 2. Dosya Yükleme
Alana özgü belgelerinizi yükleyin. Desteklenen formatlar:
- PDF
- Markdown (.md)
- Microsoft Word (.docx)
- EPUB
- Düz metin (.txt)
### 3. Metin Bölme
Dosyalar aşağıdaki yöntemlerle akıllıca bölünebilir:
- Doğal dil işleme tabanlı semantik bölme
- Özel ayırıcılara dayalı bölme
- Karakter sayısına dayalı sabit boyutlu bölme
- Manuel görsel bölme
### 4. Alan Etiketleri Oluşturma
Sistem, belge içeriğine dayalı olarak otomatik olarak hiyerarşik alan etiketleri oluşturabilir ve iki seviyeyi destekler.
### 5. Soru Üretimi
Her metin bloğu için sistem:
- İçeriğe dayalı alakalı sorular oluşturur
- Tür ve hedef kitle perspektifi sorgulamayı destekler
- Soru sayısını özelleştirme seçeneği sunar
### 6. Cevap Üretimi
Yapılandırılmış LLM API'si kullanarak:
- Her soru için kapsamlı cevaplar oluşturur
- Düşünce Zinciri (COT) üretimini destekler
- Farklı cevap şablonları destekler
### 7. Veri Seti Dışa Aktarma
Veri setinizi çeşitli formatlarda dışa aktarın:
- **Alpaca Format**: Basit talimat-takip formatı
- **ShareGPT Format**: Çok turlu konuşma formatı
- **Çok Dilli Düşünme**: COT ile genişletilmiş format
- **Özel Format**: Kendi JSON yapınızı tanımlayın
Dışa aktarma hedefleri:
- Yerel dosya sistemi
- Hugging Face Hub
- LLaMA Factory uyumluluğu
## Gelişmiş Özellikler
### Veri Damıtma
Mevcut veri setlerinden yeni eğitim örnekleri oluşturun:
- Soru damıtma: Mevcut soru-cevap çiftlerinden yeni sorular oluşturun
- Etiket damıtma: Otomatik etiket ve kategorizasyon oluşturma
### Tür-Hedef Kitle (GA) Çiftleri
Spesifik içerik stilleri ve hedef kitleler için veri setlerini uyarlayın:
- Tür: Akademik, teknik, yaratıcı yazma, vb.
- Hedef Kitle: Yeni başlayanlar, uzmanlar, öğrenciler, vb.
### Toplu İşlemler
Birden fazla öğeye verimli bir şekilde işlem:
- Toplu soru üretimi
- Toplu cevap üretimi
- Toplu veri seti dışa aktarma
### Görev Yönetimi
Tüm arka plan görevlerini izleyin ve yönetin:
- Dosya işleme görevleri
- Soru üretim görevleri
- Cevap üretim görevleri
- Dışa aktarma görevleri
## Yapılandırma
### LLM API Yapılandırması
Ayarlar sayfasında LLM API'nizi yapılandırın:
1. **Sağlayıcı**: OpenAI, Ollama, 智谱AI veya özel seçin
2. **API Anahtarı**: API anahtarınızı girin (gerekirse)
3. **Model**: Kullanılacak modeli seçin
4. **Temel URL**: Özel API'ler için temel URL'yi ayarlayın
### Görev Ayarları
Görev yürütme parametrelerini özelleştirin:
- Soru üretimi için eşzamanlılık
- Cevap üretimi için eşzamanlılık
- Varsayılan soru sayısı
- Varsayılan cevap şablonu
### Özel İstemler
Her görev türü için özel sistem istemleri ekleyin:
- Soru üretim istemi
- Cevap üretim istemi
- Etiket üretim istemi
- Damıtma istemi
## Katkıda Bulunma
Katkılara hoş geldiniz! Lütfen şu adımları izleyin:
1. Repo'yu fork edin
2. Bir özellik dalı oluşturun (`git checkout -b feature/amazing-feature`)
3. Değişikliklerinizi commit edin (`git commit -m 'Add some amazing feature'`)
4. Dala push edin (`git push origin feature/amazing-feature`)
5. Bir Pull Request açın
## Lisans
Bu proje AGPL-3.0 Lisansı altında lisanslanmıştır. Detaylar için [LICENSE](./LICENSE) dosyasına bakın.
## İletişim
- **GitHub Issues**: [Yeni bir sorun oluşturun](https://github.com/ConardLi/easy-dataset/issues)
- **Email**: lhj19950927@gmail.com
- **WeChat Grubu**: README'deki QR koduna bakın
## Alıntı
Bu aracı araştırmanızda kullanırsanız, lütfen şu şekilde alıntı yapın:
```bibtex
@misc{easy-dataset-2025,
title={Easy Dataset: A Tool for Creating Fine-tuning Datasets for Large Language Models},
author={Conard Li},
year={2025},
publisher={GitHub},
howpublished={\url{https://github.com/ConardLi/easy-dataset}}
}
```
## Teşekkürler
Bu proje aşağıdaki harika açık kaynak projelerini kullanır:
- [Next.js](https://nextjs.org/)
- [React](https://reactjs.org/)
- [Material-UI](https://mui.com/)
- [Prisma](https://www.prisma.io/)
- [Electron](https://www.electronjs.org/)
---
<div align="center">
⭐️ Bu projeyi beğendiyseniz, lütfen bir yıldız verin! ⭐️
</div>
================================================
FILE: README.zh-CN.md
================================================
<div align="center">

<img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/ConardLi/easy-dataset">
<img alt="GitHub Downloads (all assets, all releases)" src="https://img.shields.io/github/downloads/ConardLi/easy-dataset/total">
<img alt="GitHub Release" src="https://img.shields.io/github/v/release/ConardLi/easy-dataset">
<img src="https://img.shields.io/badge/license-AGPL--3.0-green.svg" alt="AGPL 3.0 License"/>
<img alt="GitHub contributors" src="https://img.shields.io/github/contributors/ConardLi/easy-dataset">
<img alt="GitHub last commit" src="https://img.shields.io/github/last-commit/ConardLi/easy-dataset">
<a href="https://arxiv.org/abs/2507.04009v1" target="_blank">
<img src="https://img.shields.io/badge/arXiv-2507.04009-b31b1b.svg" alt="arXiv:2507.04009">
</a>
<a href="https://trendshift.io/repositories/13944" target="_blank"><img src="https://trendshift.io/api/badge/repositories/13944" alt="ConardLi%2Feasy-dataset | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
**一个强大的大型语言模型微调数据集创建工具**
[简体中文](./README.zh-CN.md) | [English](./README.md)
[功能特点](#功能特点) • [快速开始](#本地运行) • [使用文档](https://docs.easy-dataset.com/) • [贡献](#贡献) • [许可证](#许可证)
如果喜欢本项目,请给本项目留下 Star⭐️,或者请作者喝杯咖啡呀 => [打赏作者](./public/imgs/aw.jpg) ❤️!
</div>
## 概述
Easy Dataset 是一个专为创建大型语言模型数据集而设计的应用程序。它提供了直观的界面,内置了强大的文档解析工具、智能分割算法、数据清洗和数据增强能力,可以将各种格式的领域文献转化为高质量结构化数据集,可用于模型微调、RAG、模型效果评估等场景。

## 新闻
🎉🎉 Easy Dataset 1.7.0 版本上线全新的评估能力,你可以轻松将领域文献转换为评估数据集(测试集),并且可以自动执行多维度评估任务,另外还配备人工盲测系统,可以轻松助你完成垂直领域模型评估、模型微调后效果评估、RAG 召回率评估等需求,使用教程: [https://www.bilibili.com/video/BV1CRrVB7Eb4/](https://www.bilibili.com/video/BV1CRrVB7Eb4/)
## 功能特点
### 📄 文档处理与数据生成
- **智能文档处理**:支持 PDF、Markdown、DOCX、TXT、EPUB 等多种格式智能识别和处理
- **智能文本分割**:支持多种智能文本分割算法(Markdown 结构、递归分隔符、固定长度、代码智能分块等),支持自定义可视化分段
- **智能问题生成**:从每个文本片段中自动提取相关问题,支持问题模板和批量生成
- **领域标签树**:基于文档目录智能构建全局领域标签树,具备全局理解和自动打标能力
- **答案生成**:使用 LLM API 为每个问题生成全面的答案和思维链(COT),支持 AI 智能优化
- **数据清洗**:智能清洗文本块内容,去除噪音数据,提升数据质量
### 🔄 多种数据集类型
- **单轮问答数据集**:标准的问答对格式,适合基础微调
- **多轮对话数据集**:支持自定义角色和场景的多轮对话格式
- **图片问答数据集**:基于图片生成视觉问答数据,支持多种导入方式(目录、PDF、压缩包)
- **数据蒸馏**:无需上传文档,直接从领域主题自动生成标签树和问题
### 📊 模型评估体系
- **评估数据集**:支持生成判断题、单选题、多选题、简答题、开放题等多种题型的评估测试集
- **模型自动评估**:使用教师模型(Judge Model)自动评估模型回答质量,支持自定义评分规则
- **人工盲测 (Arena)**:双盲对比两个模型的回答质量,消除偏见进行公正评判
- **AI 质量评估**:对生成的数据集进行自动质量评分和筛选
### 🛠️ 高级功能
- **自定义提示词**:项目级自定义各类提示词模板(问题生成、答案生成、数据清洗等)
- **GA 组合生成**:文体-受众对生成,丰富数据多样性
- **任务管理中心**:后台批量任务处理,支持任务监控和中断
- **资源监控看板**:Token 消耗统计、调用次数追踪、模型性能分析
- **模型测试 Playground**:支持最多 3 个模型同时对比测试
### 📤 导出与集成
- **多种导出格式**:支持 Alpaca、ShareGPT、Multilingual-Thinking 等格式,JSON/JSONL 文件类型
- **平衡导出**:按标签配置导出数量,实现数据集均衡
- **LLaMA Factory 集成**:一键生成 LLaMA Factory 配置文件
- **Hugging Face 上传**:直接将数据集上传至 Hugging Face Hub
### 🤖 模型支持
- **广泛的模型兼容**:兼容所有遵循 OpenAI 格式的 LLM API
- **多提供商支持**:OpenAI、Ollama(本地模型)、智谱 AI、阿里百炼、OpenRouter 等
- **视觉模型**:支持 Gemini、Claude 等视觉模型用于 PDF 解析和图片问答
### 🌐 用户体验
- **用户友好界面**:为技术和非技术用户设计的现代化直观 UI
- **多语言支持**:完整的中英文界面支持
- **数据集广场**:发现和探索各种公开数据集资源
- **桌面客户端**:提供 Windows、macOS、Linux 桌面应用
## 快速演示
https://github.com/user-attachments/assets/6ddb1225-3d1b-4695-90cd-aa4cb01376a8
## 本地运行
### 下载客户端
<table style="width: 100%">
<tr>
<td width="20%" align="center">
<b>Windows</b>
</td>
<td width="30%" align="center" colspan="2">
<b>MacOS</b>
</td>
<td width="20%" align="center">
<b>Linux</b>
</td>
</tr>
<tr style="text-align: center">
<td align="center" valign="middle">
<a href='https://github.com/ConardLi/easy-dataset/releases/latest'>
<img src='./public/imgs/windows.png' style="height:24px; width: 24px" />
<br />
<b>Setup.exe</b>
</a>
</td>
<td align="center" valign="middle">
<a href='https://github.com/ConardLi/easy-dataset/releases/latest'>
<img src='./public/imgs/mac.png' style="height:24px; width: 24px" />
<br />
<b>Intel</b>
</a>
</td>
<td align="center" valign="middle">
<a href='https://github.com/ConardLi/easy-dataset/releases/latest'>
<img src='./public/imgs/mac.png' style="height:24px; width: 24px" />
<br />
<b>M</b>
</a>
</td>
<td align="center" valign="middle">
<a href='https://github.com/ConardLi/easy-dataset/releases/latest'>
<img src='./public/imgs/linux.png' style="height:24px; width: 24px" />
<br />
<b>AppImage</b>
</a>
</td>
</tr>
</table>
### 使用 NPM 安装
1. 克隆仓库:
```bash
git clone https://github.com/ConardLi/easy-dataset.git
cd easy-dataset
```
2. 安装依赖:
```bash
npm install
```
3. 启动开发服务器:
```bash
npm run build
npm run start
```
4. 打开浏览器并访问 `http://localhost:1717`
### 使用官方 Docker 镜像
1. 克隆仓库:
```bash
git clone https://github.com/ConardLi/easy-dataset.git
cd easy-dataset
```
2. 更改 `docker-compose.yml` 文件:
```yml
services:
easy-dataset:
image: ghcr.io/conardli/easy-dataset
container_name: easy-dataset
ports:
- '1717:1717'
volumes:
- ./local-db:/app/local-db
- ./prisma:/app/prisma
restart: unless-stopped
```
> **注意:** 建议直接使用当前代码仓库目录下的 `local-db` 和 `prisma` 文件夹作为挂载路径,这样可以和 NPM 启动时的数据库路径保持一致。
> **注意:** 数据库文件会在首次启动时自动初始化,无需手动执行 `npm run db:push`。
3. 使用 docker-compose 启动
```bash
docker-compose up -d
```
4. 打开浏览器并访问 `http://localhost:1717`
### 使用本地 Dockerfile 构建
如果你想自行构建镜像,可以使用项目根目录中的 Dockerfile:
1. 克隆仓库:
```bash
git clone https://github.com/ConardLi/easy-dataset.git
cd easy-dataset
```
2. 构建 Docker 镜像:
```bash
docker build -t easy-dataset .
```
3. 运行容器:
```bash
docker run -d \
-p 1717:1717 \
-v ./local-db:/app/local-db \
-v ./prisma:/app/prisma \
--name easy-dataset \
easy-dataset
```
> **注意:** 建议直接使用当前代码仓库目录下的 `local-db` 和 `prisma` 文件夹作为挂载路径,这样可以和 NPM 启动时的数据库路径保持一致。
> **注意:** 数据库文件会在首次启动时自动初始化,无需手动执行 `npm run db:push`。
4. 打开浏览器,访问 `http://localhost:1717`
## 文档
- 有关所有功能和 API 的详细文档,请访问我们的 [文档站点](https://docs.easy-dataset.com/)
- 查看本项目的演示视频:[Easy Dataset 演示视频](https://www.bilibili.com/video/BV1y8QpYGE57/)
- 查看本项目的论文:[Easy Dataset: A Unified and Extensible Framework for Synthesizing LLM Fine-Tuning Data from Unstructured Documents](https://arxiv.org/abs/2507.04009v1)
## 社区教程
- [使用 Easy Dataset 完成测试集生成和模型评估](https://www.bilibili.com/video/BV1CRrVB7Eb4/)
- [Easy Dataset × LLaMA Factory: 让大模型高效学习领域知识](https://buaa-act.feishu.cn/wiki/KY9xwTGs1iqHrRkjXBwcZP9WnL9)
- [Easy Dataset 使用实战: 如何构建高质量数据集?](https://www.bilibili.com/video/BV1MRMnz1EGW)
- [Easy Dataset 1.4 重点功能更新解读](https://www.bilibili.com/video/BV1fyJhzHEb7/)
- [Easy Dataset 1.6 重点功能更新解读](https://www.bilibili.com/video/BV1Rq1hBtEJa/)
- [大模型微调数据集: 基础知识科普](https://docs.easy-dataset.com/zhi-shi-ke-pu)
- [实战案例1:生成汽车图片识别数据集](https://docs.easy-dataset.com/bo-ke/shi-zhan-an-li/an-li-1-sheng-cheng-qi-che-tu-pian-shi-bie-shu-ju-ji)
- [实战案例2:评论情感分类数据集](https://docs.easy-dataset.com/bo-ke/shi-zhan-an-li/an-li-2-ping-lun-qing-gan-fen-lei-shu-ju-ji)
- [实战案例3:物理学多轮对话数据集](https://docs.easy-dataset.com/bo-ke/shi-zhan-an-li/an-li-3-wu-li-xue-duo-lun-dui-hua-shu-ju-ji)
- [实战案例4:AI 智能体安全数据集](https://docs.easy-dataset.com/bo-ke/shi-zhan-an-li/an-li-4ai-zhi-neng-ti-an-quan-shu-ju-ji)
- [实战案例5:从图文 PPT 中提取数据集](https://docs.easy-dataset.com/bo-ke/shi-zhan-an-li/an-li-5-cong-tu-wen-ppt-zhong-ti-qu-shu-ju-ji)
## 贡献
我们欢迎社区的贡献!如果您想为 Easy Dataset 做出贡献,请按照以下步骤操作:
1. Fork 仓库
2. 创建新分支(`git checkout -b feature/amazing-feature`)
3. 进行更改
4. 提交更改(`git commit -m '添加一些惊人的功能'`)
5. 推送到分支(`git push origin feature/amazing-feature`)
6. 打开 Pull Request(提交至 DEV 分支)
请确保适当更新测试并遵守现有的编码风格。
## 加交流群 & 联系作者
https://docs.easy-dataset.com/geng-duo/lian-xi-wo-men
## 许可证
本项目采用 AGPL 3.0 许可证 - 有关详细信息,请参阅 [LICENSE](LICENSE) 文件。
## 引用
如果您觉得此项目有帮助,请考虑以下列格式引用
```bibtex
@misc{miao2025easydataset,
title={Easy Dataset: A Unified and Extensible Framework for Synthesizing LLM Fine-Tuning Data from Unstructured Documents},
author={Ziyang Miao and Qiyu Sun and Jingyuan Wang and Yuchen Gong and Yaowei Zheng and Shiqi Li and Richong Zhang},
year={2025},
eprint={2507.04009},
archivePrefix={arXiv},
primaryClass={cs.CL},
url={https://arxiv.org/abs/2507.04009}
}
```
## Star History
[](https://www.star-history.com/#ConardLi/easy-dataset&Date)
<div align="center">
<sub>由 <a href="https://github.com/ConardLi">ConardLi</a> 用 ❤️ 构建 • 关注我:<a href="./public/imgs/weichat.jpg">公众号</a>|<a href="https://space.bilibili.com/474921808">B站</a>|<a href="https://juejin.cn/user/3949101466785709">掘金</a>|<a href="https://www.zhihu.com/people/wen-ti-chao-ji-duo-de-xiao-qi">知乎</a>|<a href="https://www.youtube.com/@garden-conard">Youtube</a></sub>
</div>
================================================
FILE: app/api/check-update/route.js
================================================
import { NextResponse } from 'next/server';
import path from 'path';
import fs from 'fs';
// Get current version
function getCurrentVersion() {
try {
const packageJsonPath = path.join(process.cwd(), 'package.json');
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
return packageJson.version;
} catch (error) {
console.error('Failed to read version from package.json:', String(error));
return '1.0.0';
}
}
// Get latest version from GitHub
async function getLatestVersion() {
try {
const owner = 'ConardLi';
const repo = 'easy-dataset';
const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/releases/latest`);
if (!response.ok) {
throw new Error(`GitHub API request failed: ${response.status}`);
}
const data = await response.json();
return data.tag_name.replace('v', '');
} catch (error) {
console.error('Failed to fetch latest version:', String(error));
return null;
}
}
// Check for updates
export async function GET() {
try {
const currentVersion = getCurrentVersion();
const latestVersion = await getLatestVersion();
if (!latestVersion) {
return NextResponse.json({
hasUpdate: false,
currentVersion,
latestVersion: null,
error: 'Failed to fetch latest version'
});
}
// Simple semver-like comparison
const hasUpdate = compareVersions(latestVersion, currentVersion) > 0;
return NextResponse.json({
hasUpdate,
currentVersion,
latestVersion,
releaseUrl: hasUpdate ? `https://github.com/ConardLi/easy-dataset/releases/tag/v${latestVersion}` : null
});
} catch (error) {
console.error('Failed to check for updates:', String(error));
return NextResponse.json(
{
hasUpdate: false,
error: 'Failed to check for updates'
},
{ status: 500 }
);
}
}
// Simple version comparison
function compareVersions(a, b) {
const partsA = a.split('.').map(Number);
const partsB = b.split('.').map(Number);
for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
const numA = i < partsA.length ? partsA[i] : 0;
const numB = i < partsB.length ? partsB[i] : 0;
if (numA > numB) return 1;
if (numA < numB) return -1;
}
return 0;
}
================================================
FILE: app/api/llm/fetch-models/route.js
================================================
import { NextResponse } from 'next/server';
import axios from 'axios';
// Fetch model list from provider
export async function POST(request) {
try {
const { endpoint, providerId, apiKey } = await request.json();
if (!endpoint) {
return NextResponse.json({ error: 'Missing required parameter: endpoint' }, { status: 400 });
}
let url = endpoint.replace(/\/$/, ''); // Remove trailing slash
// Handle Ollama endpoint
if (providerId === 'ollama') {
// Remove possible /v1 or other version suffix
url = url.replace(/\/v\d+$/, '');
// Append /api if missing
if (!url.includes('/api')) {
url += '/api';
}
url += '/tags';
} else {
url += '/models';
}
const headers = {};
if (apiKey) {
headers.Authorization = `Bearer ${apiKey}`;
}
const response = await axios.get(url, { headers });
// Format response per provider
let formattedModels = [];
if (providerId === 'ollama') {
// Ollama /api/tags format: { models: [{ name: 'model-name', ... }] }
if (response.data.models && Array.isArray(response.data.models)) {
formattedModels = response.data.models.map(item => ({
modelId: item.name,
modelName: item.name,
providerId
}));
}
} else {
// Default handling (OpenAI-compatible)
if (response.data.data && Array.isArray(response.data.data)) {
formattedModels = response.data.data.map(item => ({
modelId: item.id,
modelName: item.id,
providerId
}));
}
}
return NextResponse.json(formattedModels);
} catch (error) {
console.error('Failed to fetch model list:', String(error));
// Handle known error shapes
if (error.response) {
if (error.response.status === 401) {
return NextResponse.json({ error: 'Invalid API key' }, { status: 401 });
}
return NextResponse.json(
{ error: `Failed to fetch model list: ${error.response.statusText}` },
{ status: error.response.status }
);
}
return NextResponse.json({ error: `Failed to fetch model list: ${error.message}` }, { status: 500 });
}
}
================================================
FILE: app/api/llm/model/route.js
================================================
import { NextResponse } from 'next/server';
import { getLlmModelsByProviderId } from '@/lib/db/llm-models';
// Get LLM models
export async function GET(request) {
try {
const searchParams = request.nextUrl.searchParams;
let providerId = searchParams.get('providerId');
if (!providerId) {
return NextResponse.json({ error: 'Invalid parameters' }, { status: 400 });
}
const models = await getLlmModelsByProviderId(providerId);
if (!models) {
return NextResponse.json({ error: 'LLM provider not found' }, { status: 404 });
}
return NextResponse.json(models);
} catch (error) {
console.error('Database query error:', String(error));
return NextResponse.json({ error: 'Database query failed' }, { status: 500 });
}
}
// Sync latest model list
export async function POST(request) {
try {
const { newModels, providerId } = await request.json();
const models = await getLlmModelsByProviderId(providerId);
const existingModelIds = models.map(model => model.modelId);
const diffModels = newModels.filter(item => !existingModelIds.includes(item.modelId));
if (diffModels.length > 0) {
// return NextResponse.json(await createLlmModels(diffModels));
return NextResponse.json({ message: 'No new models to insert' }, { status: 200 });
} else {
return NextResponse.json({ message: 'No new models to insert' }, { status: 200 });
}
} catch (error) {
return NextResponse.json({ error: 'Database insert failed' }, { status: 500 });
}
}
================================================
FILE: app/api/llm/ollama/models/route.js
================================================
import { NextResponse } from 'next/server';
const OllamaClient = require('@/lib/llm/core/providers/ollama');
// Force dynamic route to prevent static generation
export const dynamic = 'force-dynamic';
export async function GET(request) {
try {
// Read host and port from query params
const { searchParams } = new URL(request.url);
const host = searchParams.get('host') || '127.0.0.1';
const port = searchParams.get('port') || '11434';
// Create Ollama API client
const ollama = new OllamaClient({
endpoint: `http://${host}:${port}/api`
});
// Fetch model list
const models = await ollama.getModels();
return NextResponse.json(models);
} catch (error) {
// console.error('fetch Ollama models error:', error);
return NextResponse.json({ error: 'fetch Models failed' }, { status: 500 });
}
}
================================================
FILE: app/api/llm/providers/route.js
================================================
import { NextResponse } from 'next/server';
import { getLlmProviders } from '@/lib/db/llm-providers';
import { sortProvidersByPriority } from '@/lib/util/providerLogo';
// Get LLM provider data
export async function GET() {
try {
const result = await getLlmProviders();
return NextResponse.json(sortProvidersByPriority(result, item => item.id));
} catch (error) {
console.error('Database query error:', String(error));
return NextResponse.json({ error: 'Database query failed' }, { status: 500 });
}
}
================================================
FILE: app/api/monitoring/logs/route.js
================================================
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
export const dynamic = 'force-dynamic';
export async function GET(request) {
try {
const { searchParams } = new URL(request.url);
const timeRange = searchParams.get('timeRange') || '7d';
const projectId = searchParams.get('projectId');
const provider = searchParams.get('provider');
const status = searchParams.get('status');
const page = parseInt(searchParams.get('page') || '1', 10);
const pageSize = parseInt(searchParams.get('pageSize') || '10', 10);
const searchTerm = searchParams.get('search') || '';
let startDate = new Date();
if (timeRange === '24h') {
startDate.setHours(startDate.getHours() - 24);
} else if (timeRange === '30d') {
startDate.setDate(startDate.getDate() - 30);
} else {
startDate.setDate(startDate.getDate() - 7);
}
const where = {
createAt: {
gte: startDate
}
};
if (projectId && projectId !== 'all') {
where.projectId = projectId;
}
if (provider && provider !== 'all') {
where.provider = provider;
}
if (status && status !== 'all') {
where.status = status;
}
if (searchTerm) {
where.OR = [{ model: { contains: searchTerm } }, { errorMessage: { contains: searchTerm } }];
}
const total = await db.llmUsageLogs.count({ where });
const logs = await db.llmUsageLogs.findMany({
where,
select: {
id: true,
projectId: true,
provider: true,
model: true,
inputTokens: true,
outputTokens: true,
totalTokens: true,
latency: true,
status: true,
errorMessage: true,
createAt: true
},
orderBy: {
createAt: 'desc'
},
skip: (page - 1) * pageSize,
take: pageSize
});
const projectIds = [...new Set(logs.map(log => log.projectId))];
const projects = await db.projects.findMany({
where: { id: { in: projectIds } },
select: { id: true, name: true }
});
const projectMap = projects.reduce((acc, p) => {
acc[p.id] = p.name;
return acc;
}, {});
const details = logs.map(log => ({
id: log.id,
projectId: log.projectId,
projectName: projectMap[log.projectId] || 'Unknown Project',
provider: log.provider,
model: log.model,
status: log.status,
failureReason: log.errorMessage,
inputTokens: log.inputTokens,
outputTokens: log.outputTokens,
totalTokens: log.totalTokens,
calls: 1, // Single record
avgLatency: log.status === 'SUCCESS' ? (log.latency / 1000).toFixed(2) + 's' : '-',
createAt: log.createAt
}));
return NextResponse.json({
details,
total,
page,
pageSize,
totalPages: Math.ceil(total / pageSize)
});
} catch (error) {
console.error('Failed to fetch monitoring logs:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
================================================
FILE: app/api/monitoring/stats/route.js
================================================
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
export const dynamic = 'force-dynamic';
export async function GET(request) {
try {
const { searchParams } = new URL(request.url);
const timeRange = searchParams.get('timeRange') || '7d'; // 24h, 7d, 30d
const projectId = searchParams.get('projectId');
const provider = searchParams.get('provider');
const status = searchParams.get('status');
let startDate = new Date();
if (timeRange === '24h') {
startDate.setHours(startDate.getHours() - 24);
} else if (timeRange === '30d') {
startDate.setDate(startDate.getDate() - 30);
} else {
startDate.setDate(startDate.getDate() - 7);
}
const where = {
createAt: {
gte: startDate
}
};
if (projectId && projectId !== 'all') {
where.projectId = projectId;
}
if (provider && provider !== 'all') {
where.provider = provider;
}
if (status && status !== 'all') {
where.status = status;
}
// 1. Fetch data for aggregation
// Note: Prisma aggregation can be slow on very large datasets. If needed, optimize with pre-aggregated tables.
const logs = await db.llmUsageLogs.findMany({
where,
select: {
id: true,
projectId: true,
provider: true,
model: true,
inputTokens: true,
outputTokens: true,
totalTokens: true,
latency: true,
status: true,
errorMessage: true,
createAt: true,
dateString: true
},
orderBy: {
createAt: 'desc'
}
});
// Build project name map
const projects = await db.projects.findMany({
select: { id: true, name: true }
});
const projectMap = projects.reduce((acc, p) => {
acc[p.id] = p.name;
return acc;
}, {});
// 2. Process and aggregate
const summary = {
totalTokens: 0,
inputTokens: 0,
outputTokens: 0,
totalCalls: logs.length,
successCalls: 0,
failedCalls: 0,
totalLatency: 0,
avgLatency: 0
};
const trendMap = {};
const modelStats = {};
const detailedStatsMap = {}; // Key: projectId-model-status-errorMessage
logs.forEach(log => {
// Summary
summary.totalTokens += log.totalTokens;
summary.inputTokens += log.inputTokens;
summary.outputTokens += log.outputTokens;
if (log.status === 'SUCCESS') {
summary.successCalls++;
summary.totalLatency += log.latency;
} else {
summary.failedCalls++;
}
// Trend (by day or hour)
let timeKey;
if (timeRange === '24h') {
const date = new Date(log.createAt);
timeKey = `${String(date.getHours()).padStart(2, '0')}:00`;
} else {
timeKey = log.dateString.slice(5); // MM-DD
}
if (!trendMap[timeKey]) {
trendMap[timeKey] = { name: timeKey, input: 0, output: 0 };
}
trendMap[timeKey].input += log.inputTokens;
trendMap[timeKey].output += log.outputTokens;
// Model Distribution
const modelKey = log.model;
if (!modelStats[modelKey]) {
modelStats[modelKey] = { name: modelKey, value: 0 };
}
modelStats[modelKey].value += log.totalTokens;
// Detailed Table Aggregation
// Key: projectId + model + status + (errorMessage || '')
const errorKey = log.errorMessage || '';
const detailKey = `${log.projectId}|${log.model}|${log.status}|${errorKey}`;
if (!detailedStatsMap[detailKey]) {
detailedStatsMap[detailKey] = {
projectId: log.projectId,
projectName: projectMap[log.projectId] || 'Unknown Project',
provider: log.provider,
model: log.model,
status: log.status,
failureReason: log.errorMessage,
inputTokens: 0,
outputTokens: 0,
totalTokens: 0,
calls: 0,
totalLatency: 0
};
}
const detailItem = detailedStatsMap[detailKey];
detailItem.inputTokens += log.inputTokens;
detailItem.outputTokens += log.outputTokens;
detailItem.totalTokens += log.totalTokens;
detailItem.calls += 1;
if (log.status === 'SUCCESS') {
detailItem.totalLatency += log.latency;
}
});
// Calculate averages
if (summary.successCalls > 0) {
summary.avgLatency = Math.round(summary.totalLatency / summary.successCalls);
}
summary.avgTokensPerCall = summary.totalCalls > 0 ? Math.round(summary.totalTokens / summary.totalCalls) : 0;
summary.failureRate = summary.totalCalls > 0 ? summary.failedCalls / summary.totalCalls : 0;
// Format chart data
const trend = Object.values(trendMap).sort((a, b) => {
// Simple sorting; for production use, consider stricter time ordering.
return a.name.localeCompare(b.name);
});
const modelDistribution = Object.values(modelStats).sort((a, b) => b.value - a.value);
// Format detailed table data
const details = Object.values(detailedStatsMap)
.map(item => ({
...item,
avgLatency:
item.status === 'SUCCESS' && item.calls > 0 ? (item.totalLatency / item.calls / 1000).toFixed(2) + 's' : '-'
}))
.sort((a, b) => b.totalTokens - a.totalTokens); // Default sorting by token usage
return NextResponse.json({
summary,
trend,
modelDistribution,
details,
projects
});
} catch (error) {
console.error('Failed to fetch monitoring stats:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
================================================
FILE: app/api/monitoring/summary/route.js
================================================
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
export const dynamic = 'force-dynamic';
export async function GET(request) {
try {
const { searchParams } = new URL(request.url);
const timeRange = searchParams.get('timeRange') || '7d';
const projectId = searchParams.get('projectId');
const provider = searchParams.get('provider');
const status = searchParams.get('status');
let startDate = new Date();
if (timeRange === '24h') {
startDate.setHours(startDate.getHours() - 24);
} else if (timeRange === '30d') {
startDate.setDate(startDate.getDate() - 30);
} else {
startDate.setDate(startDate.getDate() - 7);
}
const where = {
createAt: {
gte: startDate
}
};
if (projectId && projectId !== 'all') {
where.projectId = projectId;
}
if (provider && provider !== 'all') {
where.provider = provider;
}
if (status && status !== 'all') {
where.status = status;
}
const logs = await db.llmUsageLogs.findMany({
where,
select: {
inputTokens: true,
outputTokens: true,
totalTokens: true,
latency: true,
status: true,
createAt: true,
dateString: true,
model: true
}
});
const summary = {
totalTokens: 0,
inputTokens: 0,
outputTokens: 0,
totalCalls: logs.length,
successCalls: 0,
failedCalls: 0,
totalLatency: 0,
avgLatency: 0
};
const trendMap = {};
const modelStats = {};
logs.forEach(log => {
summary.totalTokens += log.totalTokens;
summary.inputTokens += log.inputTokens;
summary.outputTokens += log.outputTokens;
if (log.status === 'SUCCESS') {
summary.successCalls++;
summary.totalLatency += log.latency;
} else {
summary.failedCalls++;
}
let timeKey;
if (timeRange === '24h') {
const date = new Date(log.createAt);
timeKey = `${String(date.getHours()).padStart(2, '0')}:00`;
} else {
timeKey = log.dateString.slice(5);
}
if (!trendMap[timeKey]) {
trendMap[timeKey] = { name: timeKey, input: 0, output: 0 };
}
trendMap[timeKey].input += log.inputTokens;
trendMap[timeKey].output += log.outputTokens;
const modelKey = log.model;
if (!modelStats[modelKey]) {
modelStats[modelKey] = { name: modelKey, value: 0 };
}
modelStats[modelKey].value += log.totalTokens;
});
if (summary.successCalls > 0) {
summary.avgLatency = Math.round(summary.totalLatency / summary.successCalls);
}
summary.avgTokensPerCall = summary.totalCalls > 0 ? Math.round(summary.totalTokens / summary.totalCalls) : 0;
summary.failureRate = summary.totalCalls > 0 ? summary.failedCalls / summary.totalCalls : 0;
const trend = Object.values(trendMap).sort((a, b) => a.name.localeCompare(b.name));
const modelDistribution = Object.values(modelStats).sort((a, b) => b.value - a.value);
const projects = await db.projects.findMany({
select: { id: true, name: true },
orderBy: { createAt: 'desc' }
});
const allLogs = await db.llmUsageLogs.findMany({
select: { provider: true },
distinct: ['provider']
});
const providers = allLogs.map(log => log.provider).filter(Boolean);
return NextResponse.json({
summary,
trend,
modelDistribution,
projects,
providers
});
} catch (error) {
console.error('Failed to fetch monitoring summary:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
================================================
FILE: app/api/projects/[projectId]/batch-add-manual-ga/route.js
================================================
import { NextResponse } from 'next/server';
import { getUploadFileInfoById } from '@/lib/db/upload-files';
import { createGaPairs, getGaPairsByFileId } from '@/lib/db/ga-pairs';
/**
* 批量手动添加 GA 对到多个文件
*/
export async function POST(request, { params }) {
try {
const { projectId } = params;
const body = await request.json();
if (!projectId) {
return NextResponse.json({ error: 'Project ID is required' }, { status: 400 });
}
const { fileIds, gaPair, appendMode = false } = body;
if (!fileIds || !Array.isArray(fileIds) || fileIds.length === 0) {
return NextResponse.json({ error: 'File IDs array is required' }, { status: 400 });
}
if (!gaPair || !gaPair.genreTitle || !gaPair.audienceTitle) {
return NextResponse.json({ error: 'GA pair with genreTitle and audienceTitle is required' }, { status: 400 });
}
console.log('开始处理批量手动添加GA对请求');
console.log('项目ID:', projectId);
console.log('请求的文件IDs:', fileIds);
console.log('GA对:', gaPair);
// 使用 getUploadFileInfoById 逐个验证文件
const validFiles = [];
const invalidFileIds = [];
for (const fileId of fileIds) {
try {
console.log(`正在验证文件: ${fileId}`);
const fileInfo = await getUploadFileInfoById(fileId);
if (fileInfo && fileInfo.projectId === projectId) {
console.log(`文件验证成功: ${fileInfo.fileName}`);
validFiles.push(fileInfo);
} else if (fileInfo) {
console.log(`文件属于其他项目: ${fileInfo.projectId} != ${projectId}`);
invalidFileIds.push(fileId);
} else {
console.log(`文件不存在: ${fileId}`);
invalidFileIds.push(fileId);
}
} catch (error) {
console.error(`验证文件 ${fileId} 时出错:`, String(error));
invalidFileIds.push(fileId);
}
}
console.log(`文件验证完成: 有效${validFiles.length}个, 无效${invalidFileIds.length}个`);
if (validFiles.length === 0) {
return NextResponse.json(
{
error: 'No valid files found',
debug: {
projectId,
requestedIds: fileIds,
invalidIds: invalidFileIds,
message: 'None of the requested files belong to this project or exist in the database'
}
},
{ status: 404 }
);
}
// 批量手动添加 GA 对
console.log('开始批量手动添加GA对...');
console.log('追加模式:', appendMode);
const results = [];
for (const file of validFiles) {
try {
console.log(`处理文件: ${file.fileName}`);
// 检查是否已存在 GA 对
const existingPairs = await getGaPairsByFileId(file.id);
let pairNumber = 1;
if (appendMode && existingPairs && existingPairs.length > 0) {
// 追加模式:在现有 GA 对后面添加
pairNumber = existingPairs.length + 1;
} else if (!appendMode && existingPairs && existingPairs.length > 0) {
// 非追加模式:如果已存在 GA 对则跳过
console.log(`文件 ${file.fileName} 已存在GA对,跳过`);
results.push({
fileId: file.id,
fileName: file.fileName,
success: true,
skipped: true,
message: 'GA pairs already exist'
});
continue;
}
// 创建 GA 对数据
const gaPairData = [
{
projectId,
fileId: file.id,
pairNumber,
genreTitle: gaPair.genreTitle.trim(),
genreDesc: gaPair.genreDesc?.trim() || '',
audienceTitle: gaPair.audienceTitle.trim(),
audienceDesc: gaPair.audienceDesc?.trim() || '',
isActive: true
}
];
// 保存 GA 对
if (appendMode) {
// 追加模式:只创建新的 GA 对
await createGaPairs(gaPairData);
} else {
// 非追加模式:使用 saveGaPairs 替换现有的
const { saveGaPairs } = await import('@/lib/db/ga-pairs');
await saveGaPairs(projectId, file.id, [
{
genre: { title: gaPair.genreTitle.trim(), description: gaPair.genreDesc?.trim() || '' },
audience: { title: gaPair.audienceTitle.trim(), description: gaPair.audienceDesc?.trim() || '' }
}
]);
}
results.push({
fileId: file.id,
fileName: file.fileName,
success: true,
skipped: false,
message: 'GA pair added successfully'
});
console.log(`成功为文件 ${file.fileName} 添加GA对`);
} catch (error) {
console.error(`为文件 ${file.fileName} 添加GA对失败:`, error);
results.push({
fileId: file.id,
fileName: file.fileName,
success: false,
skipped: false,
error: error.message,
message: `Failed: ${error.message}`
});
}
}
// 统计结果
const successCount = results.filter(r => r.success).length;
const failureCount = results.filter(r => !r.success).length;
console.log(`批量手动添加完成: 成功${successCount}个, 失败${failureCount}个`);
return NextResponse.json({
success: true,
data: results,
summary: {
total: results.length,
success: successCount,
failure: failureCount,
processed: validFiles.length,
skipped: invalidFileIds.length
},
message: `Added GA pairs to ${successCount} files, ${failureCount} failed, ${invalidFileIds.length} files not found`
});
} catch (error) {
console.error('Error batch adding manual GA pairs:', String(error));
return NextResponse.json({ error: String(error) || 'Failed to batch add manual GA pairs' }, { status: 500 });
}
}
================================================
FILE: app/api/projects/[projectId]/batch-delete-files/route.js
================================================
import { NextResponse } from 'next/server';
import { getUploadFileInfoById, delUploadFileInfoById } from '@/lib/db/upload-files';
import { getProject } from '@/lib/db/projects';
import { getProjectChunks, getProjectTocByName } from '@/lib/file/text-splitter';
import { batchSaveTags } from '@/lib/db/tags';
import { handleDomainTree } from '@/lib/util/domain-tree';
import path from 'path';
import { getProjectRoot } from '@/lib/db/base';
import { promises as fs } from 'fs';
/**
* 批量删除文件
* 复用单个文件删除的完整逻辑,包括领域树修订
*/
export async function POST(request, { params }) {
try {
const { projectId } = params;
const body = await request.json();
if (!projectId) {
return NextResponse.json({ error: 'Project ID is required' }, { status: 400 });
}
const { fileIds, domainTreeAction = 'keep', model, language = '中文' } = body;
if (!fileIds || !Array.isArray(fileIds) || fileIds.length === 0) {
return NextResponse.json({ error: 'File IDs array is required' }, { status: 400 });
}
console.log('开始处理批量删除文件请求');
console.log('项目ID:', projectId);
console.log('请求的文件IDs:', fileIds);
console.log('领域树操作:', domainTreeAction);
// 获取项目信息
const project = await getProject(projectId);
if (!project) {
return NextResponse.json({ error: 'The project does not exist' }, { status: 404 });
}
// 验证文件并删除
const results = [];
const deletedTocs = [];
let deletedCount = 0;
let failedCount = 0;
let totalStats = {
deletedChunks: 0,
deletedQuestions: 0,
deletedDatasets: 0
};
for (const fileId of fileIds) {
try {
console.log(`正在验证文件: ${fileId}`);
const fileInfo = await getUploadFileInfoById(fileId);
if (!fileInfo) {
console.log(`文件不存在: ${fileId}`);
results.push({
fileId,
success: false,
error: 'File not found'
});
failedCount++;
continue;
}
if (fileInfo.projectId !== projectId) {
console.log(`文件属于其他项目: ${fileInfo.projectId} != ${projectId}`);
results.push({
fileId,
success: false,
error: 'File belongs to another project'
});
failedCount++;
continue;
}
// 删除文件及其相关的文本块、问题和数据集
console.log(`删除文件: ${fileInfo.fileName}`);
const { stats, fileName } = await delUploadFileInfoById(fileId);
// 累计统计信息
totalStats.deletedChunks += stats.deletedChunks || 0;
totalStats.deletedQuestions += stats.deletedQuestions || 0;
totalStats.deletedDatasets += stats.deletedDatasets || 0;
// 获取并保存删除的 TOC 信息
const deleteToc = await getProjectTocByName(projectId, fileName);
if (deleteToc) {
deletedTocs.push(deleteToc);
}
// 删除 TOC 文件
try {
const projectRoot = await getProjectRoot();
const projectPath = path.join(projectRoot, projectId);
const tocDir = path.join(projectPath, 'toc');
const baseName = path.basename(fileInfo.fileName, path.extname(fileInfo.fileName));
const tocPath = path.join(tocDir, `${baseName}-toc.json`);
await fs.unlink(tocPath);
console.log(`成功删除 TOC 文件: ${tocPath}`);
} catch (error) {
console.error(`删除 TOC 文件失败:`, String(error));
}
results.push({
fileId,
fileName: fileInfo.fileName,
success: true,
stats
});
deletedCount++;
console.log(`成功删除文件: ${fileInfo.fileName}`);
} catch (error) {
console.error(`删除文件 ${fileId} 时出错:`, error);
results.push({
fileId,
success: false,
error: error.message
});
failedCount++;
}
}
console.log(`批量删除完成: 成功${deletedCount}个, 失败${failedCount}个`);
// 如果选择了保持领域树不变,直接返回删除结果
if (domainTreeAction === 'keep') {
return NextResponse.json({
success: true,
deletedCount,
failedCount,
total: fileIds.length,
results,
stats: totalStats,
domainTreeAction: 'keep',
message: `Successfully deleted ${deletedCount} files, ${failedCount} failed`
});
}
// 处理领域树更新
try {
// 获取项目的所有文件
const { chunks, toc } = await getProjectChunks(projectId);
// 如果不存在文本块,说明项目已经没有文件了
if (!chunks || chunks.length === 0) {
// 清空领域树
await batchSaveTags(projectId, []);
return NextResponse.json({
success: true,
deletedCount,
failedCount,
total: fileIds.length,
results,
stats: totalStats,
domainTreeAction,
message: `Successfully deleted ${deletedCount} files, domain tree cleared`,
domainTreeCleared: true
});
}
// 调用领域树处理模块
await handleDomainTree({
projectId,
action: domainTreeAction,
allToc: toc,
model: model,
language,
deleteToc: deletedTocs.length > 0 ? deletedTocs : undefined,
project
});
console.log('领域树更新成功');
} catch (error) {
console.error('Error updating domain tree after batch deletion:', String(error));
// 即使领域树更新失败,也不影响文件删除的结果
}
return NextResponse.json({
success: true,
deletedCount,
failedCount,
total: fileIds.length,
results,
stats: totalStats,
domainTreeAction,
message: `Successfully deleted ${deletedCount} files, ${failedCount} failed`
});
} catch (error) {
console.error('Error batch deleting files:', String(error));
return NextResponse.json({ error: String(error) || 'Failed to batch delete files' }, { status: 500 });
}
}
================================================
FILE: app/api/projects/[projectId]/batch-generateGA/route.js
================================================
import { NextResponse } from 'next/server';
import { batchGenerateGaPairs } from '@/lib/services/ga/ga-pairs';
import { getUploadFileInfoById } from '@/lib/db/upload-files'; // 导入单个文件查询函数
/**
* 批量生成多个文件的 GA 对
*/
export async function POST(request, { params }) {
try {
const { projectId } = params;
const body = await request.json();
if (!projectId) {
return NextResponse.json({ error: 'Project ID is required' }, { status: 400 });
}
const { fileIds, modelConfigId, language = '中文', appendMode = false } = body;
if (!fileIds || !Array.isArray(fileIds) || fileIds.length === 0) {
return NextResponse.json({ error: 'File IDs array is required' }, { status: 400 });
}
if (!modelConfigId) {
return NextResponse.json({ error: 'Model configuration ID is required' }, { status: 400 });
}
console.log('开始处理批量生成GA对请求');
console.log('项目ID:', projectId);
console.log('请求的文件IDs:', fileIds);
// 使用 getUploadFileInfoById 逐个验证文件
const validFiles = [];
const invalidFileIds = [];
for (const fileId of fileIds) {
try {
console.log(`正在验证文件: ${fileId}`);
const fileInfo = await getUploadFileInfoById(fileId);
if (fileInfo && fileInfo.projectId === projectId) {
console.log(`文件验证成功: ${fileInfo.fileName}`);
validFiles.push(fileInfo);
} else if (fileInfo) {
console.log(`文件属于其他项目: ${fileInfo.projectId} != ${projectId}`);
invalidFileIds.push(fileId);
} else {
console.log(`文件不存在: ${fileId}`);
invalidFileIds.push(fileId);
}
} catch (error) {
console.error(`验证文件 ${fileId} 时出错:`, String(error));
invalidFileIds.push(fileId);
}
}
console.log(`文件验证完成: 有效${validFiles.length}个, 无效${invalidFileIds.length}个`);
if (validFiles.length === 0) {
return NextResponse.json(
{
error: 'No valid files found',
debug: {
projectId,
requestedIds: fileIds,
invalidIds: invalidFileIds,
message: 'None of the requested files belong to this project or exist in the database'
}
},
{ status: 404 }
);
}
// 批量生成 GA 对
console.log('开始批量生成GA对...');
console.log('追加模式:', appendMode);
const results = await batchGenerateGaPairs(
projectId,
validFiles,
modelConfigId,
language,
appendMode // 传递追加模式参数
);
// 统计结果
const successCount = results.filter(r => r.success).length;
const failureCount = results.filter(r => !r.success).length;
console.log(`批量生成完成: 成功${successCount}个, 失败${failureCount}个`);
return NextResponse.json({
success: true,
data: results,
summary: {
total: results.length,
success: successCount,
failure: failureCount,
processed: validFiles.length,
skipped: invalidFileIds.length
},
message: `Generated GA pairs for ${successCount} files, ${failureCount} failed, ${invalidFileIds.length} files not found`
});
} catch (error) {
console.error('Error batch generating GA pairs:', String(error));
return NextResponse.json({ error: String(error) || 'Failed to batch generate GA pairs' }, { status: 500 });
}
}
================================================
FILE: app/api/projects/[projectId]/blind-test-tasks/[taskId]/current/route.js
================================================
import { NextResponse } from 'next/server';
import { db } from '@/lib/db/index';
import LLMClient from '@/lib/llm/core/index';
import { getModelConfigById } from '@/lib/db/model-config';
/**
* Get current question and generate answers from two models
*/
export async function GET(request, { params }) {
try {
const { projectId, taskId } = params;
const task = await db.task.findFirst({
where: {
id: taskId,
projectId,
taskType: 'blind-test'
}
});
if (!task) {
return NextResponse.json({ code: 404, error: 'Task not found' }, { status: 404 });
}
if (task.status !== 0) {
return NextResponse.json({ code: 400, error: 'Task has ended' }, { status: 400 });
}
// Parse task detail
let detail = {};
let modelInfo = {};
try {
detail = task.detail ? JSON.parse(task.detail) : {};
modelInfo = task.modelInfo ? JSON.parse(task.modelInfo) : {};
} catch (e) {
console.error('Failed to parse task detail:', e);
}
const questionIds = detail.questionIds || detail.evalDatasetIds || [];
const currentIndex = detail.currentIndex || 0;
// Check if all questions are completed
if (questionIds.length === 0 || currentIndex >= questionIds.length) {
return NextResponse.json({
code: 0,
data: {
completed: true,
message: 'All questions completed'
}
});
}
// Fetch current question
const currentQuestionId = questionIds[currentIndex];
const currentQuestion = await db.evalDatasets.findUnique({
where: { id: currentQuestionId },
select: {
id: true,
question: true,
questionType: true,
correctAnswer: true,
tags: true
}
});
if (!currentQuestion) {
return NextResponse.json({ code: 404, error: 'Question not found' }, { status: 404 });
}
// Fetch both model configs
const [modelConfigA, modelConfigB] = await Promise.all([
getModelConfigById(modelInfo.modelA.providerId),
getModelConfigById(modelInfo.modelB.providerId)
]);
if (!modelConfigA || !modelConfigB) {
return NextResponse.json({ code: 400, error: 'Model configuration not found' }, { status: 400 });
}
// Build prompts
const systemPrompt = "You are a helpful assistant. Provide detailed and accurate answers to the user's question.";
const userPrompt = currentQuestion.question;
// Call both models in parallel
const startTimeA = Date.now();
const startTimeB = Date.now();
let answerA = '';
let answerB = '';
let errorA = null;
let errorB = null;
let durationA = 0;
let durationB = 0;
try {
// Call model A
const clientA = new LLMClient(modelConfigA);
const resultA = await clientA.chat([
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userPrompt }
]);
answerA = resultA.text || '';
durationA = Date.now() - startTimeA;
} catch (err) {
console.error('Model A call failed:', err);
errorA = err.message;
durationA = Date.now() - startTimeA;
}
try {
// Call model B
const clientB = new LLMClient(modelConfigB);
const resultB = await clientB.chat([
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userPrompt }
]);
answerB = resultB.text || '';
durationB = Date.now() - startTimeB;
} catch (err) {
console.error('Model B call failed:', err);
errorB = err.message;
durationB = Date.now() - startTimeB;
}
// Randomly swap positions (core blind-test behavior)
const isSwapped = Math.random() > 0.5;
return NextResponse.json({
code: 0,
data: {
completed: false,
currentIndex,
totalCount: evalDatasetIds.length,
question: currentQuestion,
// Blind test: do not reveal which model is which
leftAnswer: {
content: isSwapped ? answerB : answerA,
error: isSwapped ? errorB : errorA,
duration: isSwapped ? durationB : durationA
},
rightAnswer: {
content: isSwapped ? answerA : answerB,
error: isSwapped ? errorA : errorB,
duration: isSwapped ? durationA : durationB
},
// Server stores the actual mapping for scoring
_swap: isSwapped
}
});
} catch (error) {
console.error('Failed to fetch current question:', error);
return NextResponse.json(
{ code: 500, error: 'Failed to fetch current question', message: error.message },
{ status: 500 }
);
}
}
================================================
FILE: app/api/projects/[projectId]/blind-test-tasks/[taskId]/question/route.js
================================================
import { NextResponse } from 'next/server';
import { db } from '@/lib/db/index';
/**
* Get current question info (including random swap info)
*/
export async function GET(request, { params }) {
const { projectId, taskId } = params;
try {
if (!projectId || !taskId) {
return NextResponse.json({ error: 'Missing required parameters' }, { status: 400 });
}
// Fetch task
const task = await db.task.findUnique({
where: { id: taskId }
});
if (!task || task.taskType !== 'blind-test') {
return NextResponse.json({ error: 'Task not found' }, { status: 404 });
}
// Parse task detail
const detail = JSON.parse(task.detail || '{}');
// Support both evalDatasetIds and questionIds
const questionIds = detail.questionIds || detail.evalDatasetIds || [];
const currentIndex = detail.currentIndex || 0;
// Check if task is completed
if (questionIds.length === 0 || currentIndex >= questionIds.length) {
return NextResponse.json({
completed: true,
currentIndex,
totalQuestions: questionIds.length
});
}
// Fetch current question
const currentQuestionId = questionIds[currentIndex];
const currentQuestion = await db.evalDatasets.findUnique({
where: { id: currentQuestionId }
});
if (!currentQuestion) {
return NextResponse.json({ error: 'Question not found' }, { status: 404 });
}
// Randomly decide whether to swap (core blind-test behavior)
const isSwapped = Math.random() > 0.5;
return NextResponse.json({
questionId: currentQuestion.id,
question: currentQuestion.question,
answer: currentQuestion.correctAnswer || '',
questionIndex: currentIndex + 1,
totalQuestions: questionIds.length,
isSwapped
});
} catch (error) {
console.error('Failed to fetch question info:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
================================================
FILE: app/api/projects/[projectId]/blind-test-tasks/[taskId]/route.js
================================================
import { NextResponse } from 'next/server';
import { db } from '@/lib/db/index';
/**
* Get blind-test task details
* Results are fetched from EvalResults table
*/
export async function GET(request, { params }) {
try {
const { projectId, taskId } = params;
const task = await db.task.findFirst({
where: {
id: taskId,
projectId,
taskType: 'blind-test'
}
});
if (!task) {
return NextResponse.json({ code: 404, error: 'Task not found' }, { status: 404 });
}
let detail = {};
let modelInfo = {};
try {
detail = task.detail ? JSON.parse(task.detail) : {};
modelInfo = task.modelInfo ? JSON.parse(task.modelInfo) : {};
} catch (e) {
console.error('Failed to parse task detail:', e);
}
// Fetch all related evaluation questions
const evalDatasetIds = detail.evalDatasetIds || [];
const evalDatasets = await db.evalDatasets.findMany({
where: {
id: { in: evalDatasetIds }
},
select: {
id: true,
question: true,
questionType: true,
correctAnswer: true,
tags: true
}
});
// Sort by evalDatasetIds order
const orderedDatasets = evalDatasetIds.map(id => evalDatasets.find(d => d.id === id)).filter(Boolean);
// Fetch results from EvalResults table
const evalResults = await db.evalResults.findMany({
where: { taskId },
orderBy: { createAt: 'asc' }
});
// Parse results into the format expected by frontend
const results = evalResults.map(r => {
let modelAnswer = {};
let judgeData = {};
try {
modelAnswer = JSON.parse(r.modelAnswer || '{}');
judgeData = JSON.parse(r.judgeResponse || '{}');
} catch (e) {
// Ignore parse errors
}
return {
questionId: r.evalDatasetId,
vote: judgeData.vote,
isSwapped: judgeData.isSwapped,
modelAScore: judgeData.modelAScore || 0,
modelBScore: judgeData.modelBScore || 0,
leftAnswer: modelAnswer.leftAnswer || '',
rightAnswer: modelAnswer.rightAnswer || '',
timestamp: r.createAt
};
});
return NextResponse.json({
code: 0,
data: {
...task,
detail: {
...detail,
results // Include results from EvalResults table
},
modelInfo,
evalDatasets: orderedDatasets
}
});
} catch (error) {
console.error('Failed to fetch blind-test task details:', error);
return NextResponse.json(
{ code: 500, error: 'Failed to fetch blind-test task details', message: error.message },
{ status: 500 }
);
}
}
/**
* Update blind-test task (interrupt/stop)
*/
export async function PUT(request, { params }) {
try {
const { projectId, taskId } = params;
const { action } = await request.json();
const task = await db.task.findFirst({
where: {
id: taskId,
projectId,
taskType: 'blind-test'
}
});
if (!task) {
return NextResponse.json({ code: 404, error: 'Task not found' }, { status: 404 });
}
if (action === 'interrupt') {
if (task.status !== 0) {
return NextResponse.json({ code: 400, error: 'Only running tasks can be interrupted' }, { status: 400 });
}
const updatedTask = await db.task.update({
where: { id: taskId },
data: {
status: 3, // Interrupted
endTime: new Date()
}
});
return NextResponse.json({
code: 0,
data: updatedTask,
message: 'Task interrupted'
});
}
return NextResponse.json({ code: 400, error: 'Unknown action' }, { status: 400 });
} catch (error) {
console.error('Failed to update blind-test task:', error);
return NextResponse.json(
{ code: 500, error: 'Failed to update blind-test task', message: error.message },
{ status: 500 }
);
}
}
/**
* Delete blind-test task and its results
*/
export async function DELETE(request, { params }) {
try {
const { projectId, taskId } = params;
const task = await db.task.findFirst({
where: {
id: taskId,
projectId,
taskType: 'blind-test'
}
});
if (!task) {
return NextResponse.json({ code: 404, error: 'Task not found' }, { status: 404 });
}
// Delete related EvalResults first
await db.evalResults.deleteMany({
where: { taskId }
});
// Then delete the task
await db.task.delete({
where: { id: taskId }
});
return NextResponse.json({
code: 0,
message: 'Task deleted'
});
} catch (error) {
console.error('Failed to delete blind-test task:', error);
return NextResponse.json(
{ code: 500, error: 'Failed to delete blind-test task', message: error.message },
{ status: 500 }
);
}
}
================================================
FILE: app/api/projects/[projectId]/blind-test-tasks/[taskId]/stream/route.js
================================================
import { NextResponse } from 'next/server';
import { db } from '@/lib/db/index';
import LLMClient from '@/lib/llm/core/index';
import { getModelConfigById } from '@/lib/db/model-config';
/**
* Stream answers from two models for the current question
*/
export async function GET(request, { params }) {
const { projectId, taskId } = params;
try {
if (!projectId || !taskId) {
return NextResponse.json({ error: 'Missing required parameters' }, { status: 400 });
}
// Fetch task
const task = await db.task.findUnique({
where: { id: taskId }
});
if (!task || task.taskType !== 'blind-test') {
return NextResponse.json({ error: 'Task not found' }, { status: 404 });
}
// Parse task detail
const detail = JSON.parse(task.detail || '{}');
const modelInfo = JSON.parse(task.modelInfo || '{}');
const { questionIds = [], currentIndex = 0 } = detail;
// Check if task is completed
if (currentIndex >= questionIds.length) {
return NextResponse.json({ completed: true });
}
// Fetch current question
const currentQuestionId = questionIds[currentIndex];
const currentQuestion = await db.evalDatasets.findUnique({
where: { id: currentQuestionId }
});
if (!currentQuestion) {
return NextResponse.json({ error: 'Question not found' }, { status: 404 });
}
// Fetch model configs
const [modelConfigA, modelConfigB] = await Promise.all([
getModelConfigById(modelInfo.modelA.providerId),
getModelConfigById(modelInfo.modelB.providerId)
]);
if (!modelConfigA || !modelConfigB) {
return NextResponse.json({ error: 'Model configuration not found' }, { status: 400 });
}
// Randomly swap positions (core blind-test behavior)
const isSwapped = Math.random() > 0.5;
// Create streaming response
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
try {
// Send init message
controller.enqueue(
encoder.encode(
JSON.stringify({
type: 'init',
question: currentQuestion.question,
questionId: currentQuestion.id,
questionIndex: currentIndex + 1,
totalQuestions: questionIds.length,
isSwapped
}) + '\n'
)
);
// Prepare messages
const messages = [
{
role: 'system',
content: "You are a helpful assistant. Provide detailed and accurate answers to the user's question."
},
{ role: 'user', content: currentQuestion.question }
];
// Create LLM clients
const clientA = new LLMClient({
projectId,
...modelConfigA
});
const clientB = new LLMClient({
projectId,
...modelConfigB
});
let answerA = '';
let answerB = '';
const startTime = Date.now();
// Call both models in parallel (streaming)
await Promise.all([
(async () => {
try {
const response = await clientA.chatStreamAPI(messages);
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
answerA += chunk;
// Send chunk update
controller.enqueue(
encoder.encode(
JSON.stringify({
type: 'chunk',
model: isSwapped ? 'B' : 'A',
content: chunk
}) + '\n'
)
);
}
} catch (err) {
console.error('Model A call failed:', err);
controller.enqueue(
encoder.encode(
JSON.stringify({
type: 'error',
model: isSwapped ? 'B' : 'A',
error: err.message
}) + '\n'
)
);
}
})(),
(async () => {
try {
const response = await clientB.chatStreamAPI(messages);
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
answerB += chunk;
// Send chunk update
controller.enqueue(
encoder.encode(
JSON.stringify({
type: 'chunk',
model: isSwapped ? 'A' : 'B',
content: chunk
}) + '\n'
)
);
}
} catch (err) {
console.error('Model B call failed:', err);
controller.enqueue(
encoder.encode(
JSON.stringify({
type: 'error',
model: isSwapped ? 'A' : 'B',
error: err.message
}) + '\n'
)
);
}
})()
]);
const duration = Date.now() - startTime;
// Send done message
controller.enqueue(
encoder.encode(
JSON.stringify({
type: 'done',
duration,
answerA: isSwapped ? answerB : answerA,
answerB: isSwapped ? answerA : answerB
}) + '\n'
)
);
controller.close();
} catch (error) {
console.error('Streaming handler failed:', error);
controller.error(error);
}
}
});
return new Response(stream, {
headers: {
'Content-Type': 'text/plain; charset=utf-8',
'Cache-Control': 'no-cache',
Connection: 'keep-alive'
}
});
} catch (error) {
console.error('API error:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
================================================
FILE: app/api/projects/[projectId]/blind-test-tasks/[taskId]/stream-model/route.js
================================================
import { NextResponse } from 'next/server';
import { db } from '@/lib/db/index';
import LLMClient from '@/lib/llm/core/index';
import { getModelConfigById } from '@/lib/db/model-config';
/**
* Stream answer for a specified model
* Query param: model=A or model=B
*/
export async function GET(request, { params }) {
const { projectId, taskId } = params;
const { searchParams } = new URL(request.url);
const modelType = searchParams.get('model'); // 'A' or 'B'
try {
if (!projectId || !taskId) {
return NextResponse.json({ error: 'Missing required parameters' }, { status: 400 });
}
if (!modelType || !['A', 'B'].includes(modelType)) {
return NextResponse.json({ error: 'Model type must be specified (A or B)' }, { status: 400 });
}
// Fetch task
const task = await db.task.findUnique({
where: { id: taskId }
});
if (!task || task.taskType !== 'blind-test') {
return NextResponse.json({ error: 'Task not found' }, { status: 404 });
}
// Parse task detail
const detail = JSON.parse(task.detail || '{}');
const modelInfo = JSON.parse(task.modelInfo || '{}');
// Support both evalDatasetIds and questionIds
const questionIds = detail.questionIds || detail.evalDatasetIds || [];
const currentIndex = detail.currentIndex || 0;
// Check if task is completed
if (questionIds.length === 0 || currentIndex >= questionIds.length) {
return NextResponse.json({ completed: true });
}
// Fetch current question
const currentQuestionId = questionIds[currentIndex];
const currentQuestion = await db.evalDatasets.findUnique({
where: { id: currentQuestionId }
});
if (!currentQuestion) {
return NextResponse.json({ error: 'Question not found' }, { status: 404 });
}
// Resolve model config based on modelType
const modelConfigKey = modelType === 'A' ? 'modelA' : 'modelB';
const modelConfig = await getModelConfigById(modelInfo[modelConfigKey].id);
if (!modelConfig) {
return NextResponse.json({ error: 'Model configuration not found' }, { status: 400 });
}
// Prepare messages
const messages = [
{
role: 'system',
content: "You are a helpful assistant. Provide detailed and accurate answers to the user's question."
},
{ role: 'user', content: currentQuestion.question }
];
// Create LLM client
const client = new LLMClient({
projectId,
...modelConfig
});
// Call streaming API and return response directly
const response = await client.chatStreamAPI(messages);
return new Response(response.body, {
headers: {
'Content-Type': 'text/plain; charset=utf-8',
'Cache-Control': 'no-cache',
Connection: 'keep-alive'
}
});
} catch (error) {
console.error(`Model ${modelType} streaming call failed:`, error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
================================================
FILE: app/api/projects/[projectId]/blind-test-tasks/[taskId]/vote/route.js
================================================
import { NextResponse } from 'next/server';
import { db } from '@/lib/db/index';
/**
* Submit vote result
* vote: 'left' | 'right' | 'both_good' | 'both_bad'
* Results are stored in EvalResults table
*/
export async function POST(request, { params }) {
try {
const { projectId, taskId } = params;
const { vote, questionId, isSwapped, leftAnswer, rightAnswer } = await request.json();
// Validate vote option
const validVotes = ['left', 'right', 'both_good', 'both_bad'];
if (!validVotes.includes(vote)) {
return NextResponse.json({ code: 400, error: 'Invalid vote option' }, { status: 400 });
}
if (!questionId) {
return NextResponse.json({ code: 400, error: 'Question ID is required' }, { status: 400 });
}
const task = await db.task.findFirst({
where: {
id: taskId,
projectId,
taskType: 'blind-test'
}
});
if (!task) {
return NextResponse.json({ code: 404, error: 'Task not found' }, { status: 404 });
}
if (task.status !== 0) {
return NextResponse.json({ code: 400, error: 'Task has ended' }, { status: 400 });
}
// Parse task details
let detail = {};
try {
detail = task.detail ? JSON.parse(task.detail) : {};
} catch (e) {
console.error('Failed to parse task detail:', e);
}
// Calculate scores
// isSwapped: true means left is model B and right is model A
// isSwapped: false means left is model A and right is model B
let modelAScore = 0;
let modelBScore = 0;
if (vote === 'left') {
if (isSwapped) {
modelBScore = 1; // Left is B
} else {
modelAScore = 1; // Left is A
}
} else if (vote === 'right') {
if (isSwapped) {
modelAScore = 1; // Right is A
} else {
modelBScore = 1; // Right is B
}
} else if (vote === 'both_good') {
modelAScore = 0.5;
modelBScore = 0.5;
}
// both_bad: both scores remain 0
// Store result in EvalResults table
const evalResult = await db.evalResults.create({
data: {
projectId,
taskId,
evalDatasetId: questionId,
modelAnswer: JSON.stringify({
leftAnswer: leftAnswer || '',
rightAnswer: rightAnswer || ''
}),
score: modelAScore, // Store modelA score for sorting/aggregation
isCorrect: false, // Not applicable for blind-test
judgeResponse: JSON.stringify({
vote,
isSwapped,
modelAScore,
modelBScore
}),
duration: 0,
status: 0
}
});
// Update task progress
const evalDatasetIds = detail.evalDatasetIds || [];
const newCurrentIndex = (detail.currentIndex || 0) + 1;
const isCompleted = newCurrentIndex >= evalDatasetIds.length;
const updatedDetail = {
...detail,
currentIndex: newCurrentIndex
};
await db.task.update({
where: { id: taskId },
data: {
detail: JSON.stringify(updatedDetail),
completedCount: newCurrentIndex,
status: isCompleted ? 1 : 0, // 1-completed, 0-running
endTime: isCompleted ? new Date() : null
}
});
// Calculate current total scores from EvalResults
const allResults = await db.evalResults.findMany({
where: { taskId },
select: { judgeResponse: true }
});
let totalModelAScore = 0;
let totalModelBScore = 0;
for (const r of allResults) {
try {
const judge = JSON.parse(r.judgeResponse || '{}');
totalModelAScore += judge.modelAScore || 0;
totalModelBScore += judge.modelBScore || 0;
} catch (e) {
// Ignore parse errors
}
}
return NextResponse.json({
code: 0,
data: {
success: true,
isCompleted,
currentIndex: newCurrentIndex,
totalCount: evalDatasetIds.length,
scores: {
modelA: totalModelAScore,
modelB: totalModelBScore
}
},
message: isCompleted ? 'Blind-test task completed' : 'Vote recorded'
});
} catch (error) {
console.error('Failed to submit vote result:', error);
return NextResponse.json(
{ code: 500, error: 'Failed to submit vote result', message: error.message },
{ status: 500 }
);
}
}
================================================
FILE: app/api/projects/[projectId]/blind-test-tasks/route.js
================================================
import { NextResponse } from 'next/server';
import { db } from '@/lib/db/index';
/**
* Get all blind-test tasks for a project
*/
export async function GET(request, { params }) {
try {
const { projectId } = params;
const { searchParams } = new URL(request.url);
const page = parseInt(searchParams.get('page') || '1');
const pageSize = parseInt(searchParams.get('pageSize') || '20');
if (!projectId) {
return NextResponse.json({ error: 'Project ID is required' }, { status: 400 });
}
const skip = (page - 1) * pageSize;
// Fetch task list and total count
const [tasks, total] = await Promise.all([
db.task.findMany({
where: {
projectId,
taskType: 'blind-test'
},
orderBy: { createAt: 'desc' },
skip,
take: pageSize
}),
db.task.count({
where: {
projectId,
taskType: 'blind-test'
}
})
]);
// Fetch evaluation results for all tasks to calculate scores
const taskIds = tasks.map(t => t.id);
const allEvalResults = await db.evalResults.findMany({
where: { taskId: { in: taskIds } },
select: {
taskId: true,
judgeResponse: true
}
});
// Group results by taskId and calculate scores
const taskScores = {};
for (const result of allEvalResults) {
if (!taskScores[result.taskId]) {
taskScores[result.taskId] = { modelAScore: 0, modelBScore: 0 };
}
try {
const judge = JSON.parse(result.judgeResponse || '{}');
taskScores[result.taskId].modelAScore += judge.modelAScore || 0;
taskScores[result.taskId].modelBScore += judge.modelBScore || 0;
} catch (e) {
// Ignore parse errors
}
}
// Parse task detail fields and attach scores
const tasksWithDetails = tasks.map(task => {
let detail = {};
let modelInfo = {};
try {
detail = task.detail ? JSON.parse(task.detail) : {};
modelInfo = task.modelInfo ? JSON.parse(task.modelInfo) : {};
} catch (e) {
console.error('Failed to parse task detail:', e);
}
// Attach calculated scores as results array
const scores = taskScores[task.id] || { modelAScore: 0, modelBScore: 0 };
const results = [
{
modelAScore: scores.modelAScore,
modelBScore: scores.modelBScore
}
];
return {
...task,
detail: {
...detail,
results // Attach results for display in task card
},
modelInfo
};
});
return NextResponse.json({
code: 0,
data: {
items: tasksWithDetails,
total,
page,
pageSize,
totalPages: Math.ceil(total / pageSize)
}
});
} catch (error) {
console.error('Failed to fetch blind-test task list:', error);
return NextResponse.json(
{ code: 500, error: 'Failed to fetch blind-test task list', message: error.message },
{ status: 500 }
);
}
}
/**
* Create a blind-test task
*/
export async function POST(request, { params }) {
try {
const { projectId } = params;
const data = await request.json();
const { modelA, modelB, evalDatasetIds, language = 'zh-CN' } = data;
if (!modelA || !modelA.modelId || !modelA.providerId) {
return NextResponse.json({ code: 400, error: 'Please select model A' }, { status: 400 });
}
if (!modelB || !modelB.modelId || !modelB.providerId) {
return NextResponse.json({ code: 400, error: 'Please select model B' }, { status: 400 });
}
if (modelA.modelId === modelB.modelId && modelA.providerId === modelB.providerId) {
return NextResponse.json({ code: 400, error: 'The two models must be different' }, { status: 400 });
}
if (!evalDatasetIds || evalDatasetIds.length === 0) {
return NextResponse.json({ code: 400, error: 'Please select questions to evaluate' }, { status: 400 });
}
const evalDatasets = await db.evalDatasets.findMany({
where: {
id: { in: evalDatasetIds },
projectId
},
select: { id: true, questionType: true }
});
const invalidQuestions = evalDatasets.filter(
q => q.questionType !== 'short_answer' && q.questionType !== 'open_ended'
);
if (invalidQuestions.length > 0) {
return NextResponse.json(
{
code: 400,
error: 'Blind-test tasks only support short-answer and open-ended questions'
},
{ status: 400 }
);
}
// Fetch model config info
const [modelConfigA, modelConfigB] = await Promise.all([
db.modelConfig.findFirst({
where: { projectId, providerId: modelA.providerId, modelId: modelA.modelId }
}),
db.modelConfig.findFirst({
where: { projectId, providerId: modelB.providerId, modelId: modelB.modelId }
})
]);
// Build model info (two models)
const modelInfo = {
modelA: {
id: modelConfigA?.id,
modelId: modelA.modelId,
modelName: modelConfigA?.modelName || modelA.modelId,
providerId: modelA.providerId,
providerName: modelConfigA?.providerName || modelA.providerId
},
modelB: {
id: modelConfigB?.id,
modelId: modelB.modelId,
modelName: modelConfigB?.modelName || modelB.modelId,
providerId: modelB.providerId,
providerName: modelConfigB?.providerName || modelB.providerId
}
};
// Build task detail (only store evalDatasetIds and currentIndex)
const taskDetail = {
evalDatasetIds,
currentIndex: 0 // Current question index
};
// Create task
const newTask = await db.task.create({
data: {
projectId,
taskType: 'blind-test',
status: 0, // Running
modelInfo: JSON.stringify(modelInfo),
language,
detail: JSON.stringify(taskDetail),
totalCount: evalDatasetIds.length,
completedCount: 0,
note: ''
}
});
return NextResponse.json({
code: 0,
data: {
...newTask,
detail: taskDetail,
modelInfo
},
message: 'Blind-test task created'
});
} catch (error) {
console.error('Failed to create blind-test task:', error);
return NextResponse.json(
{ code: 500, error: 'Failed to create blind-test task', message: error.message },
{ status: 500 }
);
}
}
================================================
FILE: app/api/projects/[projectId]/chunks/[chunkId]/clean/route.js
================================================
import { NextResponse } from 'next/server';
import logger from '@/lib/util/logger';
import cleanService from '@/lib/services/clean';
// 为指定文本块进行数据清洗
export async function POST(request, { params }) {
try {
const { projectId, chunkId } = params;
// 验证项目ID和文本块ID
if (!projectId || !chunkId) {
return NextResponse.json({ error: 'Project ID or text block ID cannot be empty' }, { status: 400 });
}
// 获取请求体
const { model, language = '中文' } = await request.json();
if (!model) {
return NextResponse.json({ error: 'Model cannot be empty' }, { status: 400 });
}
// 使用数据清洗服务
const result = await cleanService.cleanDataForChunk(projectId, chunkId, {
model,
language
});
// 返回清洗结果
return NextResponse.json({
chunkId,
originalLength: result.originalLength,
cleanedLength: result.cleanedLength,
success: result.success,
message: '数据清洗完成'
});
} catch (error) {
logger.error('Error cleaning data:', error);
return NextResponse.json({ error: error.message || 'Error cleaning data' }, { status: 500 });
}
}
================================================
FILE: app/api/projects/[projectId]/chunks/[chunkId]/eval-questions/route.js
================================================
import { NextResponse } from 'next/server';
import { generateEvalQuestionsForChunk } from '@/lib/services/eval';
import logger from '@/lib/util/logger';
/**
* 为指定文本块生成测评题目
*/
export async function POST(request, { params }) {
try {
const { projectId, chunkId } = params;
// 验证参数
if (!projectId || !chunkId) {
return NextResponse.json({ error: 'Project ID and Chunk ID are required' }, { status: 400 });
}
// 获取请求体
const { model, language = 'zh-CN' } = await request.json();
if (!model) {
return NextResponse.json({ error: 'Model configuration is required' }, { status: 400 });
}
// 调用服务层生成测评题目
const result = await generateEvalQuestionsForChunk(projectId, chunkId, {
model,
language
});
return NextResponse.json(result);
} catch (error) {
logger.error('Error generating eval questions:', error);
return NextResponse.json({ error: error.message || 'Failed to generate eval questions' }, { status: 500 });
}
}
================================================
FILE: app/api/projects/[projectId]/chunks/[chunkId]/questions/route.js
================================================
import { NextResponse } from 'next/server';
import { getQuestionsForChunk } from '@/lib/db/questions';
import logger from '@/lib/util/logger';
import questionService from '@/lib/services/questions';
// 为指定文本块生成问题
export async function POST(request, { params }) {
try {
const { projectId, chunkId } = params;
// 验证项目ID和文本块ID
if (!projectId || !chunkId) {
return NextResponse.json({ error: 'Project ID or text block ID cannot be empty' }, { status: 400 });
} // 获取请求体
const { model, language = '中文', number, enableGaExpansion = false } = await request.json();
if (!model) {
return NextResponse.json({ error: 'Model cannot be empty' }, { status: 400 });
}
// 后续会根据是否有GA对来选择是否启用GA扩展选择服务函数
const serviceFunc = questionService.generateQuestionsForChunkWithGA;
// 使用问题生成服务
const result = await serviceFunc(projectId, chunkId, {
model,
language,
number,
enableGaExpansion
});
// 统一返回格式,确保包含GA扩展信息
const response = {
chunkId,
questions: result.questions || result.labelQuestions || [],
total: result.total || (result.questions || result.labelQuestions || []).length,
gaExpansionUsed: result.gaExpansionUsed || false,
gaPairsCount: result.gaPairsCount || 0,
expectedTotal: result.expectedTotal || result.total
};
// 返回生成的问题
return NextResponse.json(response);
} catch (error) {
logger.error('Error generating questions:', error);
return NextResponse.json({ error: error.message || 'Error generating questions' }, { status: 500 });
}
}
// 获取指定文本块的问题
export async function GET(request, { params }) {
try {
const { projectId, chunkId } = params;
// 验证项目ID和文本块ID
if (!projectId || !chunkId) {
return NextResponse.json({ error: 'The item ID or text block ID cannot be empty' }, { status: 400 });
}
// 获取文本块的问题
const questions = await getQuestionsForChunk(projectId, chunkId);
// 返回问题列表
return NextResponse.json({
chunkId,
questions,
total: questions.length
});
} catch (error) {
console.error('Error getting questions:', String(error));
return NextResponse.json({ error: error.message || 'Error getting questions' }, { status: 500 });
}
}
================================================
FILE: app/api/projects/[projectId]/chunks/[chunkId]/route.js
================================================
import { NextResponse } from 'next/server';
import { deleteChunkById, getChunkById, updateChunkById } from '@/lib/db/chunks';
// 获取文本块内容
export async function GET(request, { params }) {
try {
const { projectId, chunkId } = params;
// 验证参数
if (!projectId) {
return NextResponse.json({ error: 'Project ID cannot be empty' }, { status: 400 });
}
if (!chunkId) {
return NextResponse.json({ error: 'Text block ID cannot be empty' }, { status: 400 });
}
// 获取文本块内容
const chunk = await getChunkById(chunkId);
return NextResponse.json(chunk);
} catch (error) {
console.error('Failed to get text block content:', String(error));
return NextResponse.json({ error: error.message || 'Failed to get text block content' }, { status: 500 });
}
}
// 删除文本块
export async function DELETE(request, { params }) {
try {
const { projectId, chunkId } = params;
// 验证参数
if (!projectId) {
return NextResponse.json({ error: 'Project ID cannot be empty' }, { status: 400 });
}
if (!chunkId) {
return NextResponse.json({ error: 'Text block ID cannot be empty' }, { status: 400 });
}
await deleteChunkById(chunkId);
return NextResponse.json({ message: 'Text block deleted successfully' });
} catch (error) {
console.error('Failed to delete text block:', String(error));
return NextResponse.json({ error: error.message || 'Failed to delete text block' }, { status: 500 });
}
}
// 编辑文本块内容
export async function PATCH(request, { params }) {
try {
const { projectId, chunkId } = params;
// 验证参数
if (!projectId) {
return NextResponse.json({ error: '项目ID不能为空' }, { status: 400 });
}
if (!chunkId) {
return NextResponse.json({ error: '文本块ID不能为空' }, { status: 400 });
}
// 解析请求体获取新内容
const requestData = await request.json();
const { content } = requestData;
if (!content) {
return NextResponse.json({ error: '内容不能为空' }, { status: 400 });
}
let res = await updateChunkById(chunkId, { content });
return NextResponse.json(res);
} catch (error) {
console.error('编辑文本块失败:', String(error));
return NextResponse.json({ error: error.message || '编辑文本块失败' }, { status: 500 });
}
}
================================================
FILE: app/api/projects/[projectId]/chunks/batch-content/route.js
================================================
import { getChunkContentsByNames } from '@/lib/db/chunks';
import { NextResponse } from 'next/server';
export async function POST(request, { params }) {
try {
const { projectId } = params;
const { chunkNames } = await request.json();
if (!chunkNames || !Array.isArray(chunkNames)) {
return NextResponse.json({ error: 'chunkNames 参数必须是数组' }, { status: 400 });
}
const chunkContentMap = await getChunkContentsByNames(projectId, chunkNames);
return NextResponse.json(chunkContentMap);
} catch (error) {
console.error('批量获取文本块内容失败:', error);
return NextResponse.json({ error: '批量获取文本块内容失败' }, { status: 500 });
}
}
================================================
FILE: app/api/projects/[projectId]/chunks/batch-edit/route.js
================================================
import { NextRequest, NextResponse } from 'next/server';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
/**
* 批量编辑文本块内容
* POST /api/projects/[projectId]/chunks/batch-edit
*/
export async function POST(request, { params }) {
try {
const { projectId } = params;
const body = await request.json();
const { position, content, chunkIds } = body;
// 验证参数
if (!position || !content || !chunkIds || !Array.isArray(chunkIds) || chunkIds.length === 0) {
return NextResponse.json({ error: 'Missing required parameters: position, content, chunkIds' }, { status: 400 });
}
if (!['start', 'end'].includes(position)) {
return NextResponse.json({ error: 'Position must be "start" or "end"' }, { status: 400 });
}
// 验证项目权限(获取要编辑的文本块)
const chunksToUpdate = await prisma.chunks.findMany({
where: {
id: { in: chunkIds },
projectId: projectId
},
select: {
id: true,
content: true,
name: true
}
});
if (chunksToUpdate.length === 0) {
return NextResponse.json({ error: 'Not found' }, { status: 404 });
}
if (chunksToUpdate.length !== chunkIds.length) {
return NextResponse.json({ error: 'Some chunks not found' }, { status: 400 });
}
// 准备更新数据
const updates = chunksToUpdate.map(chunk => {
let newContent;
if (position === 'start') {
// 在开头添加内容
newContent = content + '\n\n' + chunk.content;
} else {
// 在结尾添加内容
newContent = chunk.content + '\n\n' + content;
}
return {
where: { id: chunk.id },
data: {
content: newContent,
size: newContent.length,
updateAt: new Date()
}
};
});
async function processBatches(items, batchSize, processFn) {
const results = [];
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
const batchResults = await Promise.all(batch.map(processFn));
results.push(...batchResults);
}
return results;
}
const BATCH_SIZE = 50; // 每批处理 50 个
await processBatches(updates, BATCH_SIZE, update => prisma.chunks.update(update));
// 记录操作日志(可选)
console.log(`Successfully updated ${chunksToUpdate.length} chunks`);
return NextResponse.json({
success: true,
updatedCount: chunksToUpdate.length,
message: `Successfully updated ${chunksToUpdate.length} chunks`
});
} catch (error) {
console.error('批量编辑文本块失败:', error);
return NextResponse.json(
{
error: 'Batch edit chunks failed',
details: error.message
},
{ status: 500 }
);
} finally {
await prisma.$disconnect();
}
}
================================================
FILE: app/api/projects/[projectId]/chunks/name/route.js
================================================
import { NextResponse } from 'next/server';
import { getChunkByName } from '@/lib/db/chunks';
/**
* 根据文本块名称获取文本块
* @param {Request} request 请求对象
* @param {object} context 上下文,包含路径参数
* @returns {Promise<NextResponse>} 响应对象
*/
export async function GET(request, { params }) {
try {
const { projectId } = params;
// 从查询参数中获取 chunkName
const { searchParams } = new URL(request.url);
const chunkName = searchParams.get('chunkName');
if (!chunkName) {
return NextResponse.json({ error: '文本块名称不能为空' }, { status: 400 });
}
// 根据名称和项目ID查询文本块
const chunk = await getChunkByName(projectId, chunkName);
if (!chunk) {
return NextResponse.json({ error: '未找到指定的文本块' }, { status: 404 });
}
// 返回文本块信息
return NextResponse.json(chunk);
} catch (error) {
console.error('根据名称获取文本块失败:', String(error));
return NextResponse.json({ error: '获取文本块失败: ' + error.message }, { status: 500 });
}
}
================================================
FILE: app/api/projects/[projectId]/chunks/route.js
================================================
import { NextResponse } from 'next/server';
import { deleteChunkById, getChunkByFileIds, getChunkById, getChunksByFileIds, updateChunkById } from '@/lib/db/chunks';
// 获取文本块内容
export async function POST(request, { params }) {
try {
const { projectId } = params;
// 验证参数
if (!projectId) {
return NextResponse.json({ error: 'Project ID cannot be empty' }, { status: 400 });
}
const { array } = await request.json();
// 获取文本块内容
const chunk = await getChunksByFileIds(array);
return NextResponse.json(chunk);
} catch (error) {
console.error('Failed to get text block content:', String(error));
return NextResponse.json({ error: String(error) || 'Failed to get text block content' }, { status: 500 });
}
}
================================================
FILE: app/api/projects/[projectId]/config/route.js
================================================
import { NextResponse } from 'next/server';
import { getProject, updateProject, getTaskConfig } from '@/lib/db/projects';
// 获取项目配置
export async function GET(request, { params }) {
try {
const projectId = params.projectId;
const config = await getProject(projectId);
const taskConfig = await getTaskConfig(projectId);
return NextResponse.json({ ...config, ...taskConfig });
} catch (error) {
console.error('获取项目配置失败:', String(error));
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
// 更新项目配置
export async function PUT(request, { params }) {
try {
const projectId = params.projectId;
const newConfig = await request.json();
const currentConfig = await getProject(projectId);
// 只更新 prompts 部分
const updatedConfig = {
...currentConfig,
...newConfig.prompts
};
const config = await updateProject(projectId, updatedConfig);
return NextResponse.json(config);
} catch (error) {
console.error('更新项目配置失败:', String(error));
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
================================================
FILE: app/api/projects/[projectId]/custom-prompts/route.js
================================================
import { NextResponse } from 'next/server';
import {
getCustomPrompts,
getCustomPrompt,
saveCustomPrompt,
deleteCustomPrompt,
batchSaveCustomPrompts,
toggleCustomPrompt,
getPromptTemplates
} from '@/lib/db/custom-prompts';
// 获取项目的自定义提示词
export async function GET(request, { params }) {
try {
const { projectId } = params;
const { searchParams } = new URL(request.url);
const promptType = searchParams.get('promptType');
const language = searchParams.get('language');
if (!projectId) {
return NextResponse.json({ error: 'Project ID is required' }, { status: 400 });
}
const customPrompts = await getCustomPrompts(projectId, promptType, language);
const templates = await getPromptTemplates();
return NextResponse.json({
success: true,
customPrompts,
templates
});
} catch (error) {
console.error('获取自定义提示词失败:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
// 保存自定义提示词
export async function POST(request, { params }) {
try {
const { projectId } = params;
const body = await request.json();
if (!projectId) {
return NextResponse.json({ error: 'Project ID is required' }, { status: 400 });
}
// 批量保存
if (body.prompts && Array.isArray(body.prompts)) {
const results = await batchSaveCustomPrompts(projectId, body.prompts);
return NextResponse.json({
success: true,
results
});
}
// 单个保存
const { promptType, promptKey, language, content } = body;
if (!promptType || !promptKey || !language || content === undefined) {
return NextResponse.json(
{
error: 'promptType, promptKey, language and content are required'
},
{ status: 400 }
);
}
const result = await saveCustomPrompt(projectId, promptType, promptKey, language, content);
return NextResponse.json({
success: true,
result
});
} catch (error) {
console.error('保存自定义提示词失败:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
// 删除自定义提示词
export async function DELETE(request, { params }) {
try {
const { projectId } = params;
const { searchParams } = new URL(request.url);
const promptType = searchParams.get('promptType');
const promptKey = searchParams.get('promptKey');
const language = searchParams.get('language');
if (!projectId || !promptType || !promptKey || !language) {
return NextResponse.json(
{
error: 'projectId, promptType, promptKey and language are required'
},
{ status: 400 }
);
}
const success = await deleteCustomPrompt(projectId, promptType, promptKey, language);
return NextResponse.json({
success
});
} catch (error) {
console.error('删除自定义提示词失败:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
================================================
FILE: app/api/projects/[projectId]/custom-split/route.js
================================================
import { NextResponse } from 'next/server';
import { saveChunks, deleteChunksByFileId } from '@/lib/db/chunks';
import path from 'path';
import fs from 'fs/promises';
import { getProjectRoot } from '@/lib/db/base';
/**
* 处理自定义分块请求
* @param {Request} request - 请求对象
* @param {Object} params - 路由参数
* @returns {Promise<Response>} - 响应对象
*/
export async function POST(request, { params }) {
try {
const { projectId } = params;
const { fileId, fileName, content, splitPoints } = await request.json();
// 参数验证
if (!projectId || !fileId || !fileName || !content || !splitPoints) {
return NextResponse.json({ error: 'Missing required parameters' }, { status: 400 });
}
// 获取项目根目录
const projectRoot = await getProjectRoot();
const projectPath = path.join(projectRoot, projectId);
// 检查项目是否存在
try {
await fs.access(projectPath);
} catch (error) {
return NextResponse.json({ error: 'Project does not exist' }, { status: 404 });
}
// 先删除该文件已有的文本块
await deleteChunksByFileId(projectId, fileId);
// 根据分块点将文件内容分割成多个块
const customChunks = generateCustomChunks(projectId, fileId, fileName, content, splitPoints);
// 保存新的文本块
await saveChunks(customChunks);
return NextResponse.json({
success: true,
message: 'Custom chunks saved successfully',
totalChunks: customChunks.length
});
} catch (error) {
console.error('自定义分块处理出错:', String(error));
return NextResponse.json({ error: error.message || 'Failed to process custom split request' }, { status: 500 });
}
}
/**
* 根据分块点生成自定义文本块
* @param {string} projectId - 项目ID
* @param {string} fileId - 文件ID
* @param {string} fileName - 文件名
* @param {string} content - 文件内容
* @param {Array} splitPoints - 分块点数组
* @returns {Array} - 生成的文本块数组
*/
function generateCustomChunks(projectId, fileId, fileName, content, splitPoints) {
// 按位置排序分块点
const sortedPoints = [...splitPoints].sort((a, b) => a.position - b.position);
// 创建分块
const chunks = [];
let startPos = 0;
// 处理每个分块点
for (let i = 0; i < sortedPoints.length; i++) {
const endPos = sortedPoints[i].position;
// 提取当前分块内容
const chunkContent = content.substring(startPos, endPos);
// 跳过空白分块
if (chunkContent.trim().length === 0) {
startPos = endPos;
continue;
}
// 创建分块对象
const chunk = {
projectId,
name: `${path.basename(fileName, path.extname(fileName))}-part-${i + 1}`,
fileId,
fileName,
content: chunkContent,
summary: `${fileName} 自定义分块 ${i + 1}/${sortedPoints.length + 1}`,
size: chunkContent.length
};
chunks.push(chunk);
startPos = endPos;
}
// 添加最后一个分块(如果有内容)
const lastChunkContent = content.substring(startPos);
if (lastChunkContent.trim().length > 0) {
const lastChunk = {
projectId,
name: `${path.basename(fileName, path.extname(fileName))}-part-${sortedPoints.length + 1}`,
fileId,
fileName,
content: lastChunkContent,
summary: `${fileName} 自定义分块 ${sortedPoints.length + 1}/${sortedPoints.length + 1}`,
size: lastChunkContent.length
};
chunks.push(lastChunk);
}
return chunks;
}
================================================
FILE: app/api/projects/[projectId]/dataset-conversations/[conversationId]/route.js
================================================
/**
* 单个多轮对话数据集操作API
*/
import { NextResponse } from 'next/server';
import {
getDatasetConversationById,
updateDatasetConversation,
deleteDatasetConversation,
getConversationNavigationItems
} from '@/lib/db/dataset-conversations';
/**
* 获取单个多轮对话数据集详情
*/
export async function GET(request, { params }) {
try {
const { projectId, conversationId } = params;
const { searchParams } = new URL(request.url);
const operateType = searchParams.get('operateType');
// 如果是导航操作,返回导航项
if (operateType !== null) {
const data = await getConversationNavigationItems(projectId, conversationId, operateType);
return NextResponse.json(data);
}
const conversation = await getDatasetConversationById(conversationId);
if (!conversation) {
return NextResponse.json(
{
success: false,
message: '对话数据集不存在'
},
{ status: 404 }
);
}
if (conversation.projectId !== projectId) {
return NextResponse.json(
{
success: false,
message: '对话数据集不属于指定项目'
},
{ status: 403 }
);
}
return NextResponse.json(conversation);
} catch (error) {
console.error('获取多轮对话数据集详情失败:', error);
return NextResponse.json(
{
success: false,
message: error.message
},
{ status: 500 }
);
}
}
/**
* 更新多轮对话数据集
*/
export async function PUT(request, { params }) {
try {
const { projectId, conversationId } = params;
const body = await request.json();
// 验证对话数据集是否存在且属于项目
const conversation = await getDatasetConversationById(conversationId);
if (!conversation) {
return NextResponse.json(
{
success: false,
message: '对话数据集不存在'
},
{ status: 404 }
);
}
if (conversation.projectId !== projectId) {
return NextResponse.json(
{
success: false,
message: '对话数据集不属于指定项目'
},
{ status: 403 }
);
}
// 只允许更新特定字段
const allowedFields = ['score', 'tags', 'note', 'confirmed', 'aiEvaluation', 'messages'];
const updateData = {};
allowedFields.forEach(field => {
if (body.hasOwnProperty(field)) {
if (field === 'messages') {
// 将messages数组转换为rawMessages字符串存储
updateData['rawMessages'] = JSON.stringify(body[field]);
} else {
updateData[field] = body[field];
}
}
});
if (Object.keys(updateData).length === 0) {
return NextResponse.json(
{
success: false,
message: '没有有效的更新字段'
},
{ status: 400 }
);
}
const updatedConversation = await updateDatasetConversation(conversationId, updateData);
return NextResponse.json({
success: true,
data: updatedConversation
});
} catch (error) {
console.error('更新多轮对话数据集失败:', error);
return NextResponse.json(
{
success: false,
message: error.message
},
{ status: 500 }
);
}
}
/**
* 删除多轮对话数据集
*/
export async function DELETE(request, { params }) {
try {
const { projectId, conversationId } = params;
// 验证对话数据集是否存在且属于项目
const conversation = await getDatasetConversationById(conversationId);
if (!conversation) {
return NextResponse.json(
{
success: false,
message: '对话数据集不存在'
},
{ status: 404 }
);
}
if (conversation.projectId !== projectId) {
return NextResponse.json(
{
success: false,
message: '对话数据集不属于指定项目'
},
{ status: 403 }
);
}
await deleteDatasetConversation(conversationId);
return NextResponse.json({
success: true,
message: '删除成功'
});
} catch (error) {
console.error('删除多轮对话数据集失败:', error);
return NextResponse.json(
{
success: false,
message: error.message
},
{ status: 500 }
);
}
}
================================================
FILE: app/api/projects/[projectId]/dataset-conversations/export/route.js
================================================
/**
* 多轮对话数据集导出API
* 直接导出原始的 ShareGPT 格式数据集
*/
import { NextResponse } from 'next/server';
import { getAllDatasetConversations } from '@/lib/db/dataset-conversations';
/**
* 导出多轮对话数据集
*/
export async function GET(request, { params }) {
try {
const { projectId } = params;
const { searchParams } = new URL(request.url);
// 筛选条件
const filters = {
confirmed: searchParams.get('confirmed')
};
// 清除空值
Object.keys(filters).forEach(key => {
if (!filters[key]) delete filters[key];
});
// 获取所有对话数据集
const conversations = await getAllDatasetConversations(projectId, filters);
if (conversations.length === 0) {
return NextResponse.json([]);
}
// 转换为 ShareGPT 格式数组
const shareGptData = [];
for (const conversation of conversations) {
try {
// 解析 rawMessages
const messages = JSON.parse(conversation.rawMessages || '[]');
if (messages.length > 0) {
// 构建 ShareGPT 格式对象
const shareGptItem = {
messages: messages
};
shareGptData.push(shareGptItem);
}
} catch (error) {
console.error(`解析对话消息失败 ${conversation.id}:`, error);
// 跳过解析失败的对话,继续处理其他对话
continue;
}
}
return NextResponse.json(shareGptData);
} catch (error) {
console.error('导出多轮对话数据集失败:', error);
return NextResponse.json(
{
success: false,
message: error.message
},
{ status: 500 }
);
}
}
================================================
FILE: app/api/projects/[projectId]/dataset-conversations/route.js
================================================
/**
* 多轮对话数据集管理API
*/
import { NextResponse } from 'next/server';
import {
getDatasetConversationsByPagination,
getAllDatasetConversationIds,
createDatasetConversation
} from '@/lib/db/dataset-conversations';
import { generateMultiTurnConversation } from '@/lib/services/multi-turn/index';
/**
* 获取多轮对话数据集列表(支持分页和筛选)
*/
export async function GET(request, { params }) {
try {
const { projectId } = params;
const { searchParams } = new URL(request.url);
const getAllIds = searchParams.get('getAllIds') === 'true'; // 新增:获取所有对话ID的标志
// 筛选条件
const filters = {
keyword: searchParams.get('keyword'),
roleA: searchParams.get('roleA'),
roleB: searchParams.get('roleB'),
scenario: searchParams.get('scenario'),
scoreMin: searchParams.get('scoreMin'),
scoreMax: searchParams.get('scoreMax'),
confirmed: searchParams.get('confirmed')
};
// 清除空值
Object.keys(filters).forEach(key => {
if (!filters[key]) delete filters[key];
});
// 如果请求获取所有ID
if (getAllIds) {
const allConversationIds = await getAllDatasetConversationIds(projectId, filters);
return NextResponse.json({ allConversationIds });
}
// 正常分页查询
const page = parseInt(searchParams.get('page') || '1');
const pageSize = parseInt(searchParams.get('pageSize') || '20');
const result = await getDatasetConversationsByPagination(projectId, page, pageSize, filters);
return NextResponse.json({
success: true,
...result
});
} catch (error) {
console.error('获取多轮对话数据集失败:', error);
return NextResponse.json(
{
success: false,
message: error.message
},
{ status: 500 }
);
}
}
/**
* 创建多轮对话数据集
*/
export async function POST(request, { params }) {
try {
const { projectId } = params;
const body = await request.json();
const { questionId, systemPrompt, scenario, rounds, roleA, roleB, model, language = '中文' } = body;
if (!questionId) {
return NextResponse.json(
{
success: false,
message: '问题ID不能为空'
},
{ status: 400 }
);
}
if (!model || !model.modelId) {
return NextResponse.json(
{
success: false,
message: '模型配置不能为空'
},
{ status: 400 }
);
}
// 构建配置
const config = {
systemPrompt: systemPrompt || '',
scenario: scenario || '',
rounds: rounds || 3,
roleA: roleA || '用户',
roleB: roleB || '助手',
model,
language
};
// 生成多轮对话
const result = await generateMultiTurnConversation(projectId, questionId, config);
if (!result.success) {
return NextResponse.json(
{
success: false,
message: result.error
},
{ status: 500 }
);
}
return NextResponse.json({
success: true,
data: result.data
});
} catch (error) {
console.error('创建多轮对话数据集失败:', error);
return NextResponse.json(
{
success: false,
message: error.message
},
{ status: 500 }
);
}
}
================================================
FILE: app/api/projects/[projectId]/dataset-conversations/tags/route.js
================================================
import { NextResponse } from 'next/server';
import { getAllDatasetConversations } from '@/lib/db/dataset-conversations';
/**
* 获取项目中多轮对话数据集的所有标签
*/
export async function GET(request, { params }) {
try {
const { projectId } = params;
if (!projectId) {
return NextResponse.json({ error: '项目ID不能为空' }, { status: 400 });
}
// 获取项目所有对话数据集
const conversations = await getAllDatasetConversations(projectId);
// 提取所有标签
const allTags = new Set();
conversations.forEach(conversation => {
if (conversation.tags && typeof conversation.tags === 'string') {
const tags = conversation.tags.split(/\s+/).filter(tag => tag.trim().length > 0);
tags.forEach(tag => allTags.add(tag.trim()));
}
});
return NextResponse.json({
success: true,
tags: Array.from(allTags).sort()
});
} catch (error) {
console.error('获取对话标签失败:', error);
return NextResponse.json(
{
success: false,
message: error.message
},
{ status: 500 }
);
}
}
================================================
FILE: app/api/projects/[projectId]/datasets/[datasetId]/copy-to-eval/route.js
================================================
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
export async function POST(req, { params }) {
try {
const { projectId, datasetId } = params;
// 1. 获取数据集详情
const dataset = await db.datasets.findUnique({
where: { id: datasetId, projectId }
});
if (!dataset) {
return NextResponse.json({ error: 'Dataset not found' }, { status: 404 });
}
// 2. 尝试通过 questionId 查找关联的 chunkId
let chunkId = null;
if (dataset.questionId) {
const question = await db.questions.findUnique({
where: { id: dataset.questionId }
});
if (question) {
chunkId = question.chunkId;
}
}
// 3. 创建评估数据集记录
// 默认使用 open_ended 类型,因为通常数据集是问答对,适合作为评估
let evalTags = [];
try {
evalTags = JSON.parse(dataset.tags || '[]');
if (!Array.isArray(evalTags)) evalTags = [];
} catch (e) {
evalTags = [];
}
// 排除 'Eval' 标签,并将数组转为逗号分隔的字符串
const evalTagsString = evalTags.filter(tag => tag !== 'Eval').join(',');
const evalDataset = await db.evalDatasets.create({
data: {
projectId,
question: dataset.question,
questionType: 'open_ended',
correctAnswer: dataset.answer,
tags: evalTagsString,
note: dataset.note,
chunkId: chunkId,
options: '' // 开放题不需要选项
}
});
// 4. 更新原数据集,添加 'Eval' 标签
let currentTags = [];
try {
currentTags = JSON.parse(dataset.tags || '[]');
} catch (e) {
// ignore error
}
if (!currentTags.includes('Eval')) {
currentTags.push('Eval');
await db.datasets.update({
where: { id: datasetId },
data: {
tags: JSON.stringify(currentTags)
}
});
}
return NextResponse.json({ success: true, evalDataset });
} catch (error) {
console.error('Failed to copy dataset to eval:', error);
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
}
}
================================================
FILE: app/api/projects/[projectId]/datasets/[datasetId]/evaluate/route.js
================================================
import { NextResponse } from 'next/server';
import { evaluateDataset } from '@/lib/services/datasets/evaluation';
/**
* 评估单个数据集的质量
*/
export async function POST(request, { params }) {
try {
const { projectId, datasetId } = params;
const { model, language = 'zh-CN' } = await request.json();
if (!projectId || !datasetId) {
return NextResponse.json({ success: false, message: '项目ID和数据集ID不能为空' }, { status: 400 });
}
if (!model) {
return NextResponse.json({ success: false, message: '模型配置不能为空' }, { status: 400 });
}
// 使用评估服务进行数据集评估
const result = await evaluateDataset(projectId, datasetId, model, language);
if (!result.success) {
return NextResponse.json({ success: false, message: result.error }, { status: 500 });
}
return NextResponse.json({
success: true,
message: '数据集评估完成',
data: result.data
});
} catch (error) {
console.error('数据集评估失败:', error);
return NextResponse.json({ success: false, message: `评估失败: ${error.message}` }, { status: 500 });
}
}
================================================
FILE: app/api/projects/[projectId]/datasets/[datasetId]/route.js
================================================
import { NextResponse } from 'next/server';
import { getDatasetsById, getDatasetsCounts, getNavigationItems, updateDatasetMetadata } from '@/lib/db/datasets';
/**
* 获取项目的所有数据集
*/
export async function GET(request, { params }) {
try {
const { projectId, datasetId } = params;
// 验证项目ID
if (!projectId) {
return NextResponse.json({ error: '项目ID不能为空' }, { status: 400 });
}
if (!datasetId) {
return NextResponse.json({ error: '数据集ID不能为空' }, { status: 400 });
}
const { searchParams } = new URL(request.url);
const operateType = searchParams.get('operateType');
if (operateType !== null) {
const data = await getNavigationItems(projectId, datasetId, operateType);
return NextResponse.json(data);
}
const datasets = await getDatasetsById(datasetId);
let counts = await getDatasetsCounts(projectId);
return NextResponse.json({ datasets, ...counts });
} catch (error) {
console.error('获取数据集详情失败:', String(error));
return NextResponse.json(
{
error: error.message || '获取数据集详情失败'
},
{ status: 500 }
);
}
}
/**
* 更新数据集元数据(评分、标签、备注)
*/
export async function PATCH(request, { params }) {
try {
const { projectId, datasetId } = params;
// 验证参数
if (!projectId) {
return NextResponse.json({ error: '项目ID不能为空' }, { status: 400 });
}
if (!datasetId) {
return NextResponse.json({ error: '数据集ID不能为空' }, { status: 400 });
}
const body = await request.json();
const { score, tags, note } = body;
// 验证评分范围
if (score !== undefined && (score < 0 || score > 5)) {
return NextResponse.json({ error: '评分必须在0-5之间' }, { status: 400 });
}
// 验证标签格式
if (tags !== undefined && !Array.isArray(tags)) {
return NextResponse.json({ error: '标签必须是数组格式' }, { status: 400 });
}
// 更新数据集元数据
const updatedDataset = await updateDatasetMetadata(datasetId, { score, tags, note });
return NextResponse.json({
success: true,
dataset: updatedDataset
});
} catch (error) {
console.error('更新数据集元数据失败:', String(error));
return NextResponse.json(
{
error: error.message || '更新数据集元数据失败'
},
{ status: 500 }
);
}
}
================================================
FILE: app/api/projects/[projectId]/datasets/[datasetId]/token-count/route.js
================================================
import { NextResponse } from 'next/server';
import { getDatasetsById } from '@/lib/db/datasets';
import { getEncoding } from '@langchain/core/utils/tiktoken';
/**
* 异步计算数据集文本的Token数量
*/
export async function GET(request, { params }) {
try {
const { projectId, datasetId } = params;
if (!datasetId) {
return NextResponse.json({ error: '数据集ID不能为空' }, { status: 400 });
}
const datasets = await getDatasetsById(datasetId);
const tokenCounts = {
answerTokens: 0,
cotTokens: 0
};
try {
if (datasets.answer || datasets.cot) {
// 使用 cl100k_base 编码,适用于 gpt-3.5-turbo 和 gpt-4
const encoding = await getEncoding('cl100k_base');
if (datasets.answer) {
const tokens = encoding.encode(datasets.answer);
tokenCounts.answerTokens = tokens.length;
}
if (datasets.cot) {
const tokens = encoding.encode(datasets.cot);
tokenCounts.cotTokens = tokens.length;
}
}
} catch (error) {
console.error('计算Token数量失败:', String(error));
return NextResponse.json({ error: '计算Token数量失败' }, { status: 500 });
}
return NextResponse.json(tokenCounts);
} catch (error) {
console.error('获取Token计数失败:', String(error));
return NextResponse.json(
{
error: error.message || '获取Token计数失败'
},
{ status: 500 }
);
}
}
================================================
FILE: app/api/projects/[projectId]/datasets/batch-evaluate/route.js
================================================
/**
* 批量数据集评估任务API
* 创建批量评估数据集质量的异步任务
*/
import { NextResponse } from 'next/server';
import { db } from '@/lib/db/index';
import { processTask } from '@/lib/services/tasks/index';
/**
* 创建批量数据集评估任务
*/
export async function POST(request, { params }) {
try {
const { projectId } = params;
const { model, language = 'zh-CN' } = await request.json();
if (!projectId) {
return NextResponse.json({ success: false, message: '项目ID不能为空' }, { status: 400 });
}
if (!model || !model.modelId) {
return NextResponse.json({ success: false, message: '模型配置不能为空' }, { status: 400 });
}
// 创建批量评估任务
const newTask = await db.task.create({
data: {
projectId,
taskType: 'dataset-evaluation',
status: 0, // 初始状态: 处理中
modelInfo: JSON.stringify(model),
language: language || 'zh-CN',
detail: '',
totalCount: 0,
note: '准备开始批量评估数据集质量...',
completedCount: 0
}
});
// 异步处理任务
processTask(newTask.id).catch(err => {
console.error(`批量评估任务启动失败: ${newTask.id}`, String(err));
});
return NextResponse.json({
success: true,
message: '批量评估任务已创建',
data: { taskId: newTask.id }
});
} catch (error) {
console.error('创建批量评估任务失败:', error);
return NextResponse.json({ success: false, message: `创建任务失败: ${error.message}` }, { status: 500 });
}
}
================================================
FILE: app/api/projects/[projectId]/datasets/export/route.js
================================================
import { NextResponse } from 'next/server';
import {
getDatasets,
getBalancedDatasetsByTags,
getTagsWithDatasetCounts,
getDatasetsBatch,
getBalancedDatasetsByTagsBatch,
getDatasetsByIds,
getDatasetsByIdsBatch
} from '@/lib/db/datasets';
/**
* 获取导出数据集
*/
export async function GET(request, { params }) {
try {
const { projectId } = params;
const { searchParams } = new URL(request.url);
// 验证项目ID
if (!projectId) {
return NextResponse.json({ error: 'Project ID cannot be empty' }, { status: 400 });
}
const confirmedParam = searchParams.get('confirmed');
const confirmed = confirmedParam === null ? undefined : confirmedParam === 'true';
// 获取标签统计信息
const tagStats = await getTagsWithDatasetCounts(projectId, confirmed);
return NextResponse.json(tagStats);
} catch (error) {
console.error('Failed to get tag statistics:', String(error));
return NextResponse.json(
{
error: error.message || 'Failed to get tag statistics'
},
{ status: 500 }
);
}
}
/**
* 获取标签统计信息
*/
export async function POST(request, { params }) {
try {
const { projectId } = params;
const body = await request.json();
// 验证项目ID
if (!projectId) {
return NextResponse.json({ error: 'Project ID cannot be empty' }, { status: 400 });
}
let status = body.status;
let confirmed = undefined;
if (status === 'confirmed') confirmed = true;
if (status === 'unconfirmed') confirmed = false;
// 检查是否是分批导出模式
const batchMode = body.batchMode ? 'true' : 'false';
const offset = body.offset ?? 0;
const batchSize = body.batchSize ?? 1000;
// 检查是否是平衡导出
const balanceMode = body.balanceMode ? 'true' : 'false';
const balanceConfig = body.balanceConfig;
// 检查是否有选中的数据集 ID
const selectedIds = Array.isArray(body.selectedIds) ? body.selectedIds : null;
if (batchMode === 'true') {
// 分批导出模式
if (selectedIds && selectedIds.length > 0) {
// 按选中 ID 分批导出
const datasets = await getDatasetsByIdsBatch(projectId, selectedIds, offset, batchSize);
const hasMore = datasets.length === batchSize;
return NextResponse.json({
data: datasets,
hasMore,
offset: offset + datasets.length
});
} else if (balanceMode === 'true' && balanceConfig) {
// 平衡分批导出
const parsedConfig = typeof balanceConfig === 'string' ? JSON.parse(balanceConfig) : balanceConfig;
const result = await getBalancedDatasetsByTagsBatch(projectId, parsedConfig, confirmed, offset, batchSize);
return NextResponse.json({
data: result.data,
hasMore: result.hasMore,
offset: offset + result.data.length
});
} else {
// 常规分批导出
const datasets = await getDatasetsBatch(projectId, confirmed, offset, batchSize);
const hasMore = datasets.length === batchSize;
return NextResponse.json({
data: datasets,
hasMore,
offset: offset + datasets.length
});
}
} else {
// 传统一次性导出模式(保持向后兼容)
if (selectedIds && selectedIds.length > 0) {
// 按选中 ID 导出
const datasets = await getDatasetsByIds(projectId, selectedIds);
return NextResponse.json(datasets);
} else if (balanceMode === 'true' && balanceConfig) {
// 平衡导出模式
const parsedConfig = typeof balanceConfig === 'string' ? JSON.parse(balanceConfig) : balanceConfig;
const datasets = await getBalancedDatasetsByTags(projectId, parsedConfig, confirmed);
return NextResponse.json(datasets);
} else {
// 常规导出模式
const datasets = await getDatasets(projectId, confirmed);
return NextResponse.json(datasets);
}
}
} catch (error) {
console.error('Failed to get datasets:', String(error));
return NextResponse.json(
{
error: error.message || 'Failed to get datasets'
},
{ status: 500 }
);
}
}
================================================
FILE: app/api/projects/[projectId]/datasets/generate-eval-variant/route.js
================================================
import { NextResponse } from 'next/server';
import { getDatasetsById } from '@/lib/db/datasets';
import LLMClient from '@/lib/llm/core/index';
import { getEvalQuestionPrompt } from '@/lib/llm/prompts/evalQuestion';
import { extractJsonFromLLMOutput } from '@/lib/llm/common/util';
export async function POST(request, { params }) {
try {
const { projectId } = params;
const { datasetId, model, language, questionType = 'open_ended', count = 1 } = await request.json();
if (!datasetId || !model) {
return NextResponse.json({ error: 'Missing required parameters' }, { status: 400 });
}
// 1. 获取原数据集
const dataset = await getDatasetsById(datasetId);
if (!dataset) {
return NextResponse.json({ error: 'Dataset not found' }, { status: 404 });
}
// 2. 构建提示词
// 将原问题和答案合并作为上下文文本
const text = `Question: ${dataset.question}\nAnswer: ${dataset.answer}`;
const prompt = await getEvalQuestionPrompt(language || 'zh-CN', questionType, { text, number: count }, projectId);
// 3. 调用 LLM
const client = new LLMClient(model);
const response = await client.getResponse(prompt);
const result = extractJsonFromLLMOutput(response);
// 结果应该是一个数组
if (!result || !Array.isArray(result)) {
throw new Error('Failed to parse LLM output or output is not an array');
}
return NextResponse.json({ success: true, data: result });
} catch (error) {
console.error('Generate eval variant failed:', error);
return NextResponse.json({ error: error.message || 'Internal Server Error' }, { status: 500 });
}
}
================================================
FILE: app/api/projects/[projectId]/datasets/import/route.js
================================================
import { NextResponse } from 'next/server';
import { createDataset } from '@/lib/db/datasets';
import { nanoid } from 'nanoid';
export async function POST(request, { params }) {
try {
const { projectId } = params;
const { datasets, sourceInfo } = await request.json();
if (!datasets || !Array.isArray(datasets)) {
return NextResponse.json({ error: 'Invalid datasets data' }, { status: 400 });
}
const results = [];
const errors = [];
let successCount = 0;
let skippedCount = 0;
for (let i = 0; i < datasets.length; i++) {
try {
const dataset = datasets[i];
// 安全获取与清洗字段
const q = typeof dataset?.question === 'string' ? dataset.question.trim() : '';
const a = typeof dataset?.answer === 'string' ? dataset.answer.trim() : '';
// 验证必填字段:缺失则跳过
if (!q || !a) {
errors.push(`第 ${i + 1} 条记录缺少必填字段(question/answer),已跳过`);
skippedCount++;
continue;
}
// 规范化可选字段
const chunkName = dataset?.chunkName || 'Imported Data';
const chunkContent = dataset?.chunkContent || 'Imported from external source';
const model = dataset?.model || 'imported';
const questionLabel = dataset?.questionLabel || '';
const cot = typeof dataset?.cot === 'string' ? dataset.cot : '';
const confirmed = typeof dataset?.confirmed === 'boolean' ? dataset.confirmed : false;
const score = typeof dataset?.score === 'number' ? dataset.score : 0;
// tags: 支持数组/字符串/对象
let tags = '[]';
if (Array.isArray(dataset?.tags)) {
try {
tags = JSON.stringify(dataset.tags);
} catch {
tags = '[]';
}
} else if (typeof dataset?.tags === 'string') {
tags = dataset.tags;
} else if (dataset?.tags && typeof dataset.tags === 'object') {
try {
tags = JSON.stringify(dataset.tags);
} catch {
tags = '[]';
}
}
// other: 对象或字符串
let other = '{}';
if (typeof dataset?.other === 'string') {
other = dataset.other;
} else if (dataset?.other && typeof dataset.other === 'object') {
try {
other = JSON.stringify(dataset.other);
} catch {
other = '{}';
}
}
const note = typeof dataset?.note === 'string' ? dataset.note : '';
// 创建数据集记录
const newDataset = await createDataset({
projectId,
questionId: nanoid(), // 生成唯一的问题ID
question: q,
answer: a,
chunkName,
chunkContent,
model,
questionLabel,
cot,
confirmed,
score,
tags,
note,
other
});
results.push(newDataset);
successCount++;
} catch (error) {
errors.push(`第 ${i + 1} 条记录: ${error.message}`);
}
}
return NextResponse.json({
success: successCount,
total: datasets.length,
failed: errors.length,
skipped: skippedCount,
errors,
sourceInfo
});
} catch (error) {
console.error('Import datasets error:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
================================================
FILE: app/api/projects/[projectId]/datasets/optimize/route.js
================================================
import { NextResponse } from 'next/server';
import { getDatasetsById, updateDataset } from '@/lib/db/datasets';
import { getQuestionById } from '@/lib/db/questions';
import { getChunkById } from '@/lib/db/chunks';
import LLMClient from '@/lib/llm/core/index';
import { getNewAnswerPrompt } from '@/lib/llm/prompts/newAnswer';
import { extractJsonFromLLMOutput } from '@/lib/llm/common/util';
// 优化数据集答案
export async function POST(request, { params }) {
try {
const { projectId } = params;
// 验证项目ID
if (!projectId) {
return NextResponse.json({ error: 'Project ID cannot be empty' }, { status: 400 });
}
// 获取请求体
const { datasetId, model, advice, language } = await request.json();
if (!datasetId) {
return NextResponse.json({ error: 'Dataset ID cannot be empty' }, { status: 400 });
}
if (!model) {
return NextResponse.json({ error: 'Model cannot be empty' }, { status: 400 });
}
if (!advice) {
return NextResponse.json({ error: 'Please provide optimization suggestions' }, { status: 400 });
}
// 获取数据集内容
const dataset = await getDatasetsById(datasetId);
if (!dataset) {
return NextResponse.json({ error: 'Dataset does not exist' }, { status: 404 });
}
// 创建LLM客户端
const llmClient = new LLMClient(model);
const { question, answer, cot, chunkContent: storedChunkContent, questionId } = dataset;
let chunkContent = storedChunkContent || '';
if (!chunkContent && questionId) {
try {
const questionRecord = await getQuestionById(questionId);
if (questionRecord?.chunkId) {
const chunkRecord = await getChunkById(questionRecord.chunkId);
chunkContent = chunkRecord?.content || '';
}
} catch (error) {
console.error('Failed to load chunk content by questionId:', error);
}
}
// 生成优化后的答案和思维链
const prompt = await getNewAnswerPrompt(language, { question, answer, cot, advice, chunkContent }, projectId);
const response = await llmClient.getResponse(prompt);
// 从LLM输出中提取JSON格式的优化结果
const optimizedResult = extractJsonFromLLMOutput(response);
if (!optimizedResult || !optimizedResult.answer) {
return NextResponse.json({ error: 'Failed to optimize answer, please try again' }, { status: 500 });
}
// 更新数据集
const updatedDataset = {
...dataset,
answer: optimizedResult.answer,
cot: cot ? optimizedResult.cot || cot : '' // 如果没有提供思考过程,则不更新
};
await updateDataset(updatedDataset);
// 返回优化后的数据集
return NextResponse.json({
success: true,
dataset: updatedDataset
});
} catch (error) {
console.error('Failed to optimize answer:', String(error));
return NextResponse.json({ error: error.message || 'Failed to optimize answer' }, { status: 500 });
}
}
================================================
FILE: app/api/projects/[projectId]/datasets/route.js
================================================
import { NextResponse } from 'next/server';
import {
deleteDataset,
getDatasetsByPagination,
getDatasetsIds,
getDatasetsById,
updateDataset
} from '@/lib/db/datasets';
import datasetService from '@/lib/services/datasets';
// 优化思维链函数已移至服务层
/**
* 生成数据集(为单个问题生成答案)
*/
export async function POST(request, { params }) {
try {
const { projectId } = params;
const { questionId, model, language } = await request.json();
// 使用数据集生成服务
const result = await datasetService.generateDatasetForQuestion(projectId, questionId, {
model,
language
});
return NextResponse.json(result);
} catch (error) {
console.error('Failed to generate dataset:', String(error));
return NextResponse.json(
{
error: error.message || 'Failed to generate dataset'
},
{ status: 500 }
);
}
}
/**
* 获取项目的所有数据集
*/
export async function GET(request, { params }) {
try {
const { projectId } = params;
const { searchParams } = new URL(request.url);
// 验证项目ID
if (!projectId) {
return NextResponse.json({ error: '项目ID不能为空' }, { status: 400 });
}
const page = parseInt(searchParams.get('page')) || 1;
const size = parseInt(searchParams.get('size')) || 10;
const input = searchParams.get('input');
const field = searchParams.get('field') || 'question';
const status = searchParams.get('status');
const hasCot = searchParams.get('hasCot');
const isDistill = searchParams.get('isDistill');
const scoreRange = searchParams.get('scoreRange');
const customTag = searchParams.get('customTag');
const noteKeyword = searchParams.get('noteKeyword');
const chunkName = searchParams.get('chunkName');
let confirmed = undefined;
if (status === 'confirmed') confirmed = true;
if (status === 'unconfirmed') confirmed = false;
let selectedAll = searchParams.get('selectedAll');
if (selectedAll) {
let data = await getDatasetsIds(
projectId,
confirmed,
input,
field,
hasCot,
isDistill,
scoreRange,
customTag,
noteKeyword,
chunkName
);
return NextResponse.json(data);
}
// 获取数据集
const datasets = await getDatasetsByPagination(
projectId,
page,
size,
confirmed,
input,
field, // 传递搜索字段参数
hasCot, // 传递思维链筛选参数
isDistill, // 传递蒸馏数据集筛选参数
scoreRange, // 传递评分范围筛选参数
customTag, // 传递自定义标签筛选参数
noteKeyword, // 传递备注关键字筛选参数
chunkName // 传递文本块名称筛选参数
);
return NextResponse.json(datasets);
} catch (error) {
console.error('获取数据集失败:', String(error));
return NextResponse.json(
{
error: error.message || '获取数据集失败'
},
{ status: 500 }
);
}
}
/**
* 删除数据集
*/
export async function DELETE(request) {
try {
const { searchParams } = new URL(request.url);
const datasetId = searchParams.get('id');
if (!datasetId) {
return NextResponse.json(
{
error: 'Dataset ID cannot be empty'
},
{ status: 400 }
);
}
await deleteDataset(datasetId);
return NextResponse.json({
success: true,
message: 'Dataset deleted successfully'
});
} catch (error) {
console.error('Failed to delete dataset:', error);
return NextResponse.json(
{
error: error.message || 'Failed to delete dataset'
},
{ status: 500 }
);
}
}
/**
* 编辑数据集
*/
export async function PATCH(request) {
try {
const { searchParams } = new URL(request.url);
const datasetId = searchParams.get('id');
const { answer, cot, question, confirmed } = await request.json();
if (!datasetId) {
return NextResponse.json(
{
error: 'Dataset ID cannot be empty'
},
{ status: 400 }
);
}
// 获取所有数据集
let dataset = await getDatasetsById(datasetId);
if (!dataset) {
return NextResponse.json(
{
error: 'Dataset does not exist'
},
{ status: 404 }
);
}
let data = { id: datasetId };
if (confirmed !== undefined) data.confirmed = confirmed;
if (answer) data.answer = answer;
if (cot) data.cot = cot;
if (question) data.question = question;
// 保存更新后的数据集列表
await updateDataset(data);
return NextResponse.json({
success: true,
message: 'Dataset updated successfully',
dataset: dataset
});
} catch (error) {
console.error('Failed to update dataset:', String(error));
return NextResponse.json(
{
error: error.message || 'Failed to update dataset'
},
{ status: 500 }
);
}
}
================================================
FILE: app/api/projects/[projectId]/datasets/tags/route.js
================================================
import { NextResponse } from 'next/server';
import { getUsedCustomTags } from '@/lib/db/datasets';
/**
* 获取项目中使用过的自定义标签
*/
export async function GET(request, { params }) {
try {
const { projectId } = params;
// 验证项目ID
if (!projectId) {
return NextResponse.json({ error: '项目ID不能为空' }, { status: 400 });
}
const tags = await getUsedCustomTags(projectId);
return NextResponse.json({ tags });
} catch (error) {
console.error('获取自定义标签失败:', String(error));
return NextResponse.json(
{
error: error.message || '获取自定义标签失败'
},
{ status: 500 }
);
}
}
================================================
FILE: app/api/projects/[projectId]/default-prompts/route.js
================================================
import { NextResponse } from 'next/server';
// 获取默认提示词内容
export async function GET(request, { params }) {
try {
const { searchParams } = new URL(request.url);
const promptType = searchParams.get('promptType');
const promptKey = searchParams.get('promptKey');
if (!promptType || !promptKey) {
return NextResponse.json({ error: 'promptType and promptKey are required' }, { status: 400 });
}
// 动态导入对应的提示词模块
let promptModule;
try {
promptModule = await import(`@/lib/llm/prompts/${promptType}`);
} catch (error) {
return NextResponse.json({ error: `Prompt module ${promptType} not found` }, { status: 404 });
}
// 获取指定的提示词常量
const promptContent = promptModule[promptKey];
if (!promptContent) {
return NextResponse.json({ error: `Prompt key ${promptKey} not found in module ${promptType}` }, { status: 404 });
}
return NextResponse.json({
success: true,
content: promptContent,
promptType,
promptKey
});
} catch (error) {
console.error('获取默认提示词失败:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}
================================================
FILE: app/api/projects/[projectId]/distill/questions/by-tag/route.js
================================================
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
/**
* 根据标签ID获取问题列表
*/
export async function GET(request, { params }) {
try {
const { projectId } = params;
const { searchParams } = new URL(request.url);
const tagId = searchParams.get('tagId');
// 验证参数
if (!projectId) {
return NextResponse.json({ error: '项目ID不能为空' }, { status: 400 });
}
if (!tagId) {
return NextResponse.json({ error: '标签ID不能为空' }, { status: 400 });
}
// 获取标签信息
const tag = await db.tags.findUnique({
where: { id: tagId }
});
if (!tag) {
return NextResponse.json({ error: '标签不存在' }, { status: 404 });
}
// 获取或创建蒸馏文本块
let distillChunk = await db.chunks.findFirst({
where: {
projectId,
name: 'Distilled Content'
}
});
if (!distillChunk) {
// 创建一个特殊的蒸馏文本块
distillChunk = await db.chunks.create({
data: {
name: 'Distilled Content',
projectId,
fileId: 'distilled',
fileName: 'distilled.md',
content:
'This text block is used to store questions generated through data distillation and is not related to actual literature.',
summary: 'Questions generated through data distillation',
size: 0
}
});
}
const questions = await db.questions.findMany({
where: {
projectId,
label: tag.label,
chunkId: distillChunk.id
}
});
return NextResponse.json(questions);
} catch (error) {
console.error('[distill/questions/by-tag] 获取问题失败:', String(error));
return NextResponse.json({ error: error.message || '获取问题失败' }, { status: 500 });
}
}
================================================
FILE: app/api/projects/[projectId]/distill/questions/route.js
================================================
import { NextResponse } from 'next/server';
import { distillQuestionsPrompt } from '@/lib/llm/prompts/distillQuestions';
import { db } from '@/lib/db';
const LLMClient = require('@/lib/llm/core');
/**
* 生成问题接口:根据某个标签链路构造指定数量的问题
*/
export async function POST(request, { params }) {
try {
const { projectId } = params;
// 验证项目ID
if (!projectId) {
return NextResponse.json({ error: '项目ID不能为空' }, { status: 400 });
}
const { tagPath, currentTag, tagId, count = 5, model, language = 'zh' } = await request.json();
if (!currentTag || !tagPath) {
const errorMsg = language === 'en' ? 'Tag information cannot be empty' : '标签信息不能为空';
return NextResponse.json({ error: errorMsg }, { status: 400 });
}
// 首先获取或创建蒸馏文本块
let distillChunk = await db.chunks.findFirst({
where: {
projectId,
name: 'Distilled Content'
}
});
if (!distillChunk) {
// 创建一个特殊的蒸馏文本块
distillChunk = await db.chunks.create({
data: {
name: 'Distilled Content',
projectId,
fileId: 'distilled',
fileName: 'distilled.md',
content:
'This text block is used to store questions generated through data distillation and is not related to actual literature.',
summary: 'Questions generated through data distillation',
size: 0
}
});
}
// 获取已有的问题,避免重复
const existingQuestions = await db.questions.findMany({
where: {
projectId,
label: currentTag,
chunkId: distillChunk.id // 使用蒸馏文本块的 ID
},
select: { question: true }
});
const existingQuestionTexts = existingQuestions.map(q => q.question);
const llmClient = new LLMClient(model);
const prompt = await distillQuestionsPrompt(
language,
{ tagPath, currentTag, count, existingQuestionTexts },
projectId
);
const { answer } = await llmClient.getResponseWithCOT(prompt);
let questions = [];
try {
questions = JSON.parse(answer);
} catch (error) {
console.error('解析问题JSON失败:', String(error));
// 尝试使用正则表达式提取问题
const matches = answer.match(/"([^"]+)"/g);
if (matches) {
questions = matches.map(match => match.replace(/"/g, ''));
}
}
// 保存问题到数据库
const savedQuestions = [];
for (const questionText of questions) {
const question = await db.questions.create({
data: {
question: questionText,
projectId,
label: currentTag,
chunkId: distillChunk.id
}
});
savedQuestions.push(question);
}
return NextResponse.json(savedQuestions);
} catch (error) {
console.error('生成问题失败:', String(error));
return NextResponse.json({ error: error.message || '生成问题失败' }, { status: 500 });
}
}
================================================
FILE: app/api/projects/[projectId]/distill/tags/[tagId]/route.js
================================================
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
/**
* 更新标签接口
*/
export async function PUT(request, { params }) {
try {
const { projectId, tagId } = params;
// 验证参数
if (!projectId || !tagId) {
return NextResponse.json({ error: '项目ID和标签ID不能为空' }, { status: 400 });
}
const { label } = await request.json();
if (!label || !label.trim()) {
return NextResponse.json({ error: '标签名称不能为空' }, { status: 400 });
}
// 检查标签是否存在
const existingTag = await db.tags.findUnique({
where: { id: tagId }
});
if (!existingTag) {
return NextResponse.json({ error: '标签不存在' }, { status: 404 });
}
// 检查项目ID是否匹配
if (existingTag.projectId !== projectId) {
return NextResponse.json({ error: '无权限编辑此标签' }, { status: 403 });
}
// 检查新标签名称是否已存在(同级标签)
const duplicateTag = await db.tags.findFirst({
where: {
projectId,
label: label.trim(),
parentId: existingTag.parentId,
id: { not: tagId }
}
});
if (duplicateTag) {
return NextResponse.json({ error: '同级标签名称已存在' }, { status: 400 });
}
// 更新标签
const updatedTag = await db.tags.update({
where: { id: tagId },
data: { label: label.trim() }
});
return NextResponse.json(updatedTag);
} catch (error) {
console.error('[标签编辑] 更新标签失败:', String(error));
return NextResponse.json({ error: error.message || '更新标签失败' }, { status: 500 });
}
}
================================================
FILE: app/api/projects/[projectId]/distill/tags/all/route.js
================================================
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
/**
* 获取项目的所有蒸馏标签
*/
export async function GET(request, { params }) {
try {
const { projectId } = params;
// 验证项目ID
if (!projectId) {
return NextResponse.json({ error: '项目ID不能为空' }, { status: 400 });
}
// 获取所有标签
const tags = await db.tags.findMany({
where: {
projectId
},
orderBy: {
label: 'asc'
}
});
return NextResponse.json(tags);
} catch (error) {
console.error('获取蒸馏标签失败:', String(error));
return NextResponse.json({ error: error.message || '获取蒸馏标签失败' }, { status: 500 });
}
}
================================================
FILE: app/api/projects/[projectId]/distill/tags/route.js
================================================
import { NextResponse } from 'next/server';
import { distillTagsPrompt } from '@/lib/llm/prompts/distillTags';
import { db } from '@/lib/db';
import { getProject } from '@/lib/db/projects';
const LLMClient = require('@/lib/llm/core');
/**
* 生成标签接口:根据顶级主题、某级标签构造指定数量的子标签
*/
export async function POST(request, { params }) {
try {
const { projectId } = params;
// 验证项目ID
if (!projectId) {
return NextResponse.json({ error: '项目ID不能为空' }, { status: 400 });
}
const { parentTag, parentTagId, tagPath, count = 10, model, language = 'zh' } = await request.json();
if (!parentTag) {
const errorMsg = language === 'en' ? 'Topic tag name cannot be empty' : '主题标签名称不能为空';
return NextResponse.json({ error: errorMsg }, { status: 400 });
}
// 查询现有标签
const existingTags = await db.tags.findMany({
where: {
projectId,
parentId: parentTagId || null
}
});
const existingTagNames = existingTags.map(tag => tag.label);
// 创建LLM客户端
const llmClient = new LLMClient(model);
// 生成提示词
const prompt = await distillTagsPrompt(
language,
{ tagPath, parentTag, existingTags: existingTagNames, count },
projectId
);
// 调用大模型生成标签
const { answer } = await llmClient.getResponseWithCOT(prompt);
// 解析返回的标签
let tags = [];
try {
tags = JSON.parse(answer);
} catch (error) {
console.error('解析标签JSON失败:', String(error));
// 尝试使用正则表达式提取标签
const matches = answer.match(/"([^"]+)"/g);
if (matches) {
tags = matches.map(match => match.replace(/"/g, ''));
}
}
// 保存标签到数据库
const savedTags = [];
for (let i = 0; i < tags.length; i++) {
const tagName = tags[i];
try {
const tag = await db.tags.create({
data: {
label: tagName,
projectId,
parentId: parentTagId || null
}
});
savedTags.push(tag);
} catch (error) {
console.error(`[标签生成] 保存标签 ${tagName} 失败:`, String(error));
throw error;
}
}
return NextResponse.json(savedTags);
} catch (error) {
console.error('[标签生成] 生成标签失败:', String(error));
console.error('[标签生成] 错误堆栈:', error.stack);
return NextResponse.json({ error: error.message || '生成标签失败' }, { status: 500 });
}
}
================================================
FILE: app/api/projects/[projectId]/eval-datasets/[evalId]/route.js
================================================
import { NextResponse } from 'next/server';
import { getEvalQuestionById, updateEvalQuestion, deleteEvalQuestion } from '@/lib/db/evalDatasets';
import { db } from '@/lib/db/index';
/**
* Get evaluation dataset details by ID
* Supports operateType=prev|next to navigate neighbors
*/
export async function GET(request, { params }) {
try {
const { projectId, evalId } = params;
const { searchParams } = new URL(request.url);
const operateType = searchParams.get('operateType');
// Navigation request (prev/next)
if (operateType) {
const current = await db.evalDatasets.findUnique({
where: { id: evalId },
select: { createAt: true }
});
if (!current) {
return NextResponse.json(null);
}
let neighbor = null;
if (operateType === 'prev') {
// Get previous item (newer createAt when list is sorted desc)
neighbor = await db.evalDatasets.findFirst({
where: {
projectId,
createAt: { gt: current.createAt }
},
orderBy: { createAt: 'asc' },
select: { id: true }
});
} else if (operateType === 'next') {
// Get next item (older createAt)
neighbor = await db.evalDatasets.findFirst({
where: {
projectId,
createAt: { lt: current.createAt }
},
orderBy: { createAt: 'desc' },
select: { id: true }
});
}
return NextResponse.json(neighbor || null);
}
// Regular detail request
const evalQuestion = await getEvalQuestionById(evalId);
if (!evalQuestion) {
return NextResponse.json({ error: 'Eval question not found' }, { status: 404 });
}
return NextResponse.json(evalQuestion);
} catch (error) {
console.error('Failed to get eval question:', error);
return NextResponse.json({ error: error.message || 'Failed to get eval question' }, { status: 500 });
}
}
/**
* Update evaluation dataset
*/
export async function PUT(request, { params }) {
try {
const { evalId } = params;
const data = await request.json();
// Only allow specific fields
const allowedFields = ['question', 'options', 'correctAnswer', 'tags', 'note'];
const updateData = {};
for (const field of allowedFields) {
if (data[field] !== undefined) {
updateData[field] = data[field];
}
}
const updated = await updateEvalQuestion(evalId, updateData);
return NextResponse.json(updated);
} catch (error) {
console.error('Failed to update eval question:', error);
return NextResponse.json({ error: error.message || 'Failed to update eval question' }, { status: 500 });
}
}
/**
* Delete evaluation dataset
*/
export async function DELETE(request, { params }) {
try {
const { evalId } = params;
await deleteEvalQuestion(evalId);
return NextResponse.json({ success: true });
} catch (error) {
console.error('Failed to delete eval question:', error);
return NextResponse.json({ error: error.message || 'Failed to delete eval question' }, { status: 500 });
}
}
================================================
FILE: app/api/projects/[projectId]/eval-datasets/count/route.js
================================================
import { NextResponse } from 'next/server';
import { db } from '@/lib/db';
import { buildEvalQuestionWhere } from '@/lib/db/evalDatasets';
export async function GET(request, { params }) {
try {
const { projectId } = params;
const { searchParams } = new URL(request.url);
const questionType = searchParams.get('questionType') || '';
const keyword = searchParams.get('keyword') || '';
const chunkId = searchParams.get('chunkId') || '';
const questionTypes = searchParams.getAll('questionTypes') || [];
const tags =
searchParams.getAll('tags').length > 0
? searchParams.getAll('tags')
: searchParams.get('tag')
? searchParams.get('tag').split(',')
: [];
const where = buildEvalQuestionWhere(projectId, {
questionType: questionType || undefined,
questionTypes: questionTypes.length > 0 ? questionTypes : undefined,
keyword: keyword || undefined,
chunkId: chunkId || undefined,
tags: tags.length > 0 ? tags : undefined
});
const [total, byTypeRaw] = await Promise.all([
db.evalDatasets.count({ where }),
db.evalDatasets.groupBy({
by: ['questionType'],
where,
_count: { id: true }
})
]);
const byType = {};
byTypeRaw.forEach(item => {
byType[item.questionType] = item._count.id;
});
const hasShortAnswer = (byType.short_answer || 0) > 0;
const hasOpenEnded = (byType.open_ended || 0) > 0;
const hasSubjective = hasShortAnswer || hasOpenEnded;
return NextResponse.json(
{
code: 0,
data: { total, byType, hasSubjective, hasShortAnswer, hasOpenEnded }
},
{ status: 200 }
);
} catch (error) {
console.error('Failed to count eval datasets:', error);
return NextResponse.json(
{ code: 500, error: 'Failed to count eval datasets', message: error.message },
{ status: 500 }
);
}
}
================================================
FILE: app/api/projects/[projectId]/eval-datasets/export/route.js
================================================
import { NextResponse } from 'next/server';
import { db } from '@/lib/db/index';
import { buildEvalQuestionWhere } from '@/lib/db/evalDatasets';
const BATCH_SIZE = 500;
/**
* Convert an evaluation item to a CSV row
*/
function convertToCSVRow(item, isHeader = false) {
if (isHeader) {
return ['questionType', 'question', 'options', 'correctAnswer', 'tags'].join(',');
}
const escapeCSV = str => {
if (str === null || str === undefined) return '';
const strValue = String(str);
if (strValue.includes(',') || strValue.includes('"') || strValue.includes('\n')) {
return `"${strValue.replace(/"/g, '""')}"`;
}
return strValue;
};
return [
escapeCSV(item.questionType),
escapeCSV(item.question),
escapeCSV(item.options),
escapeCSV(item.correctAnswer),
escapeCSV(item.tags)
].join(',');
}
/**
* Convert an evaluation item to export format
*/
function formatExportItem(item) {
return {
questionType: item.questionType,
question: item.question,
options: item.options,
correctAnswer: item.correctAnswer,
tags: item.tags
};
}
/**
* Export evaluation datasets
* Supports JSON, JSONL, and CSV
* Uses batched streaming for large datasets
*/
export async function POST(request, { params }) {
try {
const { projectId } = params;
const body = await request.json();
const {
format = 'json', // json | jsonl | csv
questionTypes = [],
tags = [],
keyword = ''
} = body;
// Validate format
if (!['json', 'jsonl', 'csv'].includes(format)) {
return NextResponse.json({ code: 400, error: 'Unsupported export format' }, { status: 400 });
}
// Build query conditions
const where = buildEvalQuestionWhere(projectId, {
questionTypes: questionTypes.length > 0 ? questionTypes : undefined,
tags: tags.length > 0 ? tags : undefined,
keyword: keyword || undefined
});
// Fetch total count
const total = await db.evalDatasets.count({ where });
if (total === 0) {
return NextResponse.json({ code: 400, error: 'No data matches the criteria' }, { status: 400 });
}
// Return directly for small datasets
if (total <= 1000) {
const items = await db.evalDatasets.findMany({
where,
orderBy: { createAt: 'desc' }
});
const formattedItems = items.map(formatExportItem);
if (format === 'json') {
return new Response(JSON.stringify(formattedItems, null, 2), {
headers: {
'Content-Type': 'application/json',
'Content-Disposition': `attachment; filename="eval-datasets-${Date.now()}.json"`
}
});
}
if (format === 'jsonl') {
const jsonlContent = formattedItems.map(item => JSON.stringify(item)).join('\n');
return new Response(jsonlContent, {
headers: {
'Content-Type': 'application/x-ndjson',
'Content-Disposition': `attachment; filename="eval-datasets-${Date.now()}.jsonl"`
}
});
}
if (format === 'csv') {
const csvContent = [convertToCSVRow(null, true), ...items.map(item => convertToCSVRow(item))].join('\n');
return new Response('\uFEFF' + csvContent, {
headers: {
'Content-Type': 'text/csv; charset=utf-8',
'Content-Disposition': `attachment; filename="eval-datasets-${Date.now()}.csv"`
}
});
}
}
// Stream export for large datasets
const stream = new ReadableStream({
async start(controller) {
const encoder = new TextEncoder();
let isFirst = true;
// CSV outputs header row first
if (format === 'csv') {
controller.enqueue(encoder.encode('\uFEFF' + convertToCSVRow(null, true) + '\n'));
}
// JSON outputs opening bracket
if (format === 'json') {
controller.enqueue(encoder.encode('[\n'));
}
// Fetch data in batches
const totalBatches = Math.ceil(total / BATCH_SIZE);
for (let batch = 0; batch < totalBatches; batch++) {
const items = await db.evalDatasets.findMany({
where,
orderBy: { createAt: 'desc' },
skip: batch * BATCH_SIZE,
take: BATCH_SIZE
});
for (const item of items) {
const formattedItem = formatExportItem(item);
if (format === 'json') {
const prefix = isFirst ? '' : ',\n';
controller.enqueue(encoder.encode(prefix + JSON.stringify(formattedItem)));
isFirst = false;
} else if (format === 'jsonl') {
controller.enqueue(encoder.encode(JSON.stringify(formattedItem) + '\n'));
} else if (format === 'csv') {
controller.enqueue(encoder.encode(convertToCSVRow(item) + '\n'));
}
}
}
// JSON outputs closing bracket
if (format === 'json') {
controller.enqueue(encoder.encode('\n]'));
}
controller.close();
}
});
const contentTypes = {
json: 'application/json',
jsonl: 'application/x-ndjson',
csv: 'text/csv; charset=utf-8'
};
const extensions = {
json: 'json',
jsonl: 'jsonl',
csv: 'csv'
};
return new Response(stream, {
headers: {
'Content-Type': contentTypes[format],
'Content-Disposition': `attachment; filename="eval-datasets-${Date.now()}.${extensions[format]}"`,
'Transfer-Encoding': 'chunked'
}
});
} catch (error) {
console.error('Failed to export eval datasets:', error);
return NextResponse.json({ code: 500, error: error.message || 'Export failed' }, { status: 500 });
}
}
/**
* Get export preview (count only)
*/
export async function GET(request, { params }) {
try {
const { projectId } = params;
const { searchParams } = new URL(request.url);
// Pars
gitextract_cydaglf7/
├── .dockerignore
├── .gitattributes
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ ├── feature-or-enhancement-.md
│ │ └── question.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ └── workflows/
│ └── docker-build.yml
├── .gitignore
├── .husky/
│ ├── commit-msg
│ └── pre-commit
├── .npmrc
├── .prettierrc.js
├── .windsurfrules
├── AGENTS.md
├── ARCHITECTURE.md
├── Dockerfile
├── LICENSE
├── README.md
├── README.tr.md
├── README.zh-CN.md
├── app/
│ ├── api/
│ │ ├── check-update/
│ │ │ └── route.js
│ │ ├── llm/
│ │ │ ├── fetch-models/
│ │ │ │ └── route.js
│ │ │ ├── model/
│ │ │ │ └── route.js
│ │ │ ├── ollama/
│ │ │ │ └── models/
│ │ │ │ └── route.js
│ │ │ └── providers/
│ │ │ └── route.js
│ │ ├── monitoring/
│ │ │ ├── logs/
│ │ │ │ └── route.js
│ │ │ ├── stats/
│ │ │ │ └── route.js
│ │ │ └── summary/
│ │ │ └── route.js
│ │ ├── projects/
│ │ │ ├── [projectId]/
│ │ │ │ ├── batch-add-manual-ga/
│ │ │ │ │ └── route.js
│ │ │ │ ├── batch-delete-files/
│ │ │ │ │ └── route.js
│ │ │ │ ├── batch-generateGA/
│ │ │ │ │ └── route.js
│ │ │ │ ├── blind-test-tasks/
│ │ │ │ │ ├── [taskId]/
│ │ │ │ │ │ ├── current/
│ │ │ │ │ │ │ └── route.js
│ │ │ │ │ │ ├── question/
│ │ │ │ │ │ │ └── route.js
│ │ │ │ │ │ ├── route.js
│ │ │ │ │ │ ├── stream/
│ │ │ │ │ │ │ └── route.js
│ │ │ │ │ │ ├── stream-model/
│ │ │ │ │ │ │ └── route.js
│ │ │ │ │ │ └── vote/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ └── route.js
│ │ │ │ ├── chunks/
│ │ │ │ │ ├── [chunkId]/
│ │ │ │ │ │ ├── clean/
│ │ │ │ │ │ │ └── route.js
│ │ │ │ │ │ ├── eval-questions/
│ │ │ │ │ │ │ └── route.js
│ │ │ │ │ │ ├── questions/
│ │ │ │ │ │ │ └── route.js
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── batch-content/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── batch-edit/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── name/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ └── route.js
│ │ │ │ ├── config/
│ │ │ │ │ └── route.js
│ │ │ │ ├── custom-prompts/
│ │ │ │ │ └── route.js
│ │ │ │ ├── custom-split/
│ │ │ │ │ └── route.js
│ │ │ │ ├── dataset-conversations/
│ │ │ │ │ ├── [conversationId]/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── export/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── route.js
│ │ │ │ │ └── tags/
│ │ │ │ │ └── route.js
│ │ │ │ ├── datasets/
│ │ │ │ │ ├── [datasetId]/
│ │ │ │ │ │ ├── copy-to-eval/
│ │ │ │ │ │ │ └── route.js
│ │ │ │ │ │ ├── evaluate/
│ │ │ │ │ │ │ └── route.js
│ │ │ │ │ │ ├── route.js
│ │ │ │ │ │ └── token-count/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── batch-evaluate/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── export/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── generate-eval-variant/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── import/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── optimize/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── route.js
│ │ │ │ │ └── tags/
│ │ │ │ │ └── route.js
│ │ │ │ ├── default-prompts/
│ │ │ │ │ └── route.js
│ │ │ │ ├── distill/
│ │ │ │ │ ├── questions/
│ │ │ │ │ │ ├── by-tag/
│ │ │ │ │ │ │ └── route.js
│ │ │ │ │ │ └── route.js
│ │ │ │ │ └── tags/
│ │ │ │ │ ├── [tagId]/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── all/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ └── route.js
│ │ │ │ ├── eval-datasets/
│ │ │ │ │ ├── [evalId]/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── count/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── export/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── import/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── route.js
│ │ │ │ │ ├── sample/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ └── tags/
│ │ │ │ │ └── route.js
│ │ │ │ ├── eval-tasks/
│ │ │ │ │ ├── [taskId]/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ └── route.js
│ │ │ │ ├── files/
│ │ │ │ │ ├── [fileId]/
│ │ │ │ │ │ └── ga-pairs/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ └── route.js
│ │ │ │ ├── generate-questions/
│ │ │ │ │ └── route.js
│ │ │ │ ├── huggingface/
│ │ │ │ │ └── upload/
│ │ │ │ │ └── route.js
│ │ │ │ ├── image-datasets/
│ │ │ │ │ ├── [datasetId]/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── export/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── export-zip/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── route.js
│ │ │ │ │ └── tags/
│ │ │ │ │ └── route.js
│ │ │ │ ├── images/
│ │ │ │ │ ├── [imageId]/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── annotations/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── datasets/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── next-unanswered/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── pdf-convert/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── questions/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── route.js
│ │ │ │ │ └── zip-import/
│ │ │ │ │ └── route.js
│ │ │ │ ├── llamaFactory/
│ │ │ │ │ ├── checkConfig/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ └── generate/
│ │ │ │ │ └── route.js
│ │ │ │ ├── model-config/
│ │ │ │ │ ├── [modelConfigId]/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ └── route.js
│ │ │ │ ├── models/
│ │ │ │ │ ├── [modelId]/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ └── route.js
│ │ │ │ ├── playground/
│ │ │ │ │ └── chat/
│ │ │ │ │ ├── route.js
│ │ │ │ │ └── stream/
│ │ │ │ │ └── route.js
│ │ │ │ ├── preview/
│ │ │ │ │ └── [fileId]/
│ │ │ │ │ └── route.js
│ │ │ │ ├── questions/
│ │ │ │ │ ├── [questionId]/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── batch-delete/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── export/
│ │ │ │ │ │ └── route.js
│ │ │ │ │ ├── route.js
│ │ │ │ │ ├── templates/
│ │ │ │ │ │ ├── [templateId]/
│ │ │ │ │ │ │ └── route.js
│ │ │ │ │ │ └── route.js
│ │ │ │ │ └── tree/
│ │ │ │ │ └── route.js
│ │ │ │ ├── route.js
│ │ │ │ ├── split/
│ │ │ │ │ └── route.js
│ │ │ │ ├── tags/
│ │ │ │ │ └── route.js
│ │ │ │ └── tasks/
│ │ │ │ ├── [taskId]/
│ │ │ │ │ └── route.js
│ │ │ │ ├── list/
│ │ │ │ │ └── route.js
│ │ │ │ └── route.js
│ │ │ ├── delete-directory/
│ │ │ │ └── route.js
│ │ │ ├── migrate/
│ │ │ │ └── route.js
│ │ │ ├── open-directory/
│ │ │ │ └── route.js
│ │ │ ├── route.js
│ │ │ └── unmigrated/
│ │ │ └── route.js
│ │ └── update/
│ │ └── route.js
│ ├── dataset-square/
│ │ └── page.js
│ ├── globals.css
│ ├── layout.js
│ ├── monitoring/
│ │ ├── components/
│ │ │ ├── Charts.js
│ │ │ ├── StatsCards.js
│ │ │ └── UsageTable.js
│ │ ├── hooks/
│ │ │ └── useMonitoringData.js
│ │ └── page.js
│ ├── page.js
│ └── projects/
│ └── [projectId]/
│ ├── blind-test-tasks/
│ │ ├── [taskId]/
│ │ │ └── page.js
│ │ ├── components/
│ │ │ ├── BlindTestHeader.js
│ │ │ ├── BlindTestInProgress.js
│ │ │ ├── BlindTestTaskCard.js
│ │ │ ├── CreateBlindTestDialog.js
│ │ │ ├── ResultDetailList.js
│ │ │ └── ResultSummary.js
│ │ ├── hooks/
│ │ │ ├── useBlindTestDetail.js
│ │ │ └── useBlindTestTasks.js
│ │ └── page.js
│ ├── datasets/
│ │ ├── [datasetId]/
│ │ │ ├── page.js
│ │ │ └── useDatasetDetails.js
│ │ ├── components/
│ │ │ ├── ActionBar.js
│ │ │ ├── DatasetList.js
│ │ │ ├── DeleteConfirmDialog.js
│ │ │ ├── FilterDialog.js
│ │ │ └── SearchBar.js
│ │ ├── hooks/
│ │ │ ├── useDatasetEvaluation.js
│ │ │ ├── useDatasetExport.js
│ │ │ └── useDatasetFilters.js
│ │ └── page.js
│ ├── distill/
│ │ ├── autoDistillService.js
│ │ └── page.js
│ ├── eval-datasets/
│ │ ├── [evalId]/
│ │ │ ├── page.js
│ │ │ └── useEvalDatasetDetails.js
│ │ ├── components/
│ │ │ ├── BuiltinDatasetDialog.js
│ │ │ ├── EvalDatasetCard.js
│ │ │ ├── EvalDatasetHeader.js
│ │ │ ├── EvalDatasetList.js
│ │ │ ├── EvalEditableField.js
│ │ │ ├── EvalToolbar.js
│ │ │ ├── EvalToolbar.styles.js
│ │ │ ├── ExportEvalDialog.js
│ │ │ ├── ImportDialog.js
│ │ │ └── ImportDialog.styles.js
│ │ ├── constants.js
│ │ ├── hooks/
│ │ │ ├── useEvalDatasets.js
│ │ │ └── useExportEvalDatasets.js
│ │ └── page.js
│ ├── eval-tasks/
│ │ ├── [taskId]/
│ │ │ ├── components/
│ │ │ │ ├── EvalHeader.js
│ │ │ │ ├── EvalStats.js
│ │ │ │ └── QuestionCard.js
│ │ │ ├── detailStyles.js
│ │ │ └── page.js
│ │ ├── components/
│ │ │ ├── CreateEvalTaskDialog.js
│ │ │ ├── EvalTaskCard.js
│ │ │ ├── ModelSelector.js
│ │ │ ├── QuestionFilter.js
│ │ │ └── ScoreAnchorsForm.js
│ │ ├── hooks/
│ │ │ ├── useEvalTaskDetail.js
│ │ │ ├── useEvalTaskForm.js
│ │ │ └── useEvalTasks.js
│ │ ├── page.js
│ │ └── styles.js
│ ├── image-datasets/
│ │ ├── [datasetId]/
│ │ │ └── page.js
│ │ ├── components/
│ │ │ ├── DatasetContent.js
│ │ │ ├── DatasetSidebar.js
│ │ │ ├── EmptyState.js
│ │ │ ├── ExportImageDatasetDialog.js
│ │ │ ├── ImageDatasetCard.js
│ │ │ ├── ImageDatasetFilterDialog.js
│ │ │ ├── ImageDatasetFilters.js
│ │ │ ├── ImageDatasetHeader.js
│ │ │ ├── MetadataEditor.js
│ │ │ └── MetadataInfo.js
│ │ ├── hooks/
│ │ │ ├── useImageDatasetDetail.js
│ │ │ ├── useImageDatasetDetails.js
│ │ │ ├── useImageDatasetExport.js
│ │ │ ├── useImageDatasetFilters.js
│ │ │ └── useImageDatasets.js
│ │ ├── page.js
│ │ └── styles/
│ │ └── imageDatasetStyles.js
│ ├── images/
│ │ ├── components/
│ │ │ ├── DatasetDialog.js
│ │ │ ├── ImageFilters.js
│ │ │ ├── ImageGrid.js
│ │ │ ├── ImageList.js
│ │ │ ├── ImportDialog.js
│ │ │ ├── QuestionDialog.js
│ │ │ └── annotation/
│ │ │ ├── AIGenerateButton.js
│ │ │ ├── AnnotationDialog.js
│ │ │ ├── AnswerInput.js
│ │ │ └── QuestionSelector.js
│ │ ├── hooks/
│ │ │ └── useAnnotation.js
│ │ ├── page.js
│ │ └── styles/
│ │ └── imageStyles.js
│ ├── layout.js
│ ├── multi-turn/
│ │ ├── [conversationId]/
│ │ │ ├── page.js
│ │ │ └── useConversationDetails.js
│ │ ├── components/
│ │ │ ├── ConversationTable.js
│ │ │ ├── FilterDialog.js
│ │ │ ├── RatingChip.js
│ │ │ └── SearchBar.js
│ │ ├── hooks/
│ │ │ └── useMultiTurnData.js
│ │ └── page.js
│ ├── page.js
│ ├── playground/
│ │ └── page.js
│ ├── questions/
│ │ ├── components/
│ │ │ ├── ConfirmDialog.js
│ │ │ ├── ExportQuestionsDialog.js
│ │ │ ├── QuestionEditDialog.js
│ │ │ ├── QuestionsFilter.js
│ │ │ ├── QuestionsPageHeader.js
│ │ │ ├── TemplateListView.js
│ │ │ └── template/
│ │ │ ├── TemplateFormDialog.js
│ │ │ └── TemplateManagementDialog.js
│ │ ├── hooks/
│ │ │ ├── useQuestionDelete.js
│ │ │ ├── useQuestionEdit.js
│ │ │ ├── useQuestionExport.js
│ │ │ ├── useQuestionGeneration.js
│ │ │ ├── useQuestionTemplates.js
│ │ │ └── useQuestionsFilter.js
│ │ └── page.js
│ ├── settings/
│ │ ├── components/
│ │ │ ├── CategoryTabs.js
│ │ │ ├── PromptDetail.js
│ │ │ ├── PromptEditDialog.js
│ │ │ ├── PromptList.js
│ │ │ ├── PromptSettings.js
│ │ │ └── promptUtils.js
│ │ └── page.js
│ ├── tasks/
│ │ └── page.js
│ └── text-split/
│ ├── page.js
│ ├── useChunks.js
│ ├── useDataCleaning.js
│ ├── useEvalGeneration.js
│ ├── useFileProcessing.js
│ └── useQuestionGeneration.js
├── commitlint.config.mjs
├── components/
│ ├── ExportDatasetDialog.js
│ ├── ExportProgressDialog.js
│ ├── I18nProvider.js
│ ├── LanguageSwitcher.js
│ ├── ModelSelect.js
│ ├── Navbar/
│ │ ├── ActionButtons.js
│ │ ├── ContextBar.js
│ │ ├── DesktopMenus.js
│ │ ├── Logo.js
│ │ ├── MobileDrawer.js
│ │ ├── NavigationTabs.js
│ │ ├── contextBarStyles.js
│ │ ├── index.js
│ │ └── styles.js
│ ├── TaskIcon.js
│ ├── ThemeRegistry.js
│ ├── UpdateChecker.js
│ ├── common/
│ │ └── MessageAlert.js
│ ├── conversations/
│ │ ├── ConversationContent.js
│ │ ├── ConversationHeader.js
│ │ ├── ConversationMetadata.js
│ │ └── ConversationRatingSection.js
│ ├── dataset-square/
│ │ ├── DatasetSearchBar.js
│ │ ├── DatasetSiteCard.js
│ │ └── DatasetSiteList.js
│ ├── datasets/
│ │ ├── DatasetHeader.js
│ │ ├── DatasetMetadata.js
│ │ ├── DatasetRatingSection.js
│ │ ├── EditableField.js
│ │ ├── EvalVariantDialog.js
│ │ ├── ImportDatasetDialog.js
│ │ ├── NoteInput.js
│ │ ├── OptimizeDialog.js
│ │ ├── StarRating.js
│ │ ├── TagSelector.js
│ │ ├── import/
│ │ │ ├── FieldMappingStep.js
│ │ │ ├── FileUploadStep.js
│ │ │ └── ImportProgressStep.js
│ │ └── utils/
│ │ └── ratingUtils.js
│ ├── distill/
│ │ ├── AutoDistillDialog.js
│ │ ├── AutoDistillProgress.js
│ │ ├── ConfirmDialog.js
│ │ ├── DistillTreeView.js
│ │ ├── QuestionGenerationDialog.js
│ │ ├── QuestionListItem.js
│ │ ├── TagEditDialog.js
│ │ ├── TagGenerationDialog.js
│ │ ├── TagMenu.js
│ │ ├── TagTreeItem.js
│ │ └── utils.js
│ ├── export/
│ │ ├── HuggingFaceTab.js
│ │ ├── LlamaFactoryTab.js
│ │ └── LocalExportTab.js
│ ├── home/
│ │ ├── CreateProjectDialog.js
│ │ ├── HeroSection.js
│ │ ├── MigrationDialog.js
│ │ ├── ParticleBackground.js
│ │ ├── ProjectCard.js
│ │ ├── ProjectList.js
│ │ └── StatsCard.js
│ ├── mga/
│ │ ├── GaPairsIndicator.js
│ │ └── GaPairsManager.js
│ ├── playground/
│ │ ├── ChatArea.js
│ │ ├── ChatMessage.js
│ │ ├── MessageInput.js
│ │ ├── ModelSelector.js
│ │ └── PlaygroundHeader.js
│ ├── questions/
│ │ ├── QuestionListView.js
│ │ └── QuestionTreeView.js
│ ├── settings/
│ │ ├── BasicSettings.js
│ │ ├── ModelSettings.js
│ │ └── TaskSettings.js
│ ├── tasks/
│ │ ├── TaskActions.js
│ │ ├── TaskFilters.js
│ │ ├── TaskProgress.js
│ │ ├── TaskStatusChip.js
│ │ └── TasksTable.js
│ └── text-split/
│ ├── BatchEditChunkDialog.js
│ ├── ChunkBatchDeleteDialog.js
│ ├── ChunkCard.js
│ ├── ChunkDeleteDialog.js
│ ├── ChunkFilterDialog.js
│ ├── ChunkList.js
│ ├── ChunkListHeader.js
│ ├── ChunkViewDialog.js
│ ├── DomainAnalysis.js
│ ├── FileUploader.js
│ ├── LoadingBackdrop.js
│ ├── MarkdownViewDialog.js
│ ├── PdfSettings.js
│ └── components/
│ ├── DeleteConfirmDialog.js
│ ├── DirectoryView.js
│ ├── DomainTreeActionDialog.js
│ ├── DomainTreeView.js
│ ├── FileList.js
│ ├── FileLoadingProgress.js
│ ├── PdfProcessingDialog.js
│ ├── TabPanel.js
│ └── UploadArea.js
├── constant/
│ ├── index.js
│ ├── model.js
│ ├── setting.js
│ └── sites.json
├── docker-compose.yml
├── docker-entrypoint.sh
├── electron/
│ ├── entitlements.mac.plist
│ ├── loading.html
│ ├── main.js
│ ├── modules/
│ │ ├── cache.js
│ │ ├── database.js
│ │ ├── db-updater.js
│ │ ├── ipc-handlers.js
│ │ ├── logger.js
│ │ ├── menu.js
│ │ ├── server.js
│ │ ├── updater.js
│ │ └── window-manager.js
│ ├── preload.js
│ └── util.js
├── hooks/
│ ├── useDebounce.js
│ ├── useFileProcessingStatus.js
│ ├── useGenerateDataset.js
│ ├── useModelPlayground.js
│ ├── useSnackbar.js
│ └── useTaskSettings.js
├── jsconfig.json
├── lib/
│ ├── api/
│ │ ├── chunk.js
│ │ ├── file.js
│ │ ├── index.js
│ │ └── task.js
│ ├── db/
│ │ ├── base.js
│ │ ├── chunks.js
│ │ ├── custom-prompts.js
│ │ ├── dataset-conversations.js
│ │ ├── datasets.js
│ │ ├── evalDatasets.js
│ │ ├── evalResults.js
│ │ ├── fileToDb.js
│ │ ├── files.js
│ │ ├── ga-pairs.js
│ │ ├── imageDatasets.js
│ │ ├── images.js
│ │ ├── index.js
│ │ ├── llm-models.js
│ │ ├── llm-providers.js
│ │ ├── model-config.js
│ │ ├── projects.js
│ │ ├── questionTemplates.js
│ │ ├── questions.js
│ │ ├── tags.js
│ │ ├── texts.js
│ │ └── upload-files.js
│ ├── file/
│ │ ├── file-process/
│ │ │ ├── check-file.js
│ │ │ ├── epub/
│ │ │ │ └── index.js
│ │ │ ├── get-content.js
│ │ │ ├── index.js
│ │ │ ├── pdf/
│ │ │ │ ├── default.js
│ │ │ │ ├── index.js
│ │ │ │ ├── mineru-local.js
│ │ │ │ ├── mineru.js
│ │ │ │ ├── prompt/
│ │ │ │ │ ├── optimalTitle.js
│ │ │ │ │ ├── optimalTitleEn.js
│ │ │ │ │ ├── pdfToMarkdown.js
│ │ │ │ │ └── pdfToMarkdownEn.js
│ │ │ │ ├── util.js
│ │ │ │ └── vision.js
│ │ │ └── utils.js
│ │ ├── split-markdown/
│ │ │ ├── core/
│ │ │ │ ├── parser.js
│ │ │ │ ├── splitter.js
│ │ │ │ ├── summary.js
│ │ │ │ └── toc.js
│ │ │ ├── index.js
│ │ │ ├── output/
│ │ │ │ ├── fileWriter.js
│ │ │ │ └── formatter.js
│ │ │ └── utils/
│ │ │ └── common.js
│ │ └── text-splitter.js
│ ├── i18n.js
│ ├── llm/
│ │ ├── common/
│ │ │ ├── prompt-loader.js
│ │ │ ├── question-template.js
│ │ │ └── util.js
│ │ ├── core/
│ │ │ ├── index.js
│ │ │ └── providers/
│ │ │ ├── alibailian.js
│ │ │ ├── base.js
│ │ │ ├── ollama.js
│ │ │ ├── openai.js
│ │ │ ├── openrouter.js
│ │ │ └── zhipu.js
│ │ ├── prompts/
│ │ │ ├── addLabel.js
│ │ │ ├── answer.js
│ │ │ ├── dataClean.js
│ │ │ ├── datasetEvaluation.js
│ │ │ ├── distillQuestions.js
│ │ │ ├── distillTags.js
│ │ │ ├── enhancedAnswer.js
│ │ │ ├── evalQuestion.js
│ │ │ ├── ga-generation.js
│ │ │ ├── imageAnswer.js
│ │ │ ├── imageQuestion.js
│ │ │ ├── label.js
│ │ │ ├── labelRevise.js
│ │ │ ├── llmJudge.js
│ │ │ ├── modelEvaluation.js
│ │ │ ├── multiTurnConversation.js
│ │ │ ├── newAnswer.js
│ │ │ ├── optimizeCot.js
│ │ │ └── question.js
│ │ └── usageLogger.js
│ ├── services/
│ │ ├── clean.js
│ │ ├── datasets/
│ │ │ ├── evaluation.js
│ │ │ └── index.js
│ │ ├── eval/
│ │ │ └── index.js
│ │ ├── evaluation/
│ │ │ └── index.js
│ │ ├── ga/
│ │ │ ├── ga-generation.js
│ │ │ └── ga-pairs.js
│ │ ├── images/
│ │ │ └── index.js
│ │ ├── models.js
│ │ ├── multi-turn/
│ │ │ └── index.js
│ │ ├── questions/
│ │ │ ├── index.js
│ │ │ └── template.js
│ │ └── tasks/
│ │ ├── answer-generation.js
│ │ ├── data-cleaning.js
│ │ ├── data-distillation.js
│ │ ├── dataset-evaluation.js
│ │ ├── eval-generation.js
│ │ ├── file-processing.js
│ │ ├── image-dataset-generation.js
│ │ ├── image-question-generation.js
│ │ ├── index.js
│ │ ├── model-evaluation.js
│ │ ├── multi-turn-generation.js
│ │ ├── question-generation.js
│ │ └── recovery.js
│ ├── store.js
│ └── util/
│ ├── async.js
│ ├── domain-tree.js
│ ├── file.js
│ ├── image.js
│ ├── logger.js
│ ├── modelIcon.js
│ ├── processInParallel.js
│ ├── providerLogo.js
│ └── request.js
├── locales/
│ ├── en/
│ │ └── translation.json
│ ├── pt-BR/
│ │ └── translation.json
│ ├── tr/
│ │ └── translation.json
│ └── zh-CN/
│ └── translation.json
├── next.config.js
├── package.json
├── prisma/
│ ├── generate-template.js
│ ├── schema.prisma
│ └── sql.json
├── public/
│ └── imgs/
│ ├── logo.icns
│ └── logo_old.icns
└── styles/
├── blindTest.js
├── globals.css
├── home.js
└── playground.js
SYMBOL INDEX (937 symbols across 420 files)
FILE: app/api/check-update/route.js
function getCurrentVersion (line 6) | function getCurrentVersion() {
function getLatestVersion (line 18) | async function getLatestVersion() {
function GET (line 37) | async function GET() {
function compareVersions (line 73) | function compareVersions(a, b) {
FILE: app/api/llm/fetch-models/route.js
function POST (line 5) | async function POST(request) {
FILE: app/api/llm/model/route.js
function GET (line 5) | async function GET(request) {
function POST (line 24) | async function POST(request) {
FILE: app/api/llm/ollama/models/route.js
function GET (line 8) | async function GET(request) {
FILE: app/api/llm/providers/route.js
function GET (line 6) | async function GET() {
FILE: app/api/monitoring/logs/route.js
function GET (line 6) | async function GET(request) {
FILE: app/api/monitoring/stats/route.js
function GET (line 6) | async function GET(request) {
FILE: app/api/monitoring/summary/route.js
function GET (line 6) | async function GET(request) {
FILE: app/api/projects/[projectId]/batch-add-manual-ga/route.js
function POST (line 8) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/batch-delete-files/route.js
function POST (line 15) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/batch-generateGA/route.js
function POST (line 8) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/blind-test-tasks/[taskId]/current/route.js
function GET (line 9) | async function GET(request, { params }) {
FILE: app/api/projects/[projectId]/blind-test-tasks/[taskId]/question/route.js
function GET (line 7) | async function GET(request, { params }) {
FILE: app/api/projects/[projectId]/blind-test-tasks/[taskId]/route.js
function GET (line 8) | async function GET(request, { params }) {
function PUT (line 103) | async function PUT(request, { params }) {
function DELETE (line 153) | async function DELETE(request, { params }) {
FILE: app/api/projects/[projectId]/blind-test-tasks/[taskId]/stream-model/route.js
function GET (line 10) | async function GET(request, { params }) {
FILE: app/api/projects/[projectId]/blind-test-tasks/[taskId]/stream/route.js
function GET (line 9) | async function GET(request, { params }) {
FILE: app/api/projects/[projectId]/blind-test-tasks/[taskId]/vote/route.js
function POST (line 9) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/blind-test-tasks/route.js
function GET (line 7) | async function GET(request, { params }) {
function POST (line 116) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/chunks/[chunkId]/clean/route.js
function POST (line 6) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/chunks/[chunkId]/eval-questions/route.js
function POST (line 8) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/chunks/[chunkId]/questions/route.js
function POST (line 7) | async function POST(request, { params }) {
function GET (line 51) | async function GET(request, { params }) {
FILE: app/api/projects/[projectId]/chunks/[chunkId]/route.js
function GET (line 5) | async function GET(request, { params }) {
function DELETE (line 26) | async function DELETE(request, { params }) {
function PATCH (line 46) | async function PATCH(request, { params }) {
FILE: app/api/projects/[projectId]/chunks/batch-content/route.js
function POST (line 4) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/chunks/batch-edit/route.js
function POST (line 10) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/chunks/name/route.js
function GET (line 10) | async function GET(request, { params }) {
FILE: app/api/projects/[projectId]/chunks/route.js
function POST (line 5) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/config/route.js
function GET (line 5) | async function GET(request, { params }) {
function PUT (line 18) | async function PUT(request, { params }) {
FILE: app/api/projects/[projectId]/custom-prompts/route.js
function GET (line 13) | async function GET(request, { params }) {
function POST (line 39) | async function POST(request, { params }) {
function DELETE (line 80) | async function DELETE(request, { params }) {
FILE: app/api/projects/[projectId]/custom-split/route.js
function POST (line 13) | async function POST(request, { params }) {
function generateCustomChunks (line 63) | function generateCustomChunks(projectId, fileId, fileName, content, spli...
FILE: app/api/projects/[projectId]/dataset-conversations/[conversationId]/route.js
function GET (line 16) | async function GET(request, { params }) {
function PUT (line 66) | async function PUT(request, { params }) {
function DELETE (line 140) | async function DELETE(request, { params }) {
FILE: app/api/projects/[projectId]/dataset-conversations/export/route.js
function GET (line 12) | async function GET(request, { params }) {
FILE: app/api/projects/[projectId]/dataset-conversations/route.js
function GET (line 16) | async function GET(request, { params }) {
function POST (line 70) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/dataset-conversations/tags/route.js
function GET (line 7) | async function GET(request, { params }) {
FILE: app/api/projects/[projectId]/datasets/[datasetId]/copy-to-eval/route.js
function POST (line 4) | async function POST(req, { params }) {
FILE: app/api/projects/[projectId]/datasets/[datasetId]/evaluate/route.js
function POST (line 7) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/datasets/[datasetId]/route.js
function GET (line 7) | async function GET(request, { params }) {
function PATCH (line 41) | async function PATCH(request, { params }) {
FILE: app/api/projects/[projectId]/datasets/[datasetId]/token-count/route.js
function GET (line 8) | async function GET(request, { params }) {
FILE: app/api/projects/[projectId]/datasets/batch-evaluate/route.js
function POST (line 13) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/datasets/export/route.js
function GET (line 15) | async function GET(request, { params }) {
function POST (line 45) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/datasets/generate-eval-variant/route.js
function POST (line 7) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/datasets/import/route.js
function POST (line 5) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/datasets/optimize/route.js
function POST (line 10) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/datasets/route.js
function POST (line 16) | async function POST(request, { params }) {
function GET (line 42) | async function GET(request, { params }) {
function DELETE (line 114) | async function DELETE(request) {
function PATCH (line 147) | async function PATCH(request) {
FILE: app/api/projects/[projectId]/datasets/tags/route.js
function GET (line 7) | async function GET(request, { params }) {
FILE: app/api/projects/[projectId]/default-prompts/route.js
function GET (line 4) | async function GET(request, { params }) {
FILE: app/api/projects/[projectId]/distill/questions/by-tag/route.js
function GET (line 7) | async function GET(request, { params }) {
FILE: app/api/projects/[projectId]/distill/questions/route.js
function POST (line 10) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/distill/tags/[tagId]/route.js
function PUT (line 7) | async function PUT(request, { params }) {
FILE: app/api/projects/[projectId]/distill/tags/all/route.js
function GET (line 7) | async function GET(request, { params }) {
FILE: app/api/projects/[projectId]/distill/tags/route.js
function POST (line 11) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/eval-datasets/[evalId]/route.js
function GET (line 9) | async function GET(request, { params }) {
function PUT (line 70) | async function PUT(request, { params }) {
function DELETE (line 97) | async function DELETE(request, { params }) {
FILE: app/api/projects/[projectId]/eval-datasets/count/route.js
function GET (line 5) | async function GET(request, { params }) {
FILE: app/api/projects/[projectId]/eval-datasets/export/route.js
constant BATCH_SIZE (line 5) | const BATCH_SIZE = 500;
function convertToCSVRow (line 10) | function convertToCSVRow(item, isHeader = false) {
function formatExportItem (line 36) | function formatExportItem(item) {
function POST (line 51) | async function POST(request, { params }) {
function GET (line 200) | async function GET(request, { params }) {
FILE: app/api/projects/[projectId]/eval-datasets/import/route.js
function validateTrueFalse (line 9) | function validateTrueFalse(item, index) {
function validateSingleChoice (line 23) | function validateSingleChoice(item, index) {
function validateMultipleChoice (line 52) | function validateMultipleChoice(item, index) {
function validateQA (line 101) | function validateQA(item, index) {
function validateData (line 115) | function validateData(data, questionType) {
function parseExcel (line 146) | function parseExcel(buffer, questionType) {
function parseJSON (line 227) | function parseJSON(content) {
function POST (line 234) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/eval-datasets/route.js
function GET (line 7) | async function GET(request, { params }) {
function DELETE (line 59) | async function DELETE(request, { params }) {
function POST (line 86) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/eval-datasets/sample/route.js
constant SMALL_TOTAL_THRESHOLD (line 5) | const SMALL_TOTAL_THRESHOLD = 5000;
constant HARD_LIMIT (line 6) | const HARD_LIMIT = 50000;
function shuffleArray (line 8) | function shuffleArray(arr) {
function POST (line 17) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/eval-datasets/tags/route.js
function GET (line 7) | async function GET(request, { params }) {
FILE: app/api/projects/[projectId]/eval-tasks/[taskId]/route.js
function GET (line 8) | async function GET(request, { params }) {
function DELETE (line 85) | async function DELETE(request, { params }) {
function PUT (line 132) | async function PUT(request, { params }) {
FILE: app/api/projects/[projectId]/eval-tasks/route.js
function GET (line 8) | async function GET(request, { params }) {
function POST (line 80) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/files/[fileId]/ga-pairs/route.js
function POST (line 11) | async function POST(request, { params }) {
function GET (line 179) | async function GET(request, { params }) {
function PUT (line 202) | async function PUT(request, { params }) {
function PATCH (line 271) | async function PATCH(request, { params }) {
function getFileContent (line 299) | async function getFileContent(projectId, fileName) {
FILE: app/api/projects/[projectId]/files/route.js
function GET (line 23) | async function GET(request, { params }) {
function DELETE (line 54) | async function DELETE(request, { params }) {
function POST (line 154) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/generate-questions/route.js
function POST (line 8) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/huggingface/upload/route.js
function POST (line 10) | async function POST(request, { params }) {
function formatDataset (line 128) | function formatDataset(questions, formatType, systemPrompt, includeCOT, ...
function convertToCSV (line 238) | function convertToCSV(data) {
function uploadFile (line 265) | async function uploadFile(token, datasetName, filePath, destFileName) {
function generateReadme (line 295) | function generateReadme(projectName, projectDescription, formatType) {
FILE: app/api/projects/[projectId]/image-datasets/[datasetId]/route.js
function GET (line 8) | async function GET(request, { params }) {
function PUT (line 48) | async function PUT(request, { params }) {
function DELETE (line 92) | async function DELETE(request, { params }) {
FILE: app/api/projects/[projectId]/image-datasets/export-zip/route.js
function GET (line 11) | async function GET(request, { params }) {
FILE: app/api/projects/[projectId]/image-datasets/export/route.js
function POST (line 7) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/image-datasets/route.js
function GET (line 8) | async function GET(request, { params }) {
FILE: app/api/projects/[projectId]/image-datasets/tags/route.js
function GET (line 5) | async function GET(request, { params }) {
FILE: app/api/projects/[projectId]/images/[imageId]/route.js
function GET (line 5) | async function GET(request, { params }) {
FILE: app/api/projects/[projectId]/images/annotations/route.js
function POST (line 9) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/images/datasets/route.js
function POST (line 6) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/images/next-unanswered/route.js
function GET (line 8) | async function GET(request, { params }) {
FILE: app/api/projects/[projectId]/images/pdf-convert/route.js
function POST (line 9) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/images/questions/route.js
function POST (line 6) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/images/route.js
function GET (line 10) | async function GET(request, { params }) {
function POST (line 32) | async function POST(request, { params }) {
function DELETE (line 48) | async function DELETE(request, { params }) {
FILE: app/api/projects/[projectId]/images/zip-import/route.js
function POST (line 9) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/llamaFactory/checkConfig/route.js
function GET (line 6) | async function GET(request, { params }) {
FILE: app/api/projects/[projectId]/llamaFactory/generate/route.js
function POST (line 7) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/model-config/[modelConfigId]/route.js
function DELETE (line 5) | async function DELETE(request, { params }) {
FILE: app/api/projects/[projectId]/model-config/route.js
function normalizeModelEndpoint (line 7) | function normalizeModelEndpoint(endpoint = '') {
function GET (line 19) | async function GET(request, { params }) {
function POST (line 60) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/models/[modelId]/route.js
function GET (line 6) | async function GET(request, { params }) {
function PUT (line 54) | async function PUT(request, { params }) {
function DELETE (line 122) | async function DELETE(request, { params }) {
FILE: app/api/projects/[projectId]/models/route.js
function GET (line 7) | async function GET(request, { params }) {
function PUT (line 50) | async function PUT(request, { params }) {
FILE: app/api/projects/[projectId]/playground/chat/route.js
function resolveLatestModelConfig (line 5) | async function resolveLatestModelConfig(projectId, incomingModel = {}) {
function POST (line 31) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/playground/chat/stream/route.js
function resolveLatestModelConfig (line 5) | async function resolveLatestModelConfig(projectId, incomingModel = {}) {
function POST (line 33) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/preview/[fileId]/route.js
function GET (line 8) | async function GET(request, { params }) {
FILE: app/api/projects/[projectId]/questions/[questionId]/route.js
function DELETE (line 5) | async function DELETE(request, { params }) {
FILE: app/api/projects/[projectId]/questions/batch-delete/route.js
function DELETE (line 5) | async function DELETE(request) {
FILE: app/api/projects/[projectId]/questions/export/route.js
function POST (line 3) | async function POST(request, { params }) {
function getAllQuestions (line 39) | async function getAllQuestions(projectId, searchTerm = '', chunkName = '...
function getQuestionsByIds (line 81) | async function getQuestionsByIds(projectId, questionIds) {
FILE: app/api/projects/[projectId]/questions/route.js
function GET (line 12) | async function GET(request, { params }) {
function POST (line 64) | async function POST(request, { params }) {
function PUT (line 95) | async function PUT(request) {
FILE: app/api/projects/[projectId]/questions/templates/[templateId]/route.js
function GET (line 6) | async function GET(request, { params }) {
function PUT (line 33) | async function PUT(request, { params }) {
function DELETE (line 90) | async function DELETE(request, { params }) {
FILE: app/api/projects/[projectId]/questions/templates/route.js
function GET (line 6) | async function GET(request, { params }) {
function POST (line 36) | async function POST(request, { params }) {
FILE: app/api/projects/[projectId]/questions/tree/route.js
function GET (line 10) | async function GET(request, { params }) {
FILE: app/api/projects/[projectId]/route.js
function GET (line 4) | async function GET(request, { params }) {
function PUT (line 20) | async function PUT(request, { params }) {
function DELETE (line 51) | async function DELETE(request, { params }) {
FILE: app/api/projects/[projectId]/split/route.js
function POST (line 8) | async function POST(request, { params }) {
function GET (line 63) | async function GET(request, { params }) {
FILE: app/api/projects/[projectId]/tags/route.js
function GET (line 6) | async function GET(request, { params }) {
function PUT (line 26) | async function PUT(request, { params }) {
function POST (line 51) | async function POST(request, { params }) {
function DELETE (line 70) | async function DELETE(request, { params }) {
FILE: app/api/projects/[projectId]/tasks/[taskId]/route.js
function GET (line 7) | async function GET(request, { params }) {
function PATCH (line 59) | async function PATCH(request, { params }) {
function DELETE (line 133) | async function DELETE(request, { params }) {
FILE: app/api/projects/[projectId]/tasks/list/route.js
function GET (line 7) | async function GET(request, { params }) {
FILE: app/api/projects/[projectId]/tasks/route.js
function normalizeModelEndpoint (line 9) | function normalizeModelEndpoint(endpoint = '') {
function normalizeTaskModelInfo (line 20) | function normalizeTaskModelInfo(modelInfo) {
function GET (line 39) | async function GET(request, { params }) {
function PUT (line 68) | async function PUT(request, { params }) {
function POST (line 110) | async function POST(request, { params }) {
FILE: app/api/projects/delete-directory/route.js
function POST (line 13) | async function POST(request) {
FILE: app/api/projects/migrate/route.js
function POST (line 10) | async function POST() {
function GET (line 48) | async function GET(request) {
function executeMigration (line 98) | async function executeMigration(taskId) {
FILE: app/api/projects/open-directory/route.js
function POST (line 13) | async function POST(request) {
FILE: app/api/projects/route.js
function POST (line 4) | async function POST(request) {
function GET (line 34) | async function GET(request) {
FILE: app/api/projects/unmigrated/route.js
function GET (line 11) | async function GET(request) {
FILE: app/api/update/route.js
function POST (line 6) | async function POST() {
FILE: app/dataset-square/page.js
function DatasetSquarePage (line 11) | function DatasetSquarePage() {
FILE: app/layout.js
function RootLayout (line 15) | function RootLayout({ children }) {
FILE: app/monitoring/components/Charts.js
function Charts (line 18) | function Charts({ trendData, modelDistribution }) {
FILE: app/monitoring/components/StatsCards.js
function StatCard (line 11) | function StatCard({ title, value, subValue, icon: Icon, color }) {
function StatsCards (line 60) | function StatsCards({ data }) {
FILE: app/monitoring/components/UsageTable.js
function UsageTable (line 29) | function UsageTable({
FILE: app/monitoring/hooks/useMonitoringData.js
function useMonitoringData (line 6) | function useMonitoringData() {
FILE: app/monitoring/page.js
function MonitoringPage (line 30) | function MonitoringPage() {
FILE: app/page.js
function Home (line 14) | function Home() {
FILE: app/projects/[projectId]/blind-test-tasks/[taskId]/page.js
function BlindTestDetailPage (line 26) | function BlindTestDetailPage() {
FILE: app/projects/[projectId]/blind-test-tasks/components/BlindTestHeader.js
function BlindTestHeader (line 8) | function BlindTestHeader({ title, status, onBack, actions }) {
FILE: app/projects/[projectId]/blind-test-tasks/components/BlindTestInProgress.js
function AnswerBox (line 34) | function AnswerBox({ title, modelLabel, answer, streaming, showThinking,...
function BlindTestInProgress (line 188) | function BlindTestInProgress({
FILE: app/projects/[projectId]/blind-test-tasks/components/BlindTestTaskCard.js
constant STATUS_MAP (line 29) | const STATUS_MAP = {
function BlindTestTaskCard (line 36) | function BlindTestTaskCard({ task, onView, onDelete, onInterrupt, onCont...
FILE: app/projects/[projectId]/blind-test-tasks/components/CreateBlindTestDialog.js
function CreateBlindTestDialog (line 32) | function CreateBlindTestDialog({ open, onClose, projectId, onCreate }) {
FILE: app/projects/[projectId]/blind-test-tasks/components/ResultDetailList.js
function ResultAnswerSection (line 33) | function ResultAnswerSection({ title, rawContent, isWinner, modelLabel, ...
function ResultItem (line 147) | function ResultItem({ result, index, task, question }) {
function ResultDetailList (line 320) | function ResultDetailList({ task }) {
FILE: app/projects/[projectId]/blind-test-tasks/components/ResultSummary.js
function ResultSummary (line 7) | function ResultSummary({ stats, modelInfo }) {
FILE: app/projects/[projectId]/blind-test-tasks/hooks/useBlindTestDetail.js
function useBlindTestDetail (line 8) | function useBlindTestDetail(projectId, taskId) {
FILE: app/projects/[projectId]/blind-test-tasks/hooks/useBlindTestTasks.js
function useBlindTestTasks (line 8) | function useBlindTestTasks(projectId) {
FILE: app/projects/[projectId]/blind-test-tasks/page.js
function BlindTestTasksPage (line 28) | function BlindTestTasksPage() {
FILE: app/projects/[projectId]/datasets/[datasetId]/page.js
function DatasetDetailsPage (line 17) | function DatasetDetailsPage({ params }) {
FILE: app/projects/[projectId]/datasets/[datasetId]/useDatasetDetails.js
function useDatasetDetails (line 14) | function useDatasetDetails(projectId, datasetId) {
FILE: app/projects/[projectId]/datasets/hooks/useDatasetFilters.js
function useDatasetFilters (line 11) | function useDatasetFilters(projectId) {
FILE: app/projects/[projectId]/datasets/page.js
function DatasetsPage (line 25) | function DatasetsPage({ params }) {
FILE: app/projects/[projectId]/distill/autoDistillService.js
class AutoDistillService (line 8) | class AutoDistillService {
method executeDistillTask (line 23) | async executeDistillTask(config) {
method buildTagTree (line 170) | async buildTagTree(config) {
method batchBuildTagTree (line 228) | async batchBuildTagTree(config) {
method generateQuestionsForTags (line 400) | async generateQuestionsForTags(config) {
method generateDatasetsForQuestions (line 552) | async generateDatasetsForQuestions(config) {
method generateMultiTurnDatasetsForQuestions (line 641) | async generateMultiTurnDatasetsForQuestions(config) {
method generateSingleMultiTurnDataset (line 761) | async generateSingleMultiTurnDataset({ projectId, questionId, question...
method generateSingleDataset (line 780) | async generateSingleDataset({ projectId, questionId, questionInfo, mod...
method getTagDepth (line 810) | getTagDepth(tag, parentMap) {
method getTagPath (line 830) | getTagPath(tag, parentMap) {
method addLog (line 862) | addLog(onLog, message) {
FILE: app/projects/[projectId]/distill/page.js
function DistillPage (line 21) | function DistillPage() {
FILE: app/projects/[projectId]/eval-datasets/[evalId]/page.js
constant QUESTION_TYPE_CONFIG (line 40) | const QUESTION_TYPE_CONFIG = {
function EvalDatasetDetailPage (line 68) | function EvalDatasetDetailPage() {
FILE: app/projects/[projectId]/eval-datasets/[evalId]/useEvalDatasetDetails.js
function useEvalDatasetDetails (line 8) | function useEvalDatasetDetails(projectId, evalId) {
FILE: app/projects/[projectId]/eval-datasets/components/BuiltinDatasetDialog.js
function BuiltinDatasetDialog (line 30) | function BuiltinDatasetDialog({ open, onClose, projectId, onSuccess }) {
FILE: app/projects/[projectId]/eval-datasets/components/EvalDatasetCard.js
constant QUESTION_TYPE_CONFIG (line 18) | const QUESTION_TYPE_CONFIG = {
function EvalDatasetCard (line 46) | function EvalDatasetCard({ item, selected, onSelect, onEdit, onDelete, p...
FILE: app/projects/[projectId]/eval-datasets/components/EvalDatasetHeader.js
function EvalDatasetHeader (line 10) | function EvalDatasetHeader({ projectId, onNavigate, onDelete }) {
FILE: app/projects/[projectId]/eval-datasets/components/EvalDatasetList.js
function EvalDatasetList (line 23) | function EvalDatasetList({ items, selectedIds, onSelect, onSelectAll, on...
FILE: app/projects/[projectId]/eval-datasets/components/EvalEditableField.js
function EvalEditableField (line 11) | function EvalEditableField({
FILE: app/projects/[projectId]/eval-datasets/components/EvalToolbar.js
constant STATS_CONFIG (line 43) | const STATS_CONFIG = [
function EvalToolbar (line 51) | function EvalToolbar({
FILE: app/projects/[projectId]/eval-datasets/components/ExportEvalDialog.js
constant QUESTION_TYPES (line 33) | const QUESTION_TYPES = [
constant EXPORT_FORMATS (line 41) | const EXPORT_FORMATS = [
function ExportEvalDialog (line 47) | function ExportEvalDialog({
FILE: app/projects/[projectId]/eval-datasets/components/ImportDialog.js
function ImportDialog (line 42) | function ImportDialog({ open, onClose, projectId, onSuccess }) {
FILE: app/projects/[projectId]/eval-datasets/constants.js
constant QUESTION_TYPES (line 1) | const QUESTION_TYPES = [
constant FORMAT_PREVIEW (line 9) | const FORMAT_PREVIEW = {
constant DATA_SETS (line 181) | const DATA_SETS = [
FILE: app/projects/[projectId]/eval-datasets/hooks/useEvalDatasets.js
function useEvalDatasets (line 9) | function useEvalDatasets(projectId) {
FILE: app/projects/[projectId]/eval-datasets/hooks/useExportEvalDatasets.js
function useExportEvalDatasets (line 9) | function useExportEvalDatasets(projectId, stats = {}) {
FILE: app/projects/[projectId]/eval-datasets/page.js
function EvalDatasetsPage (line 32) | function EvalDatasetsPage() {
FILE: app/projects/[projectId]/eval-tasks/[taskId]/components/EvalHeader.js
function EvalHeader (line 10) | function EvalHeader({ task, stats, filterCorrect, onFilterCorrectSelect ...
FILE: app/projects/[projectId]/eval-tasks/[taskId]/components/EvalStats.js
constant QUESTION_TYPE_LABELS (line 7) | const QUESTION_TYPE_LABELS = {
function EvalStats (line 15) | function EvalStats({ stats, currentFilter, onFilterSelect }) {
FILE: app/projects/[projectId]/eval-tasks/[taskId]/components/QuestionCard.js
constant EVAL_STATUS (line 17) | const EVAL_STATUS = {
constant STATUS_CONFIG (line 24) | const STATUS_CONFIG = {
function QuestionCard (line 30) | function QuestionCard({ result, index, task }) {
FILE: app/projects/[projectId]/eval-tasks/[taskId]/page.js
function EvalTaskDetailPage (line 25) | function EvalTaskDetailPage() {
FILE: app/projects/[projectId]/eval-tasks/components/CreateEvalTaskDialog.js
function CreateEvalTaskDialog (line 29) | function CreateEvalTaskDialog({ open, onClose, projectId, onSuccess }) {
FILE: app/projects/[projectId]/eval-tasks/components/EvalTaskCard.js
constant STATUS_CONFIG (line 32) | const STATUS_CONFIG = {
function EvalTaskCard (line 39) | function EvalTaskCard({ task, onView, onDelete, onInterrupt }) {
FILE: app/projects/[projectId]/eval-tasks/components/ModelSelector.js
function ModelSelector (line 18) | function ModelSelector({ models, selectedModels, onSelectionChange, erro...
FILE: app/projects/[projectId]/eval-tasks/components/QuestionFilter.js
constant QUESTION_TYPES (line 22) | const QUESTION_TYPES = [
function QuestionFilter (line 30) | function QuestionFilter({
FILE: app/projects/[projectId]/eval-tasks/components/ScoreAnchorsForm.js
function ScoreAnchorsForm (line 24) | function ScoreAnchorsForm({
FILE: app/projects/[projectId]/eval-tasks/hooks/useEvalTaskDetail.js
function useEvalTaskDetail (line 8) | function useEvalTaskDetail(projectId, taskId) {
FILE: app/projects/[projectId]/eval-tasks/hooks/useEvalTaskForm.js
function useEvalTaskForm (line 6) | function useEvalTaskForm(projectId, open) {
FILE: app/projects/[projectId]/eval-tasks/hooks/useEvalTasks.js
function useEvalTasks (line 8) | function useEvalTasks(projectId) {
FILE: app/projects/[projectId]/eval-tasks/page.js
function EvalTasksPage (line 31) | function EvalTasksPage() {
FILE: app/projects/[projectId]/image-datasets/[datasetId]/page.js
function ImageDatasetDetailPage (line 11) | function ImageDatasetDetailPage() {
FILE: app/projects/[projectId]/image-datasets/components/DatasetContent.js
function handleAnswer (line 10) | function handleAnswer(dataset) {
function DatasetContent (line 25) | function DatasetContent({ dataset, projectId, onAnswerChange }) {
FILE: app/projects/[projectId]/image-datasets/components/DatasetSidebar.js
function DatasetSidebar (line 10) | function DatasetSidebar({ dataset, projectId, onUpdate }) {
FILE: app/projects/[projectId]/image-datasets/components/EmptyState.js
function EmptyState (line 8) | function EmptyState() {
FILE: app/projects/[projectId]/image-datasets/components/ImageDatasetCard.js
function ImageDatasetCard (line 10) | function ImageDatasetCard({
FILE: app/projects/[projectId]/image-datasets/components/ImageDatasetFilterDialog.js
function ImageDatasetFilterDialog (line 18) | function ImageDatasetFilterDialog({
FILE: app/projects/[projectId]/image-datasets/components/ImageDatasetFilters.js
function ImageDatasetFilters (line 8) | function ImageDatasetFilters({
FILE: app/projects/[projectId]/image-datasets/components/ImageDatasetHeader.js
function ImageDatasetHeader (line 14) | function ImageDatasetHeader({
FILE: app/projects/[projectId]/image-datasets/components/MetadataEditor.js
function MetadataEditor (line 11) | function MetadataEditor({ dataset, projectId, onUpdate }) {
FILE: app/projects/[projectId]/image-datasets/components/MetadataInfo.js
function MetadataInfo (line 10) | function MetadataInfo({ dataset }) {
FILE: app/projects/[projectId]/image-datasets/hooks/useImageDatasetDetail.js
function useImageDatasetDetail (line 6) | function useImageDatasetDetail(projectId, datasetId) {
FILE: app/projects/[projectId]/image-datasets/hooks/useImageDatasetDetails.js
function useImageDatasetDetails (line 9) | function useImageDatasetDetails(projectId, datasetId) {
FILE: app/projects/[projectId]/image-datasets/hooks/useImageDatasetFilters.js
constant STORAGE_KEY (line 3) | const STORAGE_KEY = 'imageDatasetFilters';
function useImageDatasetFilters (line 5) | function useImageDatasetFilters(projectId) {
FILE: app/projects/[projectId]/image-datasets/hooks/useImageDatasets.js
function useImageDatasets (line 6) | function useImageDatasets(projectId, filters = {}) {
FILE: app/projects/[projectId]/image-datasets/page.js
function ImageDatasetsPage (line 21) | function ImageDatasetsPage() {
FILE: app/projects/[projectId]/images/components/DatasetDialog.js
function DatasetDialog (line 22) | function DatasetDialog({ open, projectId, image, onClose, onSuccess }) {
FILE: app/projects/[projectId]/images/components/ImageFilters.js
function ImageFilters (line 24) | function ImageFilters({
FILE: app/projects/[projectId]/images/components/ImageGrid.js
function ImageGrid (line 28) | function ImageGrid({
FILE: app/projects/[projectId]/images/components/ImageList.js
function ImageList (line 33) | function ImageList({
FILE: app/projects/[projectId]/images/components/ImportDialog.js
function ImportDialog (line 33) | function ImportDialog({ open, projectId, onClose, onSuccess }) {
FILE: app/projects/[projectId]/images/components/QuestionDialog.js
function QuestionDialog (line 22) | function QuestionDialog({ open, projectId, image, onClose, onSuccess }) {
FILE: app/projects/[projectId]/images/components/annotation/AIGenerateButton.js
function AIGenerateButton (line 21) | function AIGenerateButton({
FILE: app/projects/[projectId]/images/components/annotation/AnnotationDialog.js
function AnnotationDialog (line 19) | function AnnotationDialog({
FILE: app/projects/[projectId]/images/components/annotation/AnswerInput.js
function AnswerInput (line 9) | function AnswerInput({
FILE: app/projects/[projectId]/images/components/annotation/QuestionSelector.js
function QuestionSelector (line 8) | function QuestionSelector({
FILE: app/projects/[projectId]/images/hooks/useAnnotation.js
function clearJsonValues (line 7) | function clearJsonValues(obj) {
function useAnnotation (line 21) | function useAnnotation(projectId, onSuccess, onFindNextImage) {
FILE: app/projects/[projectId]/images/page.js
function ImagesPage (line 40) | function ImagesPage() {
FILE: app/projects/[projectId]/layout.js
function ProjectLayout (line 11) | function ProjectLayout({ children, params }) {
FILE: app/projects/[projectId]/multi-turn/[conversationId]/page.js
function ConversationDetailPage (line 25) | function ConversationDetailPage({ params }) {
FILE: app/projects/[projectId]/multi-turn/[conversationId]/useConversationDetails.js
function useConversationDetails (line 11) | function useConversationDetails(projectId, conversationId) {
FILE: app/projects/[projectId]/multi-turn/components/ConversationTable.js
constant QUESTION_TOOLTIP_THRESHOLD (line 24) | const QUESTION_TOOLTIP_THRESHOLD = 80;
constant SCENARIO_TOOLTIP_THRESHOLD (line 25) | const SCENARIO_TOOLTIP_THRESHOLD = 120;
FILE: app/projects/[projectId]/multi-turn/page.js
function MultiTurnDatasetPage (line 13) | function MultiTurnDatasetPage({ params }) {
FILE: app/projects/[projectId]/page.js
function ProjectPage (line 10) | function ProjectPage({ params }) {
FILE: app/projects/[projectId]/playground/page.js
function ModelPlayground (line 16) | function ModelPlayground({ searchParams }) {
FILE: app/projects/[projectId]/questions/components/ConfirmDialog.js
function ConfirmDialog (line 18) | function ConfirmDialog({
FILE: app/projects/[projectId]/questions/components/ExportQuestionsDialog.js
function ExportQuestionsDialog (line 21) | function ExportQuestionsDialog({ open, onClose, onExport, selectedCount,...
FILE: app/projects/[projectId]/questions/components/QuestionEditDialog.js
function QuestionEditDialog (line 22) | function QuestionEditDialog({
FILE: app/projects/[projectId]/questions/components/QuestionsFilter.js
function QuestionsFilter (line 7) | function QuestionsFilter({
FILE: app/projects/[projectId]/questions/components/QuestionsPageHeader.js
function QuestionsPageHeader (line 16) | function QuestionsPageHeader({
FILE: app/projects/[projectId]/questions/components/TemplateListView.js
function TemplateListView (line 20) | function TemplateListView({ templates, onEditTemplate, onDeleteTemplate,...
FILE: app/projects/[projectId]/questions/components/template/TemplateFormDialog.js
function TemplateFormDialog (line 24) | function TemplateFormDialog({ open, onClose, onSubmit, template }) {
FILE: app/projects/[projectId]/questions/components/template/TemplateManagementDialog.js
function TemplateManagementDialog (line 28) | function TemplateManagementDialog({
FILE: app/projects/[projectId]/questions/hooks/useQuestionDelete.js
function useQuestionDelete (line 8) | function useQuestionDelete(projectId, onDeleteSuccess) {
FILE: app/projects/[projectId]/questions/hooks/useQuestionEdit.js
function useQuestionEdit (line 7) | function useQuestionEdit(projectId, onSuccess) {
FILE: app/projects/[projectId]/questions/hooks/useQuestionGeneration.js
function useQuestionGeneration (line 11) | function useQuestionGeneration(projectId, model, taskSettings, getQuesti...
FILE: app/projects/[projectId]/questions/hooks/useQuestionTemplates.js
function useQuestionTemplates (line 11) | function useQuestionTemplates(projectId, sourceType = null) {
FILE: app/projects/[projectId]/questions/hooks/useQuestionsFilter.js
function useQuestionsFilter (line 7) | function useQuestionsFilter(projectId) {
FILE: app/projects/[projectId]/questions/page.js
function QuestionsPage (line 29) | function QuestionsPage({ params }) {
FILE: app/projects/[projectId]/settings/components/PromptSettings.js
function PromptSettings (line 18) | function PromptSettings() {
FILE: app/projects/[projectId]/settings/page.js
constant TABS (line 15) | const TABS = {
function SettingsPage (line 22) | function SettingsPage({ params }) {
FILE: app/projects/[projectId]/tasks/page.js
function TasksPage (line 13) | function TasksPage({ params }) {
FILE: app/projects/[projectId]/text-split/page.js
function TextSplitPage (line 42) | function TextSplitPage({ params }) {
FILE: app/projects/[projectId]/text-split/useChunks.js
function useChunks (line 13) | function useChunks(projectId, currentFilter = 'all') {
FILE: app/projects/[projectId]/text-split/useDataCleaning.js
function useDataCleaning (line 9) | function useDataCleaning(projectId) {
FILE: app/projects/[projectId]/text-split/useEvalGeneration.js
function useEvalGeneration (line 14) | function useEvalGeneration(projectId) {
FILE: app/projects/[projectId]/text-split/useFileProcessing.js
function useFileProcessing (line 16) | function useFileProcessing(projectId) {
FILE: app/projects/[projectId]/text-split/useQuestionGeneration.js
function useQuestionGeneration (line 9) | function useQuestionGeneration(projectId) {
FILE: components/I18nProvider.js
function I18nProvider (line 7) | function I18nProvider({ children }) {
FILE: components/LanguageSwitcher.js
function LanguageSwitcher (line 8) | function LanguageSwitcher() {
FILE: components/ModelSelect.js
function ModelSelect (line 12) | function ModelSelect({
FILE: components/Navbar/ActionButtons.js
function ActionButtons (line 21) | function ActionButtons({
FILE: components/Navbar/ContextBar.js
function ContextBar (line 31) | function ContextBar({ projects = [], currentProjectId, onMouseLeave }) {
FILE: components/Navbar/DesktopMenus.js
function DesktopMenus (line 23) | function DesktopMenus({
FILE: components/Navbar/Logo.js
function Logo (line 12) | function Logo({ theme }) {
FILE: components/Navbar/MobileDrawer.js
function MobileDrawer (line 44) | function MobileDrawer({
FILE: components/Navbar/NavigationTabs.js
function NavigationTabs (line 20) | function NavigationTabs({
FILE: components/Navbar/index.js
function Navbar (line 30) | function Navbar({ projects = [], currentProject }) {
FILE: components/TaskIcon.js
function TaskIcon (line 18) | function TaskIcon({ projectId, theme }) {
FILE: components/ThemeRegistry.js
function ThemeRegistry (line 314) | function ThemeRegistry({ children }) {
function InnerThemeRegistry (line 332) | function InnerThemeRegistry({ children }) {
FILE: components/common/MessageAlert.js
function MessageAlert (line 5) | function MessageAlert({ message, onClose }) {
FILE: components/conversations/ConversationContent.js
function ConversationContent (line 9) | function ConversationContent({ messages, editMode, onMessageChange, conv...
FILE: components/conversations/ConversationHeader.js
function ConversationHeader (line 15) | function ConversationHeader({
FILE: components/conversations/ConversationMetadata.js
function ConversationMetadata (line 10) | function ConversationMetadata({ conversation }) {
FILE: components/conversations/ConversationRatingSection.js
function ConversationRatingSection (line 14) | function ConversationRatingSection({ conversation, projectId, onUpdate }) {
FILE: components/dataset-square/DatasetSearchBar.js
function DatasetSearchBar (line 26) | function DatasetSearchBar() {
FILE: components/dataset-square/DatasetSiteCard.js
function DatasetSiteCard (line 8) | function DatasetSiteCard({ site }) {
FILE: components/dataset-square/DatasetSiteList.js
function DatasetSiteList (line 12) | function DatasetSiteList() {
FILE: components/datasets/DatasetHeader.js
function DatasetHeader (line 14) | function DatasetHeader({
FILE: components/datasets/DatasetMetadata.js
function DatasetMetadata (line 11) | function DatasetMetadata({ currentDataset, onViewChunk }) {
FILE: components/datasets/DatasetRatingSection.js
function DatasetRatingSection (line 19) | function DatasetRatingSection({ dataset, projectId, onUpdate, currentDat...
FILE: components/datasets/EditableField.js
function getValue (line 22) | function getValue(value, answerType, useMarkdown, t, onOptimize) {
function EditableField (line 92) | function EditableField({
FILE: components/datasets/EvalVariantDialog.js
function EvalVariantDialog (line 29) | function EvalVariantDialog({ open, onClose, onGenerate, onSave }) {
FILE: components/datasets/ImportDatasetDialog.js
function ImportDatasetDialog (line 26) | function ImportDatasetDialog({ open, onClose, projectId, onImportSuccess...
FILE: components/datasets/NoteInput.js
function NoteInput (line 14) | function NoteInput({
FILE: components/datasets/OptimizeDialog.js
function OptimizeDialog (line 10) | function OptimizeDialog({ open, onClose, onConfirm }) {
FILE: components/datasets/StarRating.js
function StarRating (line 11) | function StarRating({ value = 0, onChange, readOnly = false, size = 'med...
FILE: components/datasets/TagSelector.js
function TagSelector (line 13) | function TagSelector({
FILE: components/datasets/import/FieldMappingStep.js
function FieldMappingStep (line 27) | function FieldMappingStep({ previewData, onMappingComplete, onError }) {
FILE: components/datasets/import/FileUploadStep.js
function FileUploadStep (line 23) | function FileUploadStep({ onDataLoaded, onError }) {
FILE: components/datasets/import/ImportProgressStep.js
function ImportProgressStep (line 22) | function ImportProgressStep({ projectId, rawData, fieldMapping, sourceIn...
FILE: components/datasets/utils/ratingUtils.js
constant SCORE_RANGES (line 128) | const SCORE_RANGES = {
FILE: components/distill/AutoDistillDialog.js
function AutoDistillDialog (line 35) | function AutoDistillDialog({
FILE: components/distill/AutoDistillProgress.js
function AutoDistillProgress (line 26) | function AutoDistillProgress({ open, onClose, progress = {} }) {
FILE: components/distill/ConfirmDialog.js
function ConfirmDialog (line 15) | function ConfirmDialog({
FILE: components/distill/QuestionGenerationDialog.js
function QuestionGenerationDialog (line 38) | function QuestionGenerationDialog({ open, onClose, onGenerated, projectI...
FILE: components/distill/QuestionListItem.js
function QuestionListItem (line 32) | function QuestionListItem({
FILE: components/distill/TagEditDialog.js
function TagEditDialog (line 27) | function TagEditDialog({ open, tag, projectId, onClose, onSuccess }) {
FILE: components/distill/TagGenerationDialog.js
function TagGenerationDialog (line 35) | function TagGenerationDialog({ open, onClose, onGenerated, projectId, pa...
FILE: components/distill/TagMenu.js
function TagMenu (line 17) | function TagMenu({ anchorEl, open, onClose, onEdit, onDelete }) {
FILE: components/distill/TagTreeItem.js
function TagTreeItem (line 48) | function TagTreeItem({
FILE: components/home/CreateProjectDialog.js
function CreateProjectDialog (line 23) | function CreateProjectDialog({ open, onClose }) {
FILE: components/home/HeroSection.js
function HeroSection (line 12) | function HeroSection({ onCreateProject }) {
FILE: components/home/MigrationDialog.js
function MigrationDialog (line 35) | function MigrationDialog({ open, onClose, projectIds = [] }) {
FILE: components/home/ParticleBackground.js
function ParticleBackground (line 6) | function ParticleBackground() {
FILE: components/home/ProjectCard.js
function ProjectCard (line 61) | function ProjectCard({ project, onDeleteClick }) {
FILE: components/home/ProjectList.js
function ProjectList (line 19) | function ProjectList({ projects, onCreateProject }) {
FILE: components/home/StatsCard.js
function StatsCard (line 20) | function StatsCard({ projects }) {
FILE: components/mga/GaPairsIndicator.js
function GaPairsIndicator (line 28) | function GaPairsIndicator({ projectId, fileId, fileName = '未命名文件' }) {
FILE: components/mga/GaPairsManager.js
function GaPairsManager (line 40) | function GaPairsManager({ projectId, fileId, onGaPairsChange }) {
FILE: components/playground/ChatMessage.js
function ChatMessage (line 17) | function ChatMessage({ message, modelName }) {
FILE: components/playground/ModelSelector.js
constant ITEM_HEIGHT (line 15) | const ITEM_HEIGHT = 48;
constant ITEM_PADDING_TOP (line 16) | const ITEM_PADDING_TOP = 8;
function ModelSelector (line 33) | function ModelSelector({ models, selectedModels, onChange }) {
FILE: components/questions/QuestionListView.js
function QuestionListView (line 27) | function QuestionListView({
FILE: components/questions/QuestionTreeView.js
function QuestionTreeView (line 38) | function QuestionTreeView({
FILE: components/settings/BasicSettings.js
function BasicSettings (line 8) | function BasicSettings({ projectId }) {
FILE: components/settings/ModelSettings.js
function ModelSettings (line 46) | function ModelSettings({ projectId }) {
FILE: components/settings/TaskSettings.js
function TaskSettings (line 28) | function TaskSettings({ projectId }) {
FILE: components/tasks/TaskActions.js
function TaskActions (line 10) | function TaskActions({ task, onAbort, onDelete }) {
FILE: components/tasks/TaskFilters.js
function TaskFilters (line 18) | function TaskFilters({ statusFilter, setStatusFilter, typeFilter, setTyp...
FILE: components/tasks/TaskProgress.js
function TaskProgress (line 8) | function TaskProgress({ task }) {
FILE: components/tasks/TaskStatusChip.js
function TaskStatusChip (line 8) | function TaskStatusChip({ status }) {
FILE: components/tasks/TasksTable.js
function TasksTable (line 26) | function TasksTable({
FILE: components/text-split/BatchEditChunkDialog.js
function BatchEditChunksDialog (line 33) | function BatchEditChunksDialog({
FILE: components/text-split/ChunkBatchDeleteDialog.js
function ChunkBatchDeleteDialog (line 14) | function ChunkBatchDeleteDialog({ open, onClose, onConfirm, loading, cou...
FILE: components/text-split/ChunkCard.js
function ChunkCard (line 73) | function ChunkCard({
FILE: components/text-split/ChunkDeleteDialog.js
function ChunkDeleteDialog (line 6) | function ChunkDeleteDialog({ open, onClose, onConfirm }) {
FILE: components/text-split/ChunkFilterDialog.js
function ChunkFilterDialog (line 19) | function ChunkFilterDialog({ open, onClose, onApply, initialFilters = {}...
FILE: components/text-split/ChunkList.js
function ChunkList (line 27) | function ChunkList({
FILE: components/text-split/ChunkListHeader.js
function ChunkListHeader (line 20) | function ChunkListHeader({
FILE: components/text-split/ChunkViewDialog.js
function ChunkViewDialog (line 8) | function ChunkViewDialog({ open, chunk, onClose }) {
FILE: components/text-split/DomainAnalysis.js
function TreeNode (line 54) | function TreeNode({ node, level = 0, onEdit, onDelete, onAddChild }) {
function DomainTree (line 173) | function DomainTree({ tags, onEdit, onDelete, onAddChild }) {
function DomainAnalysis (line 183) | function DomainAnalysis({ projectId, toc = '', loading = false }) {
FILE: components/text-split/FileUploader.js
function FileUploader (line 19) | function FileUploader({
FILE: components/text-split/LoadingBackdrop.js
function LoadingBackdrop (line 5) | function LoadingBackdrop({ open, title, description, progress = null }) {
FILE: components/text-split/MarkdownViewDialog.js
function MarkdownViewDialog (line 26) | function MarkdownViewDialog({ open, text, onClose, projectId, onSaveSucc...
FILE: components/text-split/PdfSettings.js
function PdfSettings (line 6) | function PdfSettings({ pdfStrategy, setPdfStrategy, selectedViosnModel, ...
FILE: components/text-split/components/DeleteConfirmDialog.js
function DeleteConfirmDialog (line 16) | function DeleteConfirmDialog({ open, fileName, onClose, onConfirm }) {
FILE: components/text-split/components/DirectoryView.js
function DirectoryView (line 19) | function DirectoryView({ items, expandedItems, onToggleItem, level = 0, ...
FILE: components/text-split/components/DomainTreeActionDialog.js
function DomainTreeActionDialog (line 22) | function DomainTreeActionDialog({ open, onClose, onConfirm, isFirstUploa...
FILE: components/text-split/components/DomainTreeView.js
function DomainTreeView (line 13) | function DomainTreeView({ nodes = [] }) {
FILE: components/text-split/components/FileList.js
function FileList (line 49) | function FileList({
FILE: components/text-split/components/FileLoadingProgress.js
function FileLoadingProgress (line 37) | function FileLoadingProgress({ fileTask }) {
function ProgressSection (line 147) | function ProgressSection({ label, progress, color, mt = 0 }) {
FILE: components/text-split/components/PdfProcessingDialog.js
function PdfProcessingDialog (line 91) | function PdfProcessingDialog({
FILE: components/text-split/components/TabPanel.js
function TabPanel (line 12) | function TabPanel({ value, index, children }) {
FILE: components/text-split/components/UploadArea.js
function UploadArea (line 20) | function UploadArea({
FILE: constant/index.js
constant FILE (line 5) | const FILE = {
constant TASK (line 9) | const TASK = {
FILE: constant/model.js
constant MODEL_PROVIDERS (line 1) | const MODEL_PROVIDERS = [
constant DEFAULT_MODEL_SETTINGS (line 79) | const DEFAULT_MODEL_SETTINGS = {
FILE: constant/setting.js
constant DEFAULT_SETTINGS (line 2) | const DEFAULT_SETTINGS = {
FILE: electron/modules/cache.js
function clearCache (line 9) | async function clearCache(app) {
FILE: electron/modules/database.js
function clearDatabaseCache (line 11) | async function clearDatabaseCache(app) {
function initializeDatabase (line 40) | async function initializeDatabase(app) {
FILE: electron/modules/db-updater.js
function executeSql (line 11) | async function executeSql(dbUrl, sql) {
function getSqlConfigs (line 46) | async function getSqlConfigs(userDataPath, resourcesPath, isDev, logger ...
function updateUserSqlConfig (line 92) | function updateUserSqlConfig(userDataPath, sqlConfig) {
function getSqlsToExecute (line 105) | function getSqlsToExecute(userSqlConfig, appSqlConfig) {
function updateDatabase (line 127) | async function updateDatabase(userDataPath, resourcesPath, isDev, logger...
FILE: electron/modules/ipc-handlers.js
function setupIpcHandlers (line 9) | function setupIpcHandlers(app, isDev) {
FILE: electron/modules/logger.js
function setupLogging (line 9) | function setupLogging(app) {
function setupIpcLogging (line 41) | function setupIpcLogging(ipcMain, app, isDev) {
function clearLogs (line 66) | async function clearLogs(app) {
FILE: electron/modules/menu.js
function createMenu (line 12) | function createMenu(mainWindow, clearCache) {
FILE: electron/modules/server.js
function checkPort (line 11) | function checkPort(port) {
function startNextServer (line 31) | async function startNextServer(port, app) {
FILE: electron/modules/updater.js
function setupAutoUpdater (line 8) | function setupAutoUpdater(mainWindow) {
function checkUpdate (line 61) | async function checkUpdate(isDev) {
function downloadUpdate (line 92) | async function downloadUpdate() {
function installUpdate (line 106) | function installUpdate() {
FILE: electron/modules/window-manager.js
function createWindow (line 14) | function createWindow(isDev, port) {
function loadAppUrl (line 85) | function loadAppUrl(appUrl) {
function openDevTools (line 94) | function openDevTools() {
function getMainWindow (line 104) | function getMainWindow() {
FILE: hooks/useDebounce.js
function useDebounce (line 3) | function useDebounce(value, delay = 500) {
FILE: hooks/useFileProcessingStatus.js
function useFileProcessingStatus (line 18) | function useFileProcessingStatus() {
FILE: hooks/useGenerateDataset.js
function useGenerateDataset (line 9) | function useGenerateDataset() {
FILE: hooks/useModelPlayground.js
function useModelPlayground (line 7) | function useModelPlayground(projectId, defaultModelId = null) {
FILE: hooks/useTaskSettings.js
function useTaskSettings (line 5) | function useTaskSettings(projectId) {
FILE: lib/api/chunk.js
function getChunkById (line 9) | async function getChunkById(projectId, chunkId) {
FILE: lib/api/file.js
function uploadFile (line 13) | async function uploadFile({ file, projectId, fileContent, fileName, t }) {
function deleteFile (line 33) | async function deleteFile({ fileToDelete, projectId, domainTreeActionTyp...
function getFiles (line 56) | async function getFiles({ projectId, page, size, fileName }) {
FILE: lib/api/task.js
function getProjectTasks (line 6) | function getProjectTasks(projectId) {
FILE: lib/db/base.js
function getDbDirectory (line 8) | function getDbDirectory() {
constant PROJECT_ROOT (line 44) | let PROJECT_ROOT = '';
function getProjectRoot (line 47) | async function getProjectRoot() {
function getProjectPath (line 54) | async function getProjectPath(projectId) {
function ensureDbExists (line 60) | async function ensureDbExists() {
function readJsonFile (line 69) | async function readJsonFile(filePath) {
function writeJsonFile (line 80) | async function writeJsonFile(filePath, data) {
function ensureDir (line 111) | async function ensureDir(dirPath) {
FILE: lib/db/chunks.js
function saveChunks (line 7) | async function saveChunks(chunks) {
function getChunkById (line 16) | async function getChunkById(chunkId) {
function getChunksByFileIds (line 25) | async function getChunksByFileIds(fileIds) {
function getChunkByProjectId (line 44) | async function getChunkByProjectId(projectId, filter) {
function deleteChunkById (line 84) | async function deleteChunkById(chunkId) {
function getChunkByName (line 101) | async function getChunkByName(projectId, chunkName) {
function getChunkContentsByNames (line 121) | async function getChunkContentsByNames(projectId, chunkNames) {
function deleteChunksByFileId (line 157) | async function deleteChunksByFileId(projectId, fileId) {
function updateChunkById (line 193) | async function updateChunkById(chunkId, chunkData) {
function deleteChunkAndFile (line 204) | async function deleteChunkAndFile(projectId, fileName) {
function updateChunkContent (line 249) | async function updateChunkContent(chunkId, newContent) {
function getChunks (line 265) | async function getChunks(projectId, page = 1, pageSize = 20) {
FILE: lib/db/custom-prompts.js
function getCustomPrompts (line 11) | async function getCustomPrompts(projectId, promptType = null, language =...
function getCustomPrompt (line 46) | async function getCustomPrompt(projectId, promptType, promptKey, languag...
function saveCustomPrompt (line 73) | async function saveCustomPrompt(projectId, promptType, promptKey, langua...
function deleteCustomPrompt (line 110) | async function deleteCustomPrompt(projectId, promptType, promptKey, lang...
function batchSaveCustomPrompts (line 135) | async function batchSaveCustomPrompts(projectId, prompts) {
function toggleCustomPrompt (line 156) | async function toggleCustomPrompt(id, isActive) {
function getPromptTemplates (line 172) | async function getPromptTemplates() {
FILE: lib/db/dataset-conversations.js
function buildDatasetConversationWhere (line 7) | function buildDatasetConversationWhere(projectId, filters = {}) {
function createDatasetConversation (line 51) | async function createDatasetConversation(data) {
function getDatasetConversationById (line 62) | async function getDatasetConversationById(id) {
function getDatasetConversationsByPagination (line 79) | async function getDatasetConversationsByPagination(projectId, page = 1, ...
function updateDatasetConversation (line 119) | async function updateDatasetConversation(id, data) {
function deleteDatasetConversation (line 134) | async function deleteDatasetConversation(id) {
function getDatasetConversationStats (line 145) | async function getDatasetConversationStats(projectId) {
function getAllDatasetConversations (line 173) | async function getAllDatasetConversations(projectId, filters = {}) {
function getAllDatasetConversationIds (line 188) | async function getAllDatasetConversationIds(projectId, filters = {}) {
function getDatasetConversationsByQuestionId (line 203) | async function getDatasetConversationsByQuestionId(questionId) {
function getConversationNavigationItems (line 217) | async function getConversationNavigationItems(projectId, conversationId,...
FILE: lib/db/datasets.js
function getDatasetsByPagination (line 16) | async function getDatasetsByPagination(
function getDatasets (line 129) | async function getDatasets(projectId, confirmed) {
function getDatasetsBatch (line 161) | async function getDatasetsBatch(projectId, confirmed, offset = 0, batchS...
function getDatasetsIds (line 188) | async function getDatasetsIds(
function getDatasetsCount (line 294) | async function getDatasetsCount(projectId) {
function getDatasetsCountByQuestionId (line 311) | async function getDatasetsCountByQuestionId(questionId) {
function getDatasetsById (line 328) | async function getDatasetsById(id) {
function updateDatasetMetadata (line 346) | async function updateDatasetMetadata(id, { score, tags, note }) {
function getUsedCustomTags (line 376) | async function getUsedCustomTags(projectId) {
function createDataset (line 408) | async function createDataset(dataset) {
function updateDataset (line 419) | async function updateDataset(dataset) {
function deleteDataset (line 433) | async function deleteDataset(datasetId) {
function updateDatasetEvaluation (line 484) | async function updateDatasetEvaluation(datasetId, score, aiEvaluation) {
function getDatasetsCounts (line 500) | async function getDatasetsCounts(projectId) {
function getNavigationItems (line 518) | async function getNavigationItems(projectId, datasetId, operateType) {
function getBalancedDatasetsByTags (line 545) | async function getBalancedDatasetsByTags(projectId, balanceConfig, confi...
function getBalancedDatasetsByTagsBatch (line 591) | async function getBalancedDatasetsByTagsBatch(
function getTagsWithDatasetCounts (line 673) | async function getTagsWithDatasetCounts(projectId, confirmed) {
function getDatasetsByIds (line 704) | async function getDatasetsByIds(projectId, datasetIds) {
function getDatasetsByIdsBatch (line 733) | async function getDatasetsByIdsBatch(projectId, datasetIds, offset = 0, ...
FILE: lib/db/evalDatasets.js
function createEvalQuestion (line 8) | async function createEvalQuestion(data) {
function createManyEvalQuestions (line 22) | async function createManyEvalQuestions(dataArray) {
function getEvalQuestions (line 36) | async function getEvalQuestions(projectId) {
function buildEvalQuestionWhere (line 60) | function buildEvalQuestionWhere(projectId, { questionType, questionTypes...
function getEvalQuestionsWithPagination (line 104) | async function getEvalQuestionsWithPagination(projectId, options = {}) {
function getEvalQuestionById (line 150) | async function getEvalQuestionById(id) {
function updateEvalQuestion (line 177) | async function updateEvalQuestion(id, data) {
function getEvalQuestionsStats (line 194) | async function getEvalQuestionsStats(projectId) {
function getEvalQuestionsByChunkId (line 245) | async function getEvalQuestionsByChunkId(chunkId) {
function deleteEvalQuestion (line 262) | async function deleteEvalQuestion(id) {
FILE: lib/db/evalResults.js
function createEvalResult (line 9) | async function createEvalResult(data) {
function createManyEvalResults (line 23) | async function createManyEvalResults(dataArray) {
function getEvalResultsByTaskId (line 38) | async function getEvalResultsByTaskId(taskId, { page = 1, pageSize = 10,...
function getEvalResultsByProjectId (line 88) | async function getEvalResultsByProjectId(projectId) {
function updateEvalResult (line 116) | async function updateEvalResult(id, data) {
function updateEvalResultsByTaskId (line 134) | async function updateEvalResultsByTaskId(taskId, data) {
function deleteEvalResultsByTaskId (line 151) | async function deleteEvalResultsByTaskId(taskId) {
function getEvalResultsStats (line 167) | async function getEvalResultsStats(taskId) {
function getEvalResult (line 218) | async function getEvalResult(taskId, evalDatasetId) {
function upsertEvalResult (line 241) | async function upsertEvalResult(taskId, evalDatasetId, data) {
FILE: lib/db/fileToDb.js
function main (line 13) | async function main(task = null) {
function backupHandle (line 104) | async function backupHandle(projectRoot, projectPath) {
function projectHandle (line 117) | async function projectHandle(projectId, projectRoot, projectPath) {
function syncOtherConfigFile (line 162) | async function syncOtherConfigFile(projectRoot, projectPath, projectNewI...
function tagsHandle (line 176) | async function tagsHandle(projectId, projectPath) {
function insertTags (line 191) | async function insertTags(projectId, tags, parentId = null) {
function modelConfigHandle (line 208) | async function modelConfigHandle(projectId, projectPath) {
function chunkHandle (line 243) | async function chunkHandle(projectId, projectPath) {
function questionHandle (line 278) | async function questionHandle(projectId, projectPath) {
function datasetHandle (line 310) | async function datasetHandle(projectId, projectPath) {
function updateQuestions (line 350) | async function updateQuestions(projectId) {
function copyDirRecursive (line 366) | async function copyDirRecursive(src, dest, projectNewId) {
function safeReadDir (line 403) | async function safeReadDir(dirPath, options = {}) {
FILE: lib/db/files.js
function getProjectFileContent (line 16) | async function getProjectFileContent(projectId, fileName) {
function getProjectFileContentById (line 38) | async function getProjectFileContentById(projectId, fileId) {
FILE: lib/db/ga-pairs.js
function getGaPairsByFileId (line 9) | async function getGaPairsByFileId(fileId) {
function getGaPairsByProjectId (line 26) | async function getGaPairsByProjectId(projectId) {
function createGaPairs (line 50) | async function createGaPairs(gaPairs) {
function updateGaPair (line 65) | async function updateGaPair(id, data) {
function deleteGaPairsByFileId (line 82) | async function deleteGaPairsByFileId(fileId) {
function toggleGaPairActive (line 99) | async function toggleGaPairActive(id, isActive) {
function getActiveGaPairsByFileId (line 116) | async function getActiveGaPairsByFileId(fileId) {
function batchUpdateGaPairs (line 136) | async function batchUpdateGaPairs(updates) {
function saveGaPairs (line 171) | async function saveGaPairs(projectId, fileId, gaPairs) {
function hasGaPairs (line 203) | async function hasGaPairs(projectId, fileId) {
function getGaPairStats (line 220) | async function getGaPairStats(projectId) {
FILE: lib/db/imageDatasets.js
function createImageDataset (line 7) | async function createImageDataset(projectId, datasetData) {
function getImageDatasets (line 24) | async function getImageDatasets(imageId, page = 1, pageSize = 10) {
function updateImageDataset (line 50) | async function updateImageDataset(datasetId, updateData) {
function deleteImageDataset (line 65) | async function deleteImageDataset(datasetId) {
function getImageDatasetsByProject (line 79) | async function getImageDatasetsByProject(projectId, page = 1, pageSize =...
function getImageDatasetById (line 129) | async function getImageDatasetById(datasetId) {
function getImageDatasetsTagsByProject (line 191) | async function getImageDatasetsTagsByProject(projectId) {
function getImageDatasetsForExport (line 208) | async function getImageDatasetsForExport(projectId, confirmedOnly = fals...
FILE: lib/db/images.js
function getImages (line 9) | async function getImages(projectId, page = 1, pageSize = 20, imageName =...
function createImage (line 187) | async function createImage(projectId, imageData) {
function createImages (line 204) | async function createImages(projectId, imagesData) {
function getImageById (line 244) | async function getImageById(imageId) {
function getImageByName (line 258) | async function getImageByName(projectId, imageName) {
function deleteImage (line 275) | async function deleteImage(imageId) {
function getImageDetail (line 289) | async function getImageDetail(imageId) {
function getImageChunk (line 324) | async function getImageChunk(projectId) {
FILE: lib/db/llm-models.js
function getLlmModelsByProviderId (line 4) | async function getLlmModelsByProviderId(providerId) {
function createLlmModels (line 13) | async function createLlmModels(models) {
FILE: lib/db/llm-providers.js
function getLlmProviders (line 4) | async function getLlmProviders() {
FILE: lib/db/model-config.js
function getModelConfigByProjectId (line 5) | async function getModelConfigByProjectId(projectId) {
function createInitModelConfig (line 14) | async function createInitModelConfig(data) {
function getModelConfigById (line 23) | async function getModelConfigById(id) {
function deleteModelConfigById (line 32) | async function deleteModelConfigById(id) {
function saveModelConfig (line 41) | async function saveModelConfig(models) {
FILE: lib/db/projects.js
function createProject (line 11) | async function createProject(projectData) {
function isExistByName (line 33) | async function isExistByName(name) {
function getProjects (line 48) | async function getProjects() {
function getProject (line 104) | async function getProject(projectId) {
function updateProject (line 114) | async function updateProject(projectId, projectData) {
function deleteProject (line 128) | async function deleteProject(projectId) {
function getTaskConfig (line 143) | async function getTaskConfig(projectId) {
FILE: lib/db/questionTemplates.js
function getTemplates (line 15) | async function getTemplates(projectId, options = {}) {
function getTemplateById (line 50) | async function getTemplateById(templateId) {
function createTemplate (line 72) | async function createTemplate(projectId, data) {
function updateTemplate (line 101) | async function updateTemplate(templateId, data) {
function deleteTemplate (line 129) | async function deleteTemplate(templateId) {
function getTemplateUsageCount (line 140) | async function getTemplateUsageCount(templateId) {
function getTemplatesUsageCount (line 154) | async function getTemplatesUsageCount(templateIds) {
FILE: lib/db/questions.js
function getQuestions (line 16) | async function getQuestions(
function getQuestionsForTree (line 84) | async function getQuestionsForTree(projectId, input, isDistill = false, ...
function getQuestionsByTag (line 141) | async function getQuestionsByTag(projectId, tag, input, isDistill = fals...
function getAllQuestionsByProjectId (line 240) | async function getAllQuestionsByProjectId(projectId) {
function getQuestionsIds (line 262) | async function getQuestionsIds(
function getQuestionsByTagName (line 307) | async function getQuestionsByTagName(projectId, tagName) {
function getDatasetCountsForQuestions (line 337) | async function getDatasetCountsForQuestions(questionIds) {
function getQuestionById (line 456) | async function getQuestionById(id) {
function isExistByQuestion (line 467) | async function isExistByQuestion(question, projectId) {
function getQuestionsCount (line 482) | async function getQuestionsCount(projectId) {
function saveQuestions (line 502) | async function saveQuestions(projectId, questions, chunkId) {
function updateQuestion (line 522) | async function updateQuestion(question) {
function updateQuestionAnsweredStatus (line 538) | async function updateQuestionAnsweredStatus(projectId, imageId, question...
function saveQuestionsWithGaPair (line 564) | async function saveQuestionsWithGaPair(projectId, questions, chunkId, ga...
function getQuestionsForChunk (line 588) | async function getQuestionsForChunk(projectId, chunkId) {
function deleteQuestion (line 596) | async function deleteQuestion(questionId) {
function batchDeleteQuestions (line 614) | async function batchDeleteQuestions(questionIds) {
function getQuestionTemplateById (line 629) | async function getQuestionTemplateById(id) {
FILE: lib/db/tags.js
function getTags (line 9) | async function getTags(projectId) {
function getTagsTreeWithQuestionCount (line 18) | async function getTagsTreeWithQuestionCount(projectId, parentId = null) {
function getAllLabels (line 123) | async function getAllLabels(tagId) {
function createTag (line 147) | async function createTag(projectId, label, parentId) {
function updateTag (line 163) | async function updateTag(label, id) {
function deleteTag (line 177) | async function deleteTag(id) {
function getAllChildTags (line 228) | async function getAllChildTags(parentId, projectId) {
function deleteQuestionsByTag (line 264) | async function deleteQuestionsByTag(label, projectId) {
function deleteDatasetsByTag (line 284) | async function deleteDatasetsByTag(label, projectId) {
function batchSaveTags (line 300) | async function batchSaveTags(projectId, tags) {
function insertTags (line 312) | async function insertTags(projectId, tags, parentId = null) {
FILE: lib/db/texts.js
function getFiles (line 8) | async function getFiles(projectId) {
function deleteFile (line 37) | async function deleteFile(projectId, fileName) {
FILE: lib/db/upload-files.js
function getUploadFilesPagination (line 9) | async function getUploadFilesPagination(projectId, page = 1, pageSize = ...
function getUploadFileInfoById (line 35) | async function getUploadFileInfoById(fileId) {
function getUploadFilesByProjectId (line 44) | async function getUploadFilesByProjectId(projectId) {
function checkUploadFileInfoByMD5 (line 67) | async function checkUploadFileInfoByMD5(projectId, md5) {
function createUploadFileInfo (line 81) | async function createUploadFileInfo(fileInfo) {
function delUploadFileInfoById (line 90) | async function delUploadFileInfoById(fileId) {
FILE: lib/file/file-process/check-file.js
function checkMaxSize (line 6) | function checkMaxSize(files) {
function getvalidFiles (line 18) | function getvalidFiles(files) {
function checkInvalidFiles (line 34) | function checkInvalidFiles(files) {
FILE: lib/file/file-process/epub/index.js
function processEpub (line 10) | async function processEpub(arrayBuffer) {
function getBookTitle (line 99) | function getBookTitle(opfDoc) {
function getChapterTitle (line 119) | function getChapterTitle(htmlContent) {
function extractBodyContent (line 154) | function extractBodyContent(htmlContent) {
FILE: lib/file/file-process/get-content.js
function getContent (line 9) | async function getContent(file) {
FILE: lib/file/file-process/pdf/default.js
function defaultProcessing (line 6) | async function defaultProcessing(projectId, fileName) {
FILE: lib/file/file-process/pdf/index.js
function processPdf (line 14) | async function processPdf(strategy = 'default', projectId, fileName, opt...
FILE: lib/file/file-process/pdf/mineru-local.js
constant MINERU_BASE_URL (line 7) | const MINERU_BASE_URL = 'file_parse';
function minerULocalProcessing (line 9) | async function minerULocalProcessing(projectId, fileName, options = {}) {
function processingFile (line 69) | async function processingFile(filePath, uploadUrl) {
FILE: lib/file/file-process/pdf/mineru.js
constant MINERU_API_BASE (line 9) | const MINERU_API_BASE = 'https://mineru.net/api/v4';
constant POLL_INTERVAL (line 10) | const POLL_INTERVAL = 3000;
constant MAX_POLL_ATTEMPTS (line 11) | const MAX_POLL_ATTEMPTS = 90;
constant PROCESSING_STATES (line 12) | const PROCESSING_STATES = {
function minerUProcessing (line 17) | async function minerUProcessing(projectId, fileName, options = {}) {
function makeHttpRequest (line 152) | async function makeHttpRequest(url, options) {
function uploadFile (line 201) | async function uploadFile(filePath, uploadUrl) {
function downloadAndExtractZip (line 241) | async function downloadAndExtractZip(zipUrl, targetDir, fileName) {
FILE: lib/file/file-process/pdf/util.js
function getFilePageCount (line 4) | async function getFilePageCount(projectId, fileList) {
FILE: lib/file/file-process/pdf/vision.js
function visionProcessing (line 9) | async function visionProcessing(projectId, fileName, options = {}) {
FILE: lib/file/file-process/utils.js
function handleLongFileName (line 5) | function handleLongFileName(filename) {
FILE: lib/file/split-markdown/core/parser.js
function extractOutline (line 10) | function extractOutline(text) {
function splitByHeadings (line 35) | function splitByHeadings(text, outline) {
FILE: lib/file/split-markdown/core/splitter.js
function splitLongSection (line 11) | function splitLongSection(section, maxSplitLength) {
function processSections (line 79) | function processSections(sections, outline, minSplitLength, maxSplitLeng...
FILE: lib/file/split-markdown/core/summary.js
function generateEnhancedSummary (line 13) | function generateEnhancedSummary(section, outline, partIndex = null, tot...
function generateSummary (line 178) | function generateSummary(section, outline, partIndex = null, totalParts ...
FILE: lib/file/split-markdown/core/toc.js
function extractTableOfContents (line 14) | function extractTableOfContents(text, options = {}) {
function generateAnchorId (line 71) | function generateAnchorId(title) {
function buildNestedToc (line 86) | function buildNestedToc(items, includeLinks) {
function tocToMarkdown (line 125) | function tocToMarkdown(toc, options = {}) {
function nestedTocToMarkdown (line 139) | function nestedTocToMarkdown(items, indent = 0, includeLinks) {
function flatTocToMarkdown (line 166) | function flatTocToMarkdown(items, includeLinks) {
FILE: lib/file/split-markdown/index.js
function splitMarkdown (line 19) | function splitMarkdown(markdownText, minSplitLength, maxSplitLength) {
FILE: lib/file/split-markdown/output/fileWriter.js
function saveToSeparateFiles (line 15) | function saveToSeparateFiles(splitResult, baseFilename, callback) {
FILE: lib/file/split-markdown/output/formatter.js
function combineMarkdown (line 10) | function combineMarkdown(splitResult) {
FILE: lib/file/split-markdown/utils/common.js
function ensureDirectoryExists (line 12) | function ensureDirectoryExists(directory) {
function getFilenameWithoutExt (line 23) | function getFilenameWithoutExt(filePath) {
FILE: lib/file/text-splitter.js
function splitFileByType (line 18) | async function splitFileByType({ projectPath, fileContent, fileName, pro...
function splitProjectFile (line 170) | async function splitProjectFile(projectId, file) {
function getProjectChunks (line 221) | async function getProjectChunks(projectId, filter) {
function getProjectTocs (line 276) | async function getProjectTocs(projectId) {
function getProjectTocByName (line 318) | async function getProjectTocByName(projectId, fileName) {
FILE: lib/llm/common/prompt-loader.js
function getPromptContent (line 12) | async function getPromptContent(projectId, promptType, promptKey, langua...
function getPromptKey (line 37) | function getPromptKey(language, baseKey) {
function getLanguageFromKey (line 52) | function getLanguageFromKey(promptKey) {
function processPrompt (line 72) | async function processPrompt(language, promptType, baseKey, defaultPromp...
FILE: lib/llm/common/question-template.js
function getQuestionTemplate (line 1) | function getQuestionTemplate(questionTemplate, language) {
FILE: lib/llm/common/util.js
function extractJsonFromLLMOutput (line 3) | function extractJsonFromLLMOutput(output) {
function safeParseJSON (line 42) | function safeParseJSON(output) {
function extractThinkChain (line 79) | function extractThinkChain(text) {
function extractAnswer (line 110) | function extractAnswer(text) {
function removeLeadingNumber (line 125) | function removeLeadingNumber(label) {
FILE: lib/llm/core/index.js
class LLMClient (line 15) | class LLMClient {
method constructor (line 26) | constructor(config = {}) {
method _handleEndpoint (line 50) | _handleEndpoint(provider, endpoint) {
method _createClient (line 70) | _createClient(provider, config) {
method setProjectId (line 95) | setProjectId(projectId) {
method _callClientMethod (line 100) | async _callClientMethod(method, ...args) {
method chat (line 140) | async chat(prompt, options = {}) {
method chatStreamAPI (line 161) | async chatStreamAPI(prompt, options = {}) {
method chatStream (line 176) | async chatStream(prompt, options = {}) {
method getResponse (line 186) | async getResponse(prompt, options = {}) {
method extractAnswerAndCOT (line 192) | extractAnswerAndCOT(llmRes) {
method getResponseWithCOT (line 218) | async getResponseWithCOT(prompt, options = {}) {
method getVisionResponse (line 231) | async getVisionResponse(prompt, base64Image, mimeType = 'image/jpeg') {
FILE: lib/llm/core/providers/alibailian.js
class AlibailianClient (line 9) | class AlibailianClient extends BaseClient {
method constructor (line 10) | constructor(config) {
method _getModel (line 20) | _getModel() {
method chat (line 28) | async chat(messages, options = {}) {
method _convertJson (line 79) | _convertJson(messages) {
method _formatMessagesForVision (line 87) | _formatMessagesForVision(messages) {
FILE: lib/llm/core/providers/base.js
function checkOpenAIModel (line 3) | function checkOpenAIModel(endpoint, model) {
class BaseClient (line 10) | class BaseClient {
method constructor (line 11) | constructor(config) {
method chat (line 26) | async chat(messages, options) {
method chatStream (line 47) | async chatStream(messages, options) {
method _getModel (line 66) | _getModel() {
method chatStreamAPI (line 73) | async chatStreamAPI(messages, options) {
method _convertJson (line 269) | _convertJson(data) {
FILE: lib/llm/core/providers/ollama.js
class OllamaClient (line 4) | class OllamaClient extends BaseClient {
method constructor (line 5) | constructor(config) {
method _getModel (line 13) | _getModel() {
method getModels (line 21) | async getModels() {
method chatStreamAPI (line 39) | async chatStreamAPI(messages, options) {
FILE: lib/llm/core/providers/openai.js
class OpenAIClient (line 4) | class OpenAIClient extends BaseClient {
method constructor (line 5) | constructor(config) {
method _getModel (line 13) | _getModel() {
FILE: lib/llm/core/providers/openrouter.js
class OpenRouterClient (line 5) | class OpenRouterClient extends BaseClient {
method constructor (line 6) | constructor(config) {
method _getModel (line 14) | _getModel() {
FILE: lib/llm/core/providers/zhipu.js
class ZhiPuClient (line 5) | class ZhiPuClient extends BaseClient {
method constructor (line 6) | constructor(config) {
method _getModel (line 14) | _getModel() {
FILE: lib/llm/prompts/addLabel.js
constant ADD_LABEL_PROMPT (line 3) | const ADD_LABEL_PROMPT = `
constant ADD_LABEL_PROMPT_EN (line 59) | const ADD_LABEL_PROMPT_EN = `
constant ADD_LABEL_PROMPT_TR (line 115) | const ADD_LABEL_PROMPT_TR = `
function getAddLabelPrompt (line 171) | async function getAddLabelPrompt(language, { label, question }, projectI...
FILE: lib/llm/prompts/answer.js
constant ANSWER_PROMPT (line 4) | const ANSWER_PROMPT = `
constant ANSWER_PROMPT_EN (line 41) | const ANSWER_PROMPT_EN = `
constant ANSWER_PROMPT_TR (line 76) | const ANSWER_PROMPT_TR = `
function getAnswerPrompt (line 111) | async function getAnswerPrompt(language, { text, question, questionTempl...
FILE: lib/llm/prompts/dataClean.js
constant DATA_CLEAN_PROMPT (line 3) | const DATA_CLEAN_PROMPT = `
constant DATA_CLEAN_PROMPT_EN (line 48) | const DATA_CLEAN_PROMPT_EN = `
constant DATA_CLEAN_PROMPT_TR (line 93) | const DATA_CLEAN_PROMPT_TR = `
function getDataCleanPrompt (line 146) | async function getDataCleanPrompt(language, { text }, projectId = null) {
FILE: lib/llm/prompts/datasetEvaluation.js
constant DATASET_EVALUATION_PROMPT (line 3) | const DATASET_EVALUATION_PROMPT = `
constant DATASET_EVALUATION_PROMPT_EN (line 105) | const DATASET_EVALUATION_PROMPT_EN = `
constant DATASET_EVALUATION_PROMPT_TR (line 207) | const DATASET_EVALUATION_PROMPT_TR = `
function getDatasetEvaluationPrompt (line 320) | async function getDatasetEvaluationPrompt(language, { chunkContent, ques...
FILE: lib/llm/prompts/distillQuestions.js
constant DISTILL_QUESTIONS_PROMPT (line 4) | const DISTILL_QUESTIONS_PROMPT = `
constant DISTILL_QUESTIONS_PROMPT_EN (line 64) | const DISTILL_QUESTIONS_PROMPT_EN = `
constant DISTILL_QUESTIONS_PROMPT_TR (line 124) | const DISTILL_QUESTIONS_PROMPT_TR = `
function distillQuestionsPrompt (line 193) | async function distillQuestionsPrompt(
FILE: lib/llm/prompts/distillTags.js
constant DISTILL_TAGS_PROMPT (line 3) | const DISTILL_TAGS_PROMPT = `
constant DISTILL_TAGS_PROMPT_EN (line 49) | const DISTILL_TAGS_PROMPT_EN = `
constant DISTILL_TAGS_PROMPT_TR (line 95) | const DISTILL_TAGS_PROMPT_TR = `
function distillTagsPrompt (line 148) | async function distillTagsPrompt(
FILE: lib/llm/prompts/enhancedAnswer.js
constant ENHANCED_ANSWER_PROMPT (line 4) | const ENHANCED_ANSWER_PROMPT = `
constant ENHANCED_ANSWER_PROMPT_EN (line 48) | const ENHANCED_ANSWER_PROMPT_EN = `
constant ENHANCED_ANSWER_PROMPT_TR (line 92) | const ENHANCED_ANSWER_PROMPT_TR = `
function getEnhancedAnswerPrompt (line 136) | async function getEnhancedAnswerPrompt(
constant GA_PROMPT (line 154) | const GA_PROMPT = `
constant GA_PROMPT_EN (line 170) | const GA_PROMPT_EN = `
constant GA_PROMPT_TR (line 186) | const GA_PROMPT_TR = `
function getGAPrompt (line 202) | function getGAPrompt(language, { activeGaPair }) {
FILE: lib/llm/prompts/evalQuestion.js
constant EVAL_TRUE_FALSE_PROMPT (line 5) | const EVAL_TRUE_FALSE_PROMPT = `
constant EVAL_TRUE_FALSE_PROMPT_EN (line 67) | const EVAL_TRUE_FALSE_PROMPT_EN = `
constant EVAL_SINGLE_CHOICE_PROMPT (line 131) | const EVAL_SINGLE_CHOICE_PROMPT = `
constant EVAL_SINGLE_CHOICE_PROMPT_EN (line 192) | const EVAL_SINGLE_CHOICE_PROMPT_EN = `
constant EVAL_MULTIPLE_CHOICE_PROMPT (line 255) | const EVAL_MULTIPLE_CHOICE_PROMPT = `
constant EVAL_MULTIPLE_CHOICE_PROMPT_EN (line 315) | const EVAL_MULTIPLE_CHOICE_PROMPT_EN = `
constant EVAL_SHORT_ANSWER_PROMPT (line 377) | const EVAL_SHORT_ANSWER_PROMPT = `
constant EVAL_SHORT_ANSWER_PROMPT_EN (line 441) | const EVAL_SHORT_ANSWER_PROMPT_EN = `
constant EVAL_OPEN_ENDED_PROMPT (line 507) | const EVAL_OPEN_ENDED_PROMPT = `
constant EVAL_OPEN_ENDED_PROMPT_EN (line 563) | const EVAL_OPEN_ENDED_PROMPT_EN = `
function getEvalQuestionPrompt (line 631) | async function getEvalQuestionPrompt(language, questionType, { text, num...
FILE: lib/llm/prompts/ga-generation.js
constant GA_GENERATION_PROMPT (line 8) | const GA_GENERATION_PROMPT = `
constant GA_GENERATION_PROMPT_EN (line 54) | const GA_GENERATION_PROMPT_EN = `
constant GA_GENERATION_PROMPT_TR (line 100) | const GA_GENERATION_PROMPT_TR = `
function getGAGenerationPrompt (line 154) | async function getGAGenerationPrompt(language, { text }, projectId = nul...
FILE: lib/llm/prompts/imageAnswer.js
constant IMAGE_ANSWER_PROMPT (line 5) | const IMAGE_ANSWER_PROMPT = `{{question}}{{templatePrompt}}{{outputForma...
constant IMAGE_ANSWER_PROMPT_EN (line 6) | const IMAGE_ANSWER_PROMPT_EN = IMAGE_ANSWER_PROMPT;
constant IMAGE_ANSWER_PROMPT_TR (line 7) | const IMAGE_ANSWER_PROMPT_TR = IMAGE_ANSWER_PROMPT;
function getImageAnswerPrompt (line 17) | async function getImageAnswerPrompt(language, { question, questionTempla...
FILE: lib/llm/prompts/imageQuestion.js
constant IMAGE_QUESTION_PROMPT (line 3) | const IMAGE_QUESTION_PROMPT = `
constant IMAGE_QUESTION_PROMPT_EN (line 55) | const IMAGE_QUESTION_PROMPT_EN = `
constant IMAGE_QUESTION_PROMPT_TR (line 107) | const IMAGE_QUESTION_PROMPT_TR = `
function getImageQuestionPrompt (line 167) | async function getImageQuestionPrompt(language, { number = 3 }, projectI...
FILE: lib/llm/prompts/label.js
constant LABEL_PROMPT (line 3) | const LABEL_PROMPT = `
constant LABEL_PROMPT_EN (line 59) | const LABEL_PROMPT_EN = `
constant LABEL_PROMPT_TR (line 118) | const LABEL_PROMPT_TR = `
function getLabelPrompt (line 185) | async function getLabelPrompt(language, { text }, projectId = null) {
FILE: lib/llm/prompts/labelRevise.js
constant LABEL_REVISE_PROMPT (line 8) | const LABEL_REVISE_PROMPT = `
constant LABEL_REVISE_PROMPT_EN (line 82) | const LABEL_REVISE_PROMPT_EN = `
constant LABEL_REVISE_PROMPT_TR (line 156) | const LABEL_REVISE_PROMPT_TR = `
function getLabelRevisePrompt (line 230) | async function getLabelRevisePrompt(
FILE: lib/llm/prompts/llmJudge.js
constant DEFAULT_SHORT_ANSWER_SCORE_ANCHORS_ZH (line 11) | const DEFAULT_SHORT_ANSWER_SCORE_ANCHORS_ZH = [
constant DEFAULT_SHORT_ANSWER_SCORE_ANCHORS_EN (line 20) | const DEFAULT_SHORT_ANSWER_SCORE_ANCHORS_EN = [
constant DEFAULT_OPEN_ENDED_SCORE_ANCHORS_ZH (line 29) | const DEFAULT_OPEN_ENDED_SCORE_ANCHORS_ZH = [
constant DEFAULT_OPEN_ENDED_SCORE_ANCHORS_EN (line 38) | const DEFAULT_OPEN_ENDED_SCORE_ANCHORS_EN = [
function getDefaultScoreAnchors (line 52) | function getDefaultScoreAnchors(questionType, language = 'zh-CN') {
function formatScoreAnchors (line 66) | function formatScoreAnchors(scoreAnchors) {
constant SHORT_ANSWER_JUDGE_PROMPT (line 75) | const SHORT_ANSWER_JUDGE_PROMPT = `
constant SHORT_ANSWER_JUDGE_PROMPT_EN (line 127) | const SHORT_ANSWER_JUDGE_PROMPT_EN = `
constant OPEN_ENDED_JUDGE_PROMPT (line 179) | const OPEN_ENDED_JUDGE_PROMPT = `
constant OPEN_ENDED_JUDGE_PROMPT_EN (line 224) | const OPEN_ENDED_JUDGE_PROMPT_EN = `
function buildJudgePrompt (line 282) | async function buildJudgePrompt(
FILE: lib/llm/prompts/modelEvaluation.js
constant TRUE_FALSE_ANSWER_PROMPT (line 12) | const TRUE_FALSE_ANSWER_PROMPT = `
constant TRUE_FALSE_ANSWER_PROMPT_EN (line 36) | const TRUE_FALSE_ANSWER_PROMPT_EN = `
constant SINGLE_CHOICE_ANSWER_PROMPT (line 60) | const SINGLE_CHOICE_ANSWER_PROMPT = `
constant SINGLE_CHOICE_ANSWER_PROMPT_EN (line 85) | const SINGLE_CHOICE_ANSWER_PROMPT_EN = `
constant MULTIPLE_CHOICE_ANSWER_PROMPT (line 110) | const MULTIPLE_CHOICE_ANSWER_PROMPT = `
constant MULTIPLE_CHOICE_ANSWER_PROMPT_EN (line 136) | const MULTIPLE_CHOICE_ANSWER_PROMPT_EN = `
constant SHORT_ANSWER_PROMPT (line 162) | const SHORT_ANSWER_PROMPT = `
constant SHORT_ANSWER_PROMPT_EN (line 190) | const SHORT_ANSWER_PROMPT_EN = `
constant OPEN_ENDED_ANSWER_PROMPT (line 218) | const OPEN_ENDED_ANSWER_PROMPT = `
constant OPEN_ENDED_ANSWER_PROMPT_EN (line 245) | const OPEN_ENDED_ANSWER_PROMPT_EN = `
function buildAnswerPrompt (line 277) | async function buildAnswerPrompt(questionType, question, options = null,...
function buildJudgePrompt (line 331) | async function buildJudgePrompt(
FILE: lib/llm/prompts/multiTurnConversation.js
constant ASSISTANT_REPLY_PROMPT (line 4) | const ASSISTANT_REPLY_PROMPT = `
constant ASSISTANT_REPLY_PROMPT_EN (line 63) | const ASSISTANT_REPLY_PROMPT_EN = `
constant ASSISTANT_REPLY_PROMPT_TR (line 121) | const ASSISTANT_REPLY_PROMPT_TR = `
constant NEXT_QUESTION_PROMPT (line 180) | const NEXT_QUESTION_PROMPT = `
constant NEXT_QUESTION_PROMPT_EN (line 237) | const NEXT_QUESTION_PROMPT_EN = `
constant NEXT_QUESTION_PROMPT_TR (line 294) | const NEXT_QUESTION_PROMPT_TR = `
function getAssistantReplyPrompt (line 365) | async function getAssistantReplyPrompt(
function getNextQuestionPrompt (line 415) | async function getNextQuestionPrompt(
FILE: lib/llm/prompts/newAnswer.js
constant NEW_ANSWER_PROMPT (line 3) | const NEW_ANSWER_PROMPT = `
constant NEW_ANSWER_PROMPT_EN (line 41) | const NEW_ANSWER_PROMPT_EN = `
constant NEW_ANSWER_PROMPT_TR (line 79) | const NEW_ANSWER_PROMPT_TR = `
function getNewAnswerPrompt (line 117) | async function getNewAnswerPrompt(language, { question, answer, cot, adv...
FILE: lib/llm/prompts/optimizeCot.js
constant OPTIMIZE_COT_PROMPT (line 3) | const OPTIMIZE_COT_PROMPT = `
constant OPTIMIZE_COT_PROMPT_EN (line 36) | const OPTIMIZE_COT_PROMPT_EN = `
constant OPTIMIZE_COT_PROMPT_TR (line 69) | const OPTIMIZE_COT_PROMPT_TR = `
function getOptimizeCotPrompt (line 112) | async function getOptimizeCotPrompt(language, { originalQuestion, answer...
FILE: lib/llm/prompts/question.js
constant QUESTION_PROMPT (line 3) | const QUESTION_PROMPT = `
constant QUESTION_PROMPT_EN (line 52) | const QUESTION_PROMPT_EN = `
constant QUESTION_PROMPT_TR (line 101) | const QUESTION_PROMPT_TR = `
constant GA_QUESTION_PROMPT (line 150) | const GA_QUESTION_PROMPT = `
constant GA_QUESTION_PROMPT_EN (line 162) | const GA_QUESTION_PROMPT_EN = `
constant GA_QUESTION_PROMPT_TR (line 177) | const GA_QUESTION_PROMPT_TR = `
function getGAPrompt (line 198) | function getGAPrompt(language, { activeGaPair }) {
function getQuestionPrompt (line 222) | async function getQuestionPrompt(
FILE: lib/llm/usageLogger.js
function getDateString (line 10) | function getDateString() {
function logLlmUsage (line 30) | async function logLlmUsage({
function createLatencyTimer (line 68) | function createLatencyTimer() {
function extractTokenUsage (line 85) | function extractTokenUsage(response) {
function withUsageLogging (line 122) | async function withUsageLogging(llmCall, context) {
FILE: lib/services/clean.js
function cleanDataForChunk (line 16) | async function cleanDataForChunk(projectId, chunkId, options) {
FILE: lib/services/datasets/evaluation.js
function evaluateDataset (line 20) | async function evaluateDataset(projectId, datasetId, model, language = '...
function batchEvaluateDatasets (line 127) | async function batchEvaluateDatasets(projectId, datasetIds, model, langu...
FILE: lib/services/datasets/index.js
function optimizeCot (line 23) | async function optimizeCot(originalQuestion, answer, originalCot, langua...
function generateDatasetForQuestion (line 46) | async function generateDatasetForQuestion(projectId, questionId, options) {
FILE: lib/services/eval/index.js
function calculateQuestionCounts (line 17) | function calculateQuestionCounts(textLength, questionGenerationLength, r...
function generateEvalQuestionsForChunk (line 81) | async function generateEvalQuestionsForChunk(projectId, chunkId, options) {
FILE: lib/services/evaluation/index.js
constant EVAL_STATUS (line 11) | const EVAL_STATUS = {
function evaluateSingleQuestion (line 28) | async function evaluateSingleQuestion({
function isFormatError (line 104) | function isFormatError(questionType, modelAnswer) {
function needsLLMJudge (line 130) | function needsLLMJudge(questionType) {
function getModelAnswer (line 137) | async function getModelAnswer(llmClient, evalDataset, language) {
function evaluateAnswer (line 161) | async function evaluateAnswer(evalDataset, modelAnswer, judgeLLMClient, ...
function evaluateTrueFalse (line 200) | function evaluateTrueFalse(modelAnswer, correctAnswer) {
function evaluateSingleChoice (line 221) | function evaluateSingleChoice(modelAnswer, correctAnswer) {
function evaluateMultipleChoice (line 237) | function evaluateMultipleChoice(modelAnswer, correctAnswer) {
function evaluateWithLLM (line 253) | async function evaluateWithLLM(
function parseJudgeResponse (line 289) | function parseJudgeResponse(responseText) {
function normalizeText (line 329) | function normalizeText(text) {
function extractLetters (line 339) | function extractLetters(answer) {
FILE: lib/services/ga/ga-generation.js
function generateGaPairs (line 14) | async function generateGaPairs(textContent, projectId, language = '中文') {
function callLLMAPI (line 65) | async function callLLMAPI(model, prompt) {
function parseGaResponse (line 92) | function parseGaResponse(response) {
function getFallbackGaPairs (line 188) | function getFallbackGaPairs() {
FILE: lib/services/ga/ga-pairs.js
function batchGenerateGaPairs (line 16) | async function batchGenerateGaPairs(projectId, files, modelConfigId, lan...
function saveGaPairsForFile (line 106) | async function saveGaPairsForFile(projectId, fileId, gaPairs, appendMode...
FILE: lib/services/images/index.js
function generateQuestionsForImage (line 29) | async function generateQuestionsForImage(projectId, imageId, options) {
function generateDatasetForImage (line 108) | async function generateDatasetForImage(projectId, imageId, question, opt...
function importImagesFromDirectories (line 194) | async function importImagesFromDirectories(projectId, directories) {
function getImageDetailWithQuestions (line 273) | async function getImageDetailWithQuestions(projectId, imageId) {
FILE: lib/services/models.js
function getActiveModel (line 10) | async function getActiveModel(projectId = null) {
function getModelById (line 39) | async function getModelById(modelConfigId) {
FILE: lib/services/multi-turn/index.js
function generateMultiTurnConversation (line 20) | async function generateMultiTurnConversation(projectId, questionId, conf...
function generateAssistantResponse (line 154) | async function generateAssistantResponse(
function generateNextUserMessage (line 196) | async function generateNextUserMessage(
function formatConversationHistory (line 238) | function formatConversationHistory(messages, roleA, roleB) {
function batchGenerateMultiTurnConversations (line 256) | async function batchGenerateMultiTurnConversations(projectId, questionId...
FILE: lib/services/questions/index.js
function randomRemoveQuestionMark (line 18) | function randomRemoveQuestionMark(questions, questionMaskRemovingProbabi...
function generateQuestionsForChunk (line 41) | async function generateQuestionsForChunk(projectId, chunkId, options) {
function extractLabels (line 116) | function extractLabels(data) {
function generateQuestionsForChunkWithGA (line 145) | async function generateQuestionsForChunkWithGA(projectId, chunkId, optio...
FILE: lib/services/questions/template.js
function generateQuestionsFromTemplate (line 19) | async function generateQuestionsFromTemplate(projectId, template) {
function generateQuestionsForTextChunks (line 67) | async function generateQuestionsForTextChunks(projectId, template, onlyN...
function generateQuestionsForImages (line 132) | async function generateQuestionsForImages(projectId, template, onlyNew =...
function generateQuestionsFromTemplateEdit (line 203) | async function generateQuestionsFromTemplateEdit(projectId, template) {
function checkTemplateGenerationAvailability (line 248) | async function checkTemplateGenerationAvailability(projectId, sourceType) {
FILE: lib/services/tasks/answer-generation.js
function processAnswerGenerationTask (line 20) | async function processAnswerGenerationTask(task) {
FILE: lib/services/tasks/data-cleaning.js
function parseTaskChunkIds (line 13) | function parseTaskChunkIds(note) {
function processDataCleaningTask (line 24) | async function processDataCleaningTask(task) {
FILE: lib/services/tasks/data-distillation.js
function processDataDistillationTask (line 18) | async function processDataDistillationTask(task) {
function buildTagTree (line 149) | async function buildTagTree({
function generateQuestionsForTags (line 276) | async function generateQuestionsForTags({
function generateDatasetsForQuestions (line 395) | async function generateDatasetsForQuestions({ taskId, projectId, model, ...
function generateMultiTurnDatasetsForQuestions (line 469) | async function generateMultiTurnDatasetsForQuestions({
function getTagDepth (line 587) | function getTagDepth(tag, allTags) {
function getTagPath (line 603) | function getTagPath(tag, allTags, projectName = '') {
FILE: lib/services/tasks/dataset-evaluation.js
function processDatasetEvaluationTask (line 20) | async function processDatasetEvaluationTask(task) {
FILE: lib/services/tasks/eval-generation.js
function processEvalGenerationTask (line 18) | async function processEvalGenerationTask(task) {
FILE: lib/services/tasks/file-processing.js
function processFileProcessingTask (line 15) | async function processFileProcessingTask(task) {
FILE: lib/services/tasks/image-dataset-generation.js
function processImageDatasetGenerationTask (line 21) | async function processImageDatasetGenerationTask(task) {
FILE: lib/services/tasks/image-question-generation.js
function processImageQuestionGenerationTask (line 18) | async function processImageQuestionGenerationTask(task) {
FILE: lib/services/tasks/index.js
function processTask (line 28) | async function processTask(taskId) {
function updateTask (line 97) | async function updateTask(taskId, data) {
function startTaskProcessor (line 121) | async function startTaskProcessor() {
FILE: lib/services/tasks/model-evaluation.js
function processModelEvaluationTask (line 18) | async function processModelEvaluationTask(task) {
function saveEvalResult (line 143) | async function saveEvalResult(projectId, taskId, evalDatasetId, result) {
FILE: lib/services/tasks/multi-turn-generation.js
function processMultiTurnGenerationTask (line 21) | async function processMultiTurnGenerationTask(task) {
FILE: lib/services/tasks/question-generation.js
function parseTaskChunkIds (line 13) | function parseTaskChunkIds(note) {
function processQuestionGenerationTask (line 24) | async function processQuestionGenerationTask(task) {
FILE: lib/services/tasks/recovery.js
function recoverPendingTasks (line 18) | async function recoverPendingTasks() {
FILE: lib/util/domain-tree.js
function handleDomainTree (line 26) | async function handleDomainTree({
FILE: lib/util/file.js
function getFileMD5 (line 7) | async function getFileMD5(filePath) {
function filterDomainTree (line 18) | function filterDomainTree(tree = []) {
function renderFunction (line 30) | async function renderFunction(options) {
FILE: lib/util/image.js
function getMimeType (line 3) | function getMimeType(filename) {
FILE: lib/util/logger.js
function log (line 4) | function log(level, ...args) {
FILE: lib/util/modelIcon.js
function getModelIcon (line 6) | function getModelIcon(modelName) {
FILE: lib/util/processInParallel.js
function processInParallel (line 8) | async function processInParallel(items, processFunction, concurrencyLimi...
FILE: lib/util/providerLogo.js
constant PROVIDER_LOGO_MAP (line 1) | const PROVIDER_LOGO_MAP = {
constant PROVIDER_PRIORITY (line 15) | const PROVIDER_PRIORITY = ['openrouter'];
function normalizeProviderId (line 17) | function normalizeProviderId(providerId = '', providerName = '') {
function getProviderLogo (line 34) | function getProviderLogo(providerId = '', providerName = '') {
function getProviderPriority (line 39) | function getProviderPriority(providerId = '', providerName = '') {
function sortProvidersByPriority (line 45) | function sortProvidersByPriority(list = [], getId = item => item?.id || ...
Condensed preview — 516 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (3,038K chars).
[
{
"path": ".dockerignore",
"chars": 199,
"preview": "node_modules\n.next\n.git\n.github\nREADME.md\nREADME.zh-CN.md\n.gitignore\n.env.local\n.env.development.local\n.env.test.local\n."
},
{
"path": ".gitattributes",
"chars": 152,
"preview": "# Ensure shell scripts always use LF line endings\n*.sh text eol=lf\ndocker-entrypoint.sh text eol=lf\n\n# Ensure Dockerfile"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 515,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: '[Bug]'\nlabels: bug\nassignees: ''\n---\n\n**注意:请务必按照此"
},
{
"path": ".github/ISSUE_TEMPLATE/feature-or-enhancement-.md",
"chars": 318,
"preview": "---\nname: 'Feature or enhancement '\nabout: Suggest an idea for this project\ntitle: '[Feature]'\nlabels: enhancement\nassig"
},
{
"path": ".github/ISSUE_TEMPLATE/question.md",
"chars": 519,
"preview": "---\nname: Question\nabout: Ask questions you want to know\ntitle: '[Question]'\nlabels: question\nassignees: ''\n---\n\n**注意:请务"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 159,
"preview": "### 变更类型- [ ] 新功能(feat)\n\n- [ ] 修复(fix)\n- [ ] 文档(docs)\n- [ ] 重构(refactor)\n\n### 变更描述- 简要说明修改内容(关联Issue:#123)\n\n### 文档更新- [ "
},
{
"path": ".github/workflows/docker-build.yml",
"chars": 1216,
"preview": "name: Build and Push Docker image on Tag\n\non:\n push:\n tags:\n - '*'\n\njobs:\n docker-image-release:\n runs-on: "
},
{
"path": ".gitignore",
"chars": 285,
"preview": "node_modules\nbuild\n.vscode\nwebsite-local.json\nai-local.json\n.next\n.DS_Store\ntsconfig.tsbuildinfo\nmock-login-callback.ts\n"
},
{
"path": ".husky/commit-msg",
"chars": 46,
"preview": "#!/usr/bin/env sh\n\nnpx commitlint --edit \"$1\"\n"
},
{
"path": ".husky/pre-commit",
"chars": 16,
"preview": "npx lint-staged\n"
},
{
"path": ".npmrc",
"chars": 153,
"preview": "# 国内用户可使用淘宝源加速 (Chinese users can use Taobao registry for faster downloads)\n# registry=https://registry.npmmirror.com\nre"
},
{
"path": ".prettierrc.js",
"chars": 255,
"preview": "module.exports = {\n semi: true,\n trailingComma: 'none',\n singleQuote: true,\n tabWidth: 2,\n useTabs: false,\n bracke"
},
{
"path": ".windsurfrules",
"chars": 2123,
"preview": "# Easy DataSet 项目架构设计\n\n## 项目概述\n\nEasy DataSet 是一个用于创建大模型微调数据集的应用程序。用户可以上传文本文件,系统会自动分割文本并生成问题,最终生成用于微调的数据集。\n\n## 技术栈\n\n- **前"
},
{
"path": "AGENTS.md",
"chars": 3444,
"preview": "# Easy Dataset Agent 指南\n\n## 项目概述\n\nEasy Dataset 是一个专为大型语言模型(LLM)微调数据集创建而设计的应用程序。它提供完整的workflow,从文档处理到数据集导出,支持多种文件格式和AI模型。"
},
{
"path": "ARCHITECTURE.md",
"chars": 2742,
"preview": "# Easy DataSet 项目架构设计\n\n## 项目概述\n\nEasy DataSet 是一个用于创建大模型微调数据集的应用程序。用户可以上传文本文件,系统会自动分割文本并生成问题,最终生成用于微调的数据集。\n\n## 技术栈\n\n- **前"
},
{
"path": "Dockerfile",
"chars": 2033,
"preview": "# 创建包含pnpm的基础镜像\nFROM node:20-alpine AS pnpm-base\nRUN npm install -g pnpm@9\n\n# 构建阶段\nFROM pnpm-base AS builder\nWORKDIR /ap"
},
{
"path": "LICENSE",
"chars": 1746,
"preview": "GNU AFFERO GENERAL PUBLIC LICENSE\nVersion 3, 19 November 2007\n\nCopyright (C) 2025 Easy Dataset Project\n\nThis program is "
},
{
"path": "README.md",
"chars": 12348,
"preview": "<div align=\"center\">\n\n\n\n<img alt=\"GitHub Repo stars\" src=\"https://img.shields.io/github/stars"
},
{
"path": "README.tr.md",
"chars": 10215,
"preview": "<div align=\"center\">\n\n\n\n<img alt=\"GitHub Repo stars\" src=\"https://img.shields.io/github/stars"
},
{
"path": "README.zh-CN.md",
"chars": 8800,
"preview": "<div align=\"center\">\n\n\n\n<img alt=\"GitHub Repo stars\" src=\"https://img.shields.io/github/stars"
},
{
"path": "app/api/check-update/route.js",
"chars": 2329,
"preview": "import { NextResponse } from 'next/server';\nimport path from 'path';\nimport fs from 'fs';\n\n// Get current version\nfuncti"
},
{
"path": "app/api/llm/fetch-models/route.js",
"chars": 2212,
"preview": "import { NextResponse } from 'next/server';\nimport axios from 'axios';\n\n// Fetch model list from provider\nexport async f"
},
{
"path": "app/api/llm/model/route.js",
"chars": 1533,
"preview": "import { NextResponse } from 'next/server';\nimport { getLlmModelsByProviderId } from '@/lib/db/llm-models';\n\n// Get LLM "
},
{
"path": "app/api/llm/ollama/models/route.js",
"chars": 853,
"preview": "import { NextResponse } from 'next/server';\n\nconst OllamaClient = require('@/lib/llm/core/providers/ollama');\n\n// Force "
},
{
"path": "app/api/llm/providers/route.js",
"chars": 525,
"preview": "import { NextResponse } from 'next/server';\nimport { getLlmProviders } from '@/lib/db/llm-providers';\nimport { sortProvi"
},
{
"path": "app/api/monitoring/logs/route.js",
"chars": 3036,
"preview": "import { NextResponse } from 'next/server';\nimport { db } from '@/lib/db';\n\nexport const dynamic = 'force-dynamic';\n\nexp"
},
{
"path": "app/api/monitoring/stats/route.js",
"chars": 5651,
"preview": "import { NextResponse } from 'next/server';\nimport { db } from '@/lib/db';\n\nexport const dynamic = 'force-dynamic';\n\nexp"
},
{
"path": "app/api/monitoring/summary/route.js",
"chars": 3699,
"preview": "import { NextResponse } from 'next/server';\nimport { db } from '@/lib/db';\n\nexport const dynamic = 'force-dynamic';\n\nexp"
},
{
"path": "app/api/projects/[projectId]/batch-add-manual-ga/route.js",
"chars": 5551,
"preview": "import { NextResponse } from 'next/server';\nimport { getUploadFileInfoById } from '@/lib/db/upload-files';\nimport { crea"
},
{
"path": "app/api/projects/[projectId]/batch-delete-files/route.js",
"chars": 5788,
"preview": "import { NextResponse } from 'next/server';\nimport { getUploadFileInfoById, delUploadFileInfoById } from '@/lib/db/uploa"
},
{
"path": "app/api/projects/[projectId]/batch-generateGA/route.js",
"chars": 3295,
"preview": "import { NextResponse } from 'next/server';\nimport { batchGenerateGaPairs } from '@/lib/services/ga/ga-pairs';\nimport { "
},
{
"path": "app/api/projects/[projectId]/blind-test-tasks/[taskId]/current/route.js",
"chars": 4675,
"preview": "import { NextResponse } from 'next/server';\nimport { db } from '@/lib/db/index';\nimport LLMClient from '@/lib/llm/core/i"
},
{
"path": "app/api/projects/[projectId]/blind-test-tasks/[taskId]/question/route.js",
"chars": 1966,
"preview": "import { NextResponse } from 'next/server';\nimport { db } from '@/lib/db/index';\n\n/**\n * Get current question info (incl"
},
{
"path": "app/api/projects/[projectId]/blind-test-tasks/[taskId]/route.js",
"chars": 4905,
"preview": "import { NextResponse } from 'next/server';\nimport { db } from '@/lib/db/index';\n\n/**\n * Get blind-test task details\n * "
},
{
"path": "app/api/projects/[projectId]/blind-test-tasks/[taskId]/stream/route.js",
"chars": 6612,
"preview": "import { NextResponse } from 'next/server';\nimport { db } from '@/lib/db/index';\nimport LLMClient from '@/lib/llm/core/i"
},
{
"path": "app/api/projects/[projectId]/blind-test-tasks/[taskId]/stream-model/route.js",
"chars": 2978,
"preview": "import { NextResponse } from 'next/server';\nimport { db } from '@/lib/db/index';\nimport LLMClient from '@/lib/llm/core/i"
},
{
"path": "app/api/projects/[projectId]/blind-test-tasks/[taskId]/vote/route.js",
"chars": 4333,
"preview": "import { NextResponse } from 'next/server';\nimport { db } from '@/lib/db/index';\n\n/**\n * Submit vote result\n * vote: 'le"
},
{
"path": "app/api/projects/[projectId]/blind-test-tasks/route.js",
"chars": 6477,
"preview": "import { NextResponse } from 'next/server';\nimport { db } from '@/lib/db/index';\n\n/**\n * Get all blind-test tasks for a "
},
{
"path": "app/api/projects/[projectId]/chunks/[chunkId]/clean/route.js",
"chars": 1120,
"preview": "import { NextResponse } from 'next/server';\nimport logger from '@/lib/util/logger';\nimport cleanService from '@/lib/serv"
},
{
"path": "app/api/projects/[projectId]/chunks/[chunkId]/eval-questions/route.js",
"chars": 1001,
"preview": "import { NextResponse } from 'next/server';\nimport { generateEvalQuestionsForChunk } from '@/lib/services/eval';\nimport "
},
{
"path": "app/api/projects/[projectId]/chunks/[chunkId]/questions/route.js",
"chars": 2263,
"preview": "import { NextResponse } from 'next/server';\nimport { getQuestionsForChunk } from '@/lib/db/questions';\nimport logger fro"
},
{
"path": "app/api/projects/[projectId]/chunks/[chunkId]/route.js",
"chars": 2249,
"preview": "import { NextResponse } from 'next/server';\nimport { deleteChunkById, getChunkById, updateChunkById } from '@/lib/db/chu"
},
{
"path": "app/api/projects/[projectId]/chunks/batch-content/route.js",
"chars": 659,
"preview": "import { getChunkContentsByNames } from '@/lib/db/chunks';\nimport { NextResponse } from 'next/server';\n\nexport async fun"
},
{
"path": "app/api/projects/[projectId]/chunks/batch-edit/route.js",
"chars": 2795,
"preview": "import { NextRequest, NextResponse } from 'next/server';\nimport { PrismaClient } from '@prisma/client';\n\nconst prisma = "
},
{
"path": "app/api/projects/[projectId]/chunks/name/route.js",
"chars": 954,
"preview": "import { NextResponse } from 'next/server';\nimport { getChunkByName } from '@/lib/db/chunks';\n\n/**\n * 根据文本块名称获取文本块\n * @p"
},
{
"path": "app/api/projects/[projectId]/chunks/route.js",
"chars": 755,
"preview": "import { NextResponse } from 'next/server';\nimport { deleteChunkById, getChunkByFileIds, getChunkById, getChunksByFileId"
},
{
"path": "app/api/projects/[projectId]/config/route.js",
"chars": 1106,
"preview": "import { NextResponse } from 'next/server';\nimport { getProject, updateProject, getTaskConfig } from '@/lib/db/projects'"
},
{
"path": "app/api/projects/[projectId]/custom-prompts/route.js",
"chars": 2945,
"preview": "import { NextResponse } from 'next/server';\nimport {\n getCustomPrompts,\n getCustomPrompt,\n saveCustomPrompt,\n delete"
},
{
"path": "app/api/projects/[projectId]/custom-split/route.js",
"chars": 3206,
"preview": "import { NextResponse } from 'next/server';\nimport { saveChunks, deleteChunksByFileId } from '@/lib/db/chunks';\nimport p"
},
{
"path": "app/api/projects/[projectId]/dataset-conversations/[conversationId]/route.js",
"chars": 3995,
"preview": "/**\n * 单个多轮对话数据集操作API\n */\n\nimport { NextResponse } from 'next/server';\nimport {\n getDatasetConversationById,\n updateDa"
},
{
"path": "app/api/projects/[projectId]/dataset-conversations/export/route.js",
"chars": 1515,
"preview": "/**\n * 多轮对话数据集导出API\n * 直接导出原始的 ShareGPT 格式数据集\n */\n\nimport { NextResponse } from 'next/server';\nimport { getAllDatasetCon"
},
{
"path": "app/api/projects/[projectId]/dataset-conversations/route.js",
"chars": 3129,
"preview": "/**\n * 多轮对话数据集管理API\n */\n\nimport { NextResponse } from 'next/server';\nimport {\n getDatasetConversationsByPagination,\n g"
},
{
"path": "app/api/projects/[projectId]/dataset-conversations/tags/route.js",
"chars": 1053,
"preview": "import { NextResponse } from 'next/server';\nimport { getAllDatasetConversations } from '@/lib/db/dataset-conversations';"
},
{
"path": "app/api/projects/[projectId]/datasets/[datasetId]/copy-to-eval/route.js",
"chars": 1995,
"preview": "import { NextResponse } from 'next/server';\nimport { db } from '@/lib/db';\n\nexport async function POST(req, { params }) "
},
{
"path": "app/api/projects/[projectId]/datasets/[datasetId]/evaluate/route.js",
"chars": 1064,
"preview": "import { NextResponse } from 'next/server';\nimport { evaluateDataset } from '@/lib/services/datasets/evaluation';\n\n/**\n "
},
{
"path": "app/api/projects/[projectId]/datasets/[datasetId]/route.js",
"chars": 2246,
"preview": "import { NextResponse } from 'next/server';\nimport { getDatasetsById, getDatasetsCounts, getNavigationItems, updateDatas"
},
{
"path": "app/api/projects/[projectId]/datasets/[datasetId]/token-count/route.js",
"chars": 1396,
"preview": "import { NextResponse } from 'next/server';\nimport { getDatasetsById } from '@/lib/db/datasets';\nimport { getEncoding } "
},
{
"path": "app/api/projects/[projectId]/datasets/batch-evaluate/route.js",
"chars": 1405,
"preview": "/**\n * 批量数据集评估任务API\n * 创建批量评估数据集质量的异步任务\n */\n\nimport { NextResponse } from 'next/server';\nimport { db } from '@/lib/db/in"
},
{
"path": "app/api/projects/[projectId]/datasets/export/route.js",
"chars": 4017,
"preview": "import { NextResponse } from 'next/server';\nimport {\n getDatasets,\n getBalancedDatasetsByTags,\n getTagsWithDatasetCou"
},
{
"path": "app/api/projects/[projectId]/datasets/generate-eval-variant/route.js",
"chars": 1593,
"preview": "import { NextResponse } from 'next/server';\nimport { getDatasetsById } from '@/lib/db/datasets';\nimport LLMClient from '"
},
{
"path": "app/api/projects/[projectId]/datasets/import/route.js",
"chars": 3318,
"preview": "import { NextResponse } from 'next/server';\nimport { createDataset } from '@/lib/db/datasets';\nimport { nanoid } from 'n"
},
{
"path": "app/api/projects/[projectId]/datasets/optimize/route.js",
"chars": 2845,
"preview": "import { NextResponse } from 'next/server';\nimport { getDatasetsById, updateDataset } from '@/lib/db/datasets';\nimport {"
},
{
"path": "app/api/projects/[projectId]/datasets/route.js",
"chars": 4699,
"preview": "import { NextResponse } from 'next/server';\nimport {\n deleteDataset,\n getDatasetsByPagination,\n getDatasetsIds,\n get"
},
{
"path": "app/api/projects/[projectId]/datasets/tags/route.js",
"chars": 621,
"preview": "import { NextResponse } from 'next/server';\nimport { getUsedCustomTags } from '@/lib/db/datasets';\n\n/**\n * 获取项目中使用过的自定义标"
},
{
"path": "app/api/projects/[projectId]/default-prompts/route.js",
"chars": 1158,
"preview": "import { NextResponse } from 'next/server';\n\n// 获取默认提示词内容\nexport async function GET(request, { params }) {\n try {\n c"
},
{
"path": "app/api/projects/[projectId]/distill/questions/by-tag/route.js",
"chars": 1723,
"preview": "import { NextResponse } from 'next/server';\nimport { db } from '@/lib/db';\n\n/**\n * 根据标签ID获取问题列表\n */\nexport async functio"
},
{
"path": "app/api/projects/[projectId]/distill/questions/route.js",
"chars": 2844,
"preview": "import { NextResponse } from 'next/server';\nimport { distillQuestionsPrompt } from '@/lib/llm/prompts/distillQuestions';"
},
{
"path": "app/api/projects/[projectId]/distill/tags/[tagId]/route.js",
"chars": 1491,
"preview": "import { NextResponse } from 'next/server';\nimport { db } from '@/lib/db';\n\n/**\n * 更新标签接口\n */\nexport async function PUT("
},
{
"path": "app/api/projects/[projectId]/distill/tags/all/route.js",
"chars": 655,
"preview": "import { NextResponse } from 'next/server';\nimport { db } from '@/lib/db';\n\n/**\n * 获取项目的所有蒸馏标签\n */\nexport async function"
},
{
"path": "app/api/projects/[projectId]/distill/tags/route.js",
"chars": 2351,
"preview": "import { NextResponse } from 'next/server';\nimport { distillTagsPrompt } from '@/lib/llm/prompts/distillTags';\nimport { "
},
{
"path": "app/api/projects/[projectId]/eval-datasets/[evalId]/route.js",
"chars": 3132,
"preview": "import { NextResponse } from 'next/server';\nimport { getEvalQuestionById, updateEvalQuestion, deleteEvalQuestion } from "
},
{
"path": "app/api/projects/[projectId]/eval-datasets/count/route.js",
"chars": 1936,
"preview": "import { NextResponse } from 'next/server';\nimport { db } from '@/lib/db';\nimport { buildEvalQuestionWhere } from '@/lib"
},
{
"path": "app/api/projects/[projectId]/eval-datasets/export/route.js",
"chars": 6812,
"preview": "import { NextResponse } from 'next/server';\nimport { db } from '@/lib/db/index';\nimport { buildEvalQuestionWhere } from "
},
{
"path": "app/api/projects/[projectId]/eval-datasets/import/route.js",
"chars": 11276,
"preview": "import { NextResponse } from 'next/server';\nimport { db } from '@/lib/db/index';\nimport { nanoid } from 'nanoid';\nimport"
},
{
"path": "app/api/projects/[projectId]/eval-datasets/route.js",
"chars": 5712,
"preview": "import { NextResponse } from 'next/server';\nimport { getEvalQuestionsWithPagination, getEvalQuestionsStats, deleteEvalQu"
},
{
"path": "app/api/projects/[projectId]/eval-datasets/sample/route.js",
"chars": 3173,
"preview": "import { NextResponse } from 'next/server';\nimport { db } from '@/lib/db';\nimport { buildEvalQuestionWhere } from '@/lib"
},
{
"path": "app/api/projects/[projectId]/eval-datasets/tags/route.js",
"chars": 1002,
"preview": "import { NextResponse } from 'next/server';\nimport { db } from '@/lib/db/index';\n\n/**\n * Get all evaluation dataset tags"
},
{
"path": "app/api/projects/[projectId]/eval-tasks/[taskId]/route.js",
"chars": 4774,
"preview": "import { NextResponse } from 'next/server';\nimport { db } from '@/lib/db/index';\nimport { getEvalResultsByTaskId, getEva"
},
{
"path": "app/api/projects/[projectId]/eval-tasks/route.js",
"chars": 6148,
"preview": "import { NextResponse } from 'next/server';\nimport { db } from '@/lib/db/index';\nimport { processTask } from '@/lib/serv"
},
{
"path": "app/api/projects/[projectId]/files/[fileId]/ga-pairs/route.js",
"chars": 10569,
"preview": "import { NextResponse } from 'next/server';\nimport { getGaPairsByFileId, toggleGaPairActive, saveGaPairs, createGaPairs "
},
{
"path": "app/api/projects/[projectId]/files/route.js",
"chars": 7758,
"preview": "import { NextResponse } from 'next/server';\nimport { getProject } from '@/lib/db/projects';\nimport path from 'path';\nimp"
},
{
"path": "app/api/projects/[projectId]/generate-questions/route.js",
"chars": 3875,
"preview": "import { NextResponse } from 'next/server';\nimport { getProjectChunks } from '@/lib/file/text-splitter';\nimport { getTas"
},
{
"path": "app/api/projects/[projectId]/huggingface/upload/route.js",
"chars": 8995,
"preview": "import { NextResponse } from 'next/server';\nimport { getProject } from '@/lib/db/projects';\nimport { getDatasets } from "
},
{
"path": "app/api/projects/[projectId]/image-datasets/[datasetId]/route.js",
"chars": 3562,
"preview": "import { NextResponse } from 'next/server';\nimport { getImageDatasetById, updateImageDataset, deleteImageDataset } from "
},
{
"path": "app/api/projects/[projectId]/image-datasets/export/route.js",
"chars": 825,
"preview": "import { NextResponse } from 'next/server';\nimport { getImageDatasetsForExport } from '@/lib/db/imageDatasets';\n\n/**\n * "
},
{
"path": "app/api/projects/[projectId]/image-datasets/export-zip/route.js",
"chars": 2378,
"preview": "import { NextResponse } from 'next/server';\nimport { getImageDatasetsForExport } from '@/lib/db/imageDatasets';\nimport a"
},
{
"path": "app/api/projects/[projectId]/image-datasets/route.js",
"chars": 2328,
"preview": "import { NextResponse } from 'next/server';\nimport { getImageDatasetsByProject } from '@/lib/db/imageDatasets';\nimport {"
},
{
"path": "app/api/projects/[projectId]/image-datasets/tags/route.js",
"chars": 965,
"preview": "import { NextResponse } from 'next/server';\nimport { getImageDatasetsTagsByProject } from '@/lib/db/imageDatasets';\n\n// "
},
{
"path": "app/api/projects/[projectId]/images/[imageId]/route.js",
"chars": 878,
"preview": "import { NextResponse } from 'next/server';\nimport { getImageDetailWithQuestions } from '@/lib/services/images';\n\n// 根据图"
},
{
"path": "app/api/projects/[projectId]/images/annotations/route.js",
"chars": 2729,
"preview": "import { NextResponse } from 'next/server';\nimport { PrismaClient } from '@prisma/client';\nimport { getImageById, getIma"
},
{
"path": "app/api/projects/[projectId]/images/datasets/route.js",
"chars": 1215,
"preview": "import { NextResponse } from 'next/server';\nimport { getImageByName } from '@/lib/db/images';\nimport imageService from '"
},
{
"path": "app/api/projects/[projectId]/images/next-unanswered/route.js",
"chars": 1047,
"preview": "import { NextResponse } from 'next/server';\nimport { PrismaClient } from '@prisma/client';\nimport { getImageDetailWithQu"
},
{
"path": "app/api/projects/[projectId]/images/pdf-convert/route.js",
"chars": 3060,
"preview": "import { NextResponse } from 'next/server';\nimport { getProjectPath } from '@/lib/db/base';\nimport { importImagesFromDir"
},
{
"path": "app/api/projects/[projectId]/images/questions/route.js",
"chars": 1145,
"preview": "import { NextResponse } from 'next/server';\nimport { getImageByName } from '@/lib/db/images';\nimport imageService from '"
},
{
"path": "app/api/projects/[projectId]/images/route.js",
"chars": 2781,
"preview": "import { NextResponse } from 'next/server';\nimport { getImages, deleteImage, getImageDetail } from '@/lib/db/images';\nim"
},
{
"path": "app/api/projects/[projectId]/images/zip-import/route.js",
"chars": 3803,
"preview": "import { NextResponse } from 'next/server';\nimport { getProjectPath } from '@/lib/db/base';\nimport { importImagesFromDir"
},
{
"path": "app/api/projects/[projectId]/llamaFactory/checkConfig/route.js",
"chars": 847,
"preview": "import { NextResponse } from 'next/server';\nimport path from 'path';\nimport fs from 'fs';\nimport { getProjectRoot } from"
},
{
"path": "app/api/projects/[projectId]/llamaFactory/generate/route.js",
"chars": 4487,
"preview": "import { NextResponse } from 'next/server';\nimport path from 'path';\nimport fs from 'fs';\nimport { getProjectRoot } from"
},
{
"path": "app/api/projects/[projectId]/model-config/[modelConfigId]/route.js",
"chars": 646,
"preview": "import { NextResponse } from 'next/server';\nimport { deleteModelConfigById } from '@/lib/db/model-config';\n\n// 删除模型配置\nex"
},
{
"path": "app/api/projects/[projectId]/model-config/route.js",
"chars": 3839,
"preview": "import { NextResponse } from 'next/server';\nimport { createInitModelConfig, getModelConfigByProjectId, saveModelConfig }"
},
{
"path": "app/api/projects/[projectId]/models/[modelId]/route.js",
"chars": 5105,
"preview": "import { NextResponse } from 'next/server';\nimport { getProjectRoot } from '@/lib/db/base';\nimport path from 'path';\nimp"
},
{
"path": "app/api/projects/[projectId]/models/route.js",
"chars": 2594,
"preview": "import { NextResponse } from 'next/server';\nimport path from 'path';\nimport fs from 'fs/promises';\nimport { getProjectRo"
},
{
"path": "app/api/projects/[projectId]/playground/chat/route.js",
"chars": 3007,
"preview": "import { NextResponse } from 'next/server';\nimport LLMClient from '@/lib/llm/core/index';\nimport { getModelConfigById } "
},
{
"path": "app/api/projects/[projectId]/playground/chat/stream/route.js",
"chars": 2549,
"preview": "import { NextResponse } from 'next/server';\nimport LLMClient from '@/lib/llm/core/index';\nimport { getModelConfigById } "
},
{
"path": "app/api/projects/[projectId]/preview/[fileId]/route.js",
"chars": 1263,
"preview": "import { NextResponse } from 'next/server';\nimport fs from 'fs';\nimport path from 'path';\nimport { getProjectRoot } from"
},
{
"path": "app/api/projects/[projectId]/questions/[questionId]/route.js",
"chars": 759,
"preview": "import { NextResponse } from 'next/server';\nimport { deleteQuestion } from '@/lib/db/questions';\n\n// 删除单个问题\nexport async"
},
{
"path": "app/api/projects/[projectId]/questions/batch-delete/route.js",
"chars": 685,
"preview": "import { NextResponse } from 'next/server';\nimport { batchDeleteQuestions } from '@/lib/db/questions';\n\n// 批量删除问题\nexport"
},
{
"path": "app/api/projects/[projectId]/questions/export/route.js",
"chars": 2240,
"preview": "import { NextResponse } from 'next/server';\n\nexport async function POST(request, { params }) {\n try {\n const { proje"
},
{
"path": "app/api/projects/[projectId]/questions/route.js",
"chars": 3245,
"preview": "import { NextResponse } from 'next/server';\nimport {\n getAllQuestionsByProjectId,\n getQuestions,\n getQuestionsIds,\n "
},
{
"path": "app/api/projects/[projectId]/questions/templates/[templateId]/route.js",
"chars": 3396,
"preview": "import { NextResponse } from 'next/server';\nimport templateDb from '@/lib/db/questionTemplates';\nimport { generateQuesti"
},
{
"path": "app/api/projects/[projectId]/questions/templates/route.js",
"chars": 3625,
"preview": "import { NextResponse } from 'next/server';\nimport templateDb from '@/lib/db/questionTemplates';\nimport { generateQuesti"
},
{
"path": "app/api/projects/[projectId]/questions/tree/route.js",
"chars": 1603,
"preview": "import { NextResponse } from 'next/server';\nimport { getQuestionsForTree, getQuestionsByTag } from '@/lib/db/questions';"
},
{
"path": "app/api/projects/[projectId]/route.js",
"chars": 2028,
"preview": "// 获取项目详情\nimport { deleteProject, getProject, updateProject, getTaskConfig } from '@/lib/db/projects';\n\nexport async fun"
},
{
"path": "app/api/projects/[projectId]/split/route.js",
"chars": 2650,
"preview": "import { NextResponse } from 'next/server';\nimport { splitProjectFile, getProjectChunks } from '@/lib/file/text-splitter"
},
{
"path": "app/api/projects/[projectId]/tags/route.js",
"chars": 2983,
"preview": "import { NextResponse } from 'next/server';\nimport { getTags, createTag, updateTag, deleteTag } from '@/lib/db/tags';\nim"
},
{
"path": "app/api/projects/[projectId]/tasks/[taskId]/route.js",
"chars": 3237,
"preview": "import { NextResponse } from 'next/server';\nimport { PrismaClient } from '@prisma/client';\n\nconst prisma = new PrismaCli"
},
{
"path": "app/api/projects/[projectId]/tasks/list/route.js",
"chars": 1382,
"preview": "import { NextResponse } from 'next/server';\nimport { PrismaClient } from '@prisma/client';\n\nconst prisma = new PrismaCli"
},
{
"path": "app/api/projects/[projectId]/tasks/route.js",
"chars": 4462,
"preview": "import { NextResponse } from 'next/server';\nimport path from 'path';\nimport fs from 'fs/promises';\nimport { getProjectRo"
},
{
"path": "app/api/projects/delete-directory/route.js",
"chars": 1390,
"preview": "import { getProjectRoot } from '@/lib/db/base';\nimport { NextResponse } from 'next/server';\nimport path from 'path';\nimp"
},
{
"path": "app/api/projects/migrate/route.js",
"chars": 3872,
"preview": "import { NextResponse } from 'next/server';\nimport { main } from '@/lib/db/fileToDb';\n\n// Store migration task states\nco"
},
{
"path": "app/api/projects/open-directory/route.js",
"chars": 1469,
"preview": "import { getProjectRoot } from '@/lib/db/base';\nimport { NextResponse } from 'next/server';\nimport path from 'path';\nimp"
},
{
"path": "app/api/projects/route.js",
"chars": 1402,
"preview": "import { createProject, getProjects, isExistByName } from '@/lib/db/projects';\nimport { createInitModelConfig, getModelC"
},
{
"path": "app/api/projects/unmigrated/route.js",
"chars": 2096,
"preview": "import { getProjectRoot } from '@/lib/db/base';\nimport { db } from '@/lib/db/index';\nimport fs from 'fs';\nimport path fr"
},
{
"path": "app/api/update/route.js",
"chars": 1857,
"preview": "import { NextResponse } from 'next/server';\nimport { exec } from 'child_process';\nimport path from 'path';\nimport fs fro"
},
{
"path": "app/dataset-square/page.js",
"chars": 6545,
"preview": "'use client';\n\nimport { useState, useEffect } from 'react';\nimport { Box, Container, Typography, Paper, useTheme, alpha "
},
{
"path": "app/globals.css",
"chars": 1923,
"preview": "* {\n box-sizing: border-box;\n padding: 0;\n margin: 0;\n}\n\nhtml,\nbody {\n width: 100%;\n max-width: 100%;\n overflow-x:"
},
{
"path": "app/layout.js",
"chars": 774,
"preview": "import './globals.css';\nimport ThemeRegistry from '@/components/ThemeRegistry';\nimport I18nProvider from '@/components/I"
},
{
"path": "app/monitoring/components/Charts.js",
"chars": 7052,
"preview": "import React from 'react';\nimport { Card, CardContent, Typography, Box, useTheme } from '@mui/material';\nimport { useTra"
},
{
"path": "app/monitoring/components/StatsCards.js",
"chars": 4730,
"preview": "import React from 'react';\nimport { Box, Card, CardContent, Grid, Typography, Stack, useTheme, alpha } from '@mui/materi"
},
{
"path": "app/monitoring/components/UsageTable.js",
"chars": 7875,
"preview": "import React, { useState } from 'react';\nimport {\n Box,\n Card,\n CardContent,\n Typography,\n Table,\n TableBody,\n Ta"
},
{
"path": "app/monitoring/hooks/useMonitoringData.js",
"chars": 3117,
"preview": "import { useState, useEffect, useCallback } from 'react';\nimport axios from 'axios';\nimport { toast } from 'sonner';\nimp"
},
{
"path": "app/monitoring/page.js",
"chars": 8272,
"preview": "'use client';\n\nimport React, { useState, useEffect } from 'react';\nimport {\n Box,\n Container,\n Stack,\n Button,\n For"
},
{
"path": "app/page.js",
"chars": 4517,
"preview": "'use client';\n\nimport { useState, useEffect } from 'react';\nimport { Container, Box, Typography, CircularProgress, Stack"
},
{
"path": "app/projects/[projectId]/blind-test-tasks/[taskId]/page.js",
"chars": 4450,
"preview": "'use client';\n\nimport { useState } from 'react';\nimport { useParams, useRouter } from 'next/navigation';\nimport {\n Box,"
},
{
"path": "app/projects/[projectId]/blind-test-tasks/components/BlindTestHeader.js",
"chars": 2486,
"preview": "import { Box, Typography, IconButton, Chip, Button } from '@mui/material';\nimport ArrowBackIcon from '@mui/icons-materia"
},
{
"path": "app/projects/[projectId]/blind-test-tasks/components/BlindTestInProgress.js",
"chars": 15028,
"preview": "import { useState, useRef, useEffect } from 'react';\nimport {\n Box,\n Paper,\n Typography,\n Button,\n LinearProgress,\n"
},
{
"path": "app/projects/[projectId]/blind-test-tasks/components/BlindTestTaskCard.js",
"chars": 11607,
"preview": "'use client';\n\nimport {\n Box,\n Card,\n CardContent,\n Typography,\n Chip,\n IconButton,\n Menu,\n MenuItem,\n LinearPr"
},
{
"path": "app/projects/[projectId]/blind-test-tasks/components/CreateBlindTestDialog.js",
"chars": 17337,
"preview": "'use client';\n\nimport { useState, useEffect, useCallback } from 'react';\nimport {\n Dialog,\n DialogTitle,\n DialogConte"
},
{
"path": "app/projects/[projectId]/blind-test-tasks/components/ResultDetailList.js",
"chars": 10909,
"preview": "import { Box, Paper, Typography, Chip, Collapse, IconButton, Avatar, Divider, Grid } from '@mui/material';\nimport ReactM"
},
{
"path": "app/projects/[projectId]/blind-test-tasks/components/ResultSummary.js",
"chars": 9003,
"preview": "import { Box, Paper, Typography, Card, CardContent, Chip, Grid, Avatar } from '@mui/material';\nimport { useTheme, alpha "
},
{
"path": "app/projects/[projectId]/blind-test-tasks/hooks/useBlindTestDetail.js",
"chars": 12483,
"preview": "'use client';\n\nimport { useState, useCallback, useEffect, useRef } from 'react';\n\n/**\n * 盲测任务详情和盲测过程管理 Hook\n */\nexport d"
},
{
"path": "app/projects/[projectId]/blind-test-tasks/hooks/useBlindTestTasks.js",
"chars": 3448,
"preview": "'use client';\n\nimport { useState, useCallback, useEffect } from 'react';\n\n/**\n * 盲测任务列表管理 Hook\n */\nexport default functi"
},
{
"path": "app/projects/[projectId]/blind-test-tasks/page.js",
"chars": 6966,
"preview": "'use client';\n\nimport { useState } from 'react';\nimport { useParams, useRouter } from 'next/navigation';\nimport {\n Box,"
},
{
"path": "app/projects/[projectId]/datasets/[datasetId]/page.js",
"chars": 6171,
"preview": "'use client';\n\nimport { Container, Box, Typography, Alert, Snackbar, Paper } from '@mui/material';\nimport { useEffect } "
},
{
"path": "app/projects/[projectId]/datasets/[datasetId]/useDatasetDetails.js",
"chars": 12167,
"preview": "'use client';\n\nimport { useState, useEffect, useRef, useCallback } from 'react';\nimport { useRouter } from 'next/navigat"
},
{
"path": "app/projects/[projectId]/datasets/components/ActionBar.js",
"chars": 1142,
"preview": "'use client';\n\nimport { Box, Button } from '@mui/material';\nimport AssessmentIcon from '@mui/icons-material/Assessment';"
},
{
"path": "app/projects/[projectId]/datasets/components/DatasetList.js",
"chars": 15299,
"preview": "'use client';\n\nimport {\n Box,\n Typography,\n IconButton,\n Table,\n TableBody,\n TableCell,\n TableContainer,\n TableH"
},
{
"path": "app/projects/[projectId]/datasets/components/DeleteConfirmDialog.js",
"chars": 3231,
"preview": "'use client';\n\nimport {\n Dialog,\n DialogTitle,\n DialogContent,\n DialogActions,\n Typography,\n Paper,\n Box,\n Linea"
},
{
"path": "app/projects/[projectId]/datasets/components/FilterDialog.js",
"chars": 6029,
"preview": "'use client';\n\nimport {\n Dialog,\n DialogTitle,\n DialogContent,\n DialogActions,\n Box,\n Typography,\n Select,\n Menu"
},
{
"path": "app/projects/[projectId]/datasets/components/SearchBar.js",
"chars": 2257,
"preview": "'use client';\n\nimport { Box, Paper, IconButton, InputBase, Select, MenuItem, Button, Badge } from '@mui/material';\nimpor"
},
{
"path": "app/projects/[projectId]/datasets/hooks/useDatasetEvaluation.js",
"chars": 3854,
"preview": "'use client';\n\nimport { useState } from 'react';\nimport { useRouter } from 'next/navigation';\nimport { useTranslation } "
},
{
"path": "app/projects/[projectId]/datasets/hooks/useDatasetExport.js",
"chars": 15506,
"preview": "'use client';\n\nimport { useTranslation } from 'react-i18next';\nimport { toast } from 'sonner';\nimport axios from 'axios'"
},
{
"path": "app/projects/[projectId]/datasets/hooks/useDatasetFilters.js",
"chars": 4581,
"preview": "'use client';\n\nimport { useState, useEffect } from 'react';\n\n/**\n * 数据集筛选条件持久化 Hook\n * 负责筛选条件的保存、恢复和管理\n * @param {string"
},
{
"path": "app/projects/[projectId]/datasets/page.js",
"chars": 16920,
"preview": "'use client';\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { Container, Box, Typography, Button, Ca"
},
{
"path": "app/projects/[projectId]/distill/autoDistillService.js",
"chars": 24957,
"preview": "'use client';\n\nimport axios from 'axios';\n\n/**\n * 自动蒸馏服务\n */\nclass AutoDistillService {\n /**\n * 执行自动蒸馆任务\n * @param "
},
{
"path": "app/projects/[projectId]/distill/page.js",
"chars": 16357,
"preview": "'use client';\n\nimport React, { useState, useEffect, useRef } from 'react';\nimport { useTranslation } from 'react-i18next"
},
{
"path": "app/projects/[projectId]/eval-datasets/[evalId]/page.js",
"chars": 13476,
"preview": "'use client';\nimport { useState, useEffect } from 'react';\nimport { useParams } from 'next/navigation';\nimport {\n Box,\n"
},
{
"path": "app/projects/[projectId]/eval-datasets/[evalId]/useEvalDatasetDetails.js",
"chars": 4035,
"preview": "'use client';\n\nimport { useState, useEffect, useCallback, useRef } from 'react';\nimport { useRouter } from 'next/navigat"
},
{
"path": "app/projects/[projectId]/eval-datasets/components/BuiltinDatasetDialog.js",
"chars": 11539,
"preview": "'use client';\n\nimport { useState, useMemo } from 'react';\nimport {\n Dialog,\n DialogContent,\n DialogActions,\n Button,"
},
{
"path": "app/projects/[projectId]/eval-datasets/components/EvalDatasetCard.js",
"chars": 11462,
"preview": "'use client';\n\nimport { Card, CardContent, Box, Typography, Chip, Checkbox, IconButton, Tooltip, Divider } from '@mui/ma"
},
{
"path": "app/projects/[projectId]/eval-datasets/components/EvalDatasetHeader.js",
"chars": 1697,
"preview": "'use client';\n\nimport { Box, Button, Divider, Typography, IconButton, Paper, Tooltip } from '@mui/material';\nimport Navi"
},
{
"path": "app/projects/[projectId]/eval-datasets/components/EvalDatasetList.js",
"chars": 5256,
"preview": "'use client';\n\nimport {\n Table,\n TableBody,\n TableCell,\n TableContainer,\n TableHead,\n TableRow,\n Paper,\n Checkbo"
},
{
"path": "app/projects/[projectId]/eval-datasets/components/EvalEditableField.js",
"chars": 3798,
"preview": "'use client';\n\nimport { useState } from 'react';\nimport { Box, Typography, Button, TextField, IconButton, Paper } from '"
},
{
"path": "app/projects/[projectId]/eval-datasets/components/EvalToolbar.js",
"chars": 8329,
"preview": "'use client';\n\nimport {\n Box,\n IconButton,\n ToggleButton,\n Tooltip,\n Divider,\n Autocomplete,\n TextField,\n Menu,\n"
},
{
"path": "app/projects/[projectId]/eval-datasets/components/EvalToolbar.styles.js",
"chars": 4663,
"preview": "import { styled, alpha } from '@mui/material/styles';\nimport { Box, Paper, Button, ToggleButton, ToggleButtonGroup, Inpu"
},
{
"path": "app/projects/[projectId]/eval-datasets/components/ExportEvalDialog.js",
"chars": 8912,
"preview": "'use client';\n\nimport {\n Dialog,\n DialogTitle,\n DialogContent,\n DialogActions,\n Button,\n Box,\n Typography,\n Text"
},
{
"path": "app/projects/[projectId]/eval-datasets/components/ImportDialog.js",
"chars": 10394,
"preview": "'use client';\n\nimport { useState, useRef } from 'react';\nimport {\n Dialog,\n DialogContent,\n DialogActions,\n Button,\n"
},
{
"path": "app/projects/[projectId]/eval-datasets/components/ImportDialog.styles.js",
"chars": 3946,
"preview": "import { styled, alpha } from '@mui/material/styles';\nimport { Box, Paper, DialogTitle as MuiDialogTitle, RadioGroup, Fo"
},
{
"path": "app/projects/[projectId]/eval-datasets/constants.js",
"chars": 17141,
"preview": "export const QUESTION_TYPES = [\n { value: 'true_false', label: 'eval.questionTypes.true_false', labelZh: '判断题' },\n { v"
},
{
"path": "app/projects/[projectId]/eval-datasets/hooks/useEvalDatasets.js",
"chars": 5453,
"preview": "'use client';\n\nimport { useState, useCallback, useEffect, useRef } from 'react';\n\n/**\n * Eval datasets list hook\n * @par"
},
{
"path": "app/projects/[projectId]/eval-datasets/hooks/useExportEvalDatasets.js",
"chars": 4547,
"preview": "'use client';\n\nimport { useState, useCallback, useEffect } from 'react';\n\n/**\n * 评估数据集导出 Hook\n * 管理导出对话框状态、筛选条件和导出逻辑\n */"
},
{
"path": "app/projects/[projectId]/eval-datasets/page.js",
"chars": 9236,
"preview": "'use client';\n\nimport { useState } from 'react';\nimport { useParams, useRouter } from 'next/navigation';\nimport {\n Box,"
},
{
"path": "app/projects/[projectId]/eval-tasks/[taskId]/components/EvalHeader.js",
"chars": 5470,
"preview": "'use client';\n\nimport { Box, Paper, Typography, Chip, Grid, Divider } from '@mui/material';\nimport SmartToyIcon from '@m"
},
{
"path": "app/projects/[projectId]/eval-tasks/[taskId]/components/EvalStats.js",
"chars": 3481,
"preview": "'use client';\n\nimport { Box, Grid, Typography, LinearProgress } from '@mui/material';\nimport { detailStyles } from '../d"
},
{
"path": "app/projects/[projectId]/eval-tasks/[taskId]/components/QuestionCard.js",
"chars": 12030,
"preview": "'use client';\n\nimport { useState, useRef, useEffect } from 'react';\nimport { Box, Typography, Chip, Paper, Button } from"
},
{
"path": "app/projects/[projectId]/eval-tasks/[taskId]/detailStyles.js",
"chars": 5907,
"preview": "export const detailStyles = {\n // 页面背景\n pageContainer: {\n py: 4,\n minHeight: '100vh',\n bgcolor: '#f5f7fa'\n }"
},
{
"path": "app/projects/[projectId]/eval-tasks/[taskId]/page.js",
"chars": 6709,
"preview": "'use client';\n\nimport { useParams, useRouter } from 'next/navigation';\nimport {\n Container,\n Box,\n Button,\n Circular"
},
{
"path": "app/projects/[projectId]/eval-tasks/components/CreateEvalTaskDialog.js",
"chars": 9603,
"preview": "'use client';\n\nimport { useState } from 'react';\nimport {\n Dialog,\n DialogTitle,\n DialogContent,\n DialogActions,\n B"
},
{
"path": "app/projects/[projectId]/eval-tasks/components/EvalTaskCard.js",
"chars": 6525,
"preview": "'use client';\n\nimport { useState } from 'react';\nimport {\n Card,\n CardContent,\n Box,\n Typography,\n Chip,\n IconButt"
},
{
"path": "app/projects/[projectId]/eval-tasks/components/ModelSelector.js",
"chars": 2642,
"preview": "'use client';\n\nimport {\n Box,\n Typography,\n Checkbox,\n FormHelperText,\n FormControl,\n InputLabel,\n Select,\n Menu"
},
{
"path": "app/projects/[projectId]/eval-tasks/components/QuestionFilter.js",
"chars": 5395,
"preview": "'use client';\n\nimport {\n Box,\n Typography,\n TextField,\n FormControl,\n InputLabel,\n Select,\n MenuItem,\n Chip,\n O"
},
{
"path": "app/projects/[projectId]/eval-tasks/components/ScoreAnchorsForm.js",
"chars": 4377,
"preview": "'use client';\n\nimport { useState, useEffect } from 'react';\nimport {\n Box,\n Typography,\n TextField,\n Accordion,\n Ac"
},
{
"path": "app/projects/[projectId]/eval-tasks/hooks/useEvalTaskDetail.js",
"chars": 2392,
"preview": "'use client';\n\nimport { useState, useEffect, useCallback } from 'react';\n\n/**\n * 评估任务详情 Hook\n */\nexport default function"
},
{
"path": "app/projects/[projectId]/eval-tasks/hooks/useEvalTaskForm.js",
"chars": 5540,
"preview": "'use client';\n\nimport { useState, useEffect } from 'react';\nimport { getDefaultScoreAnchors } from '@/lib/llm/prompts/ll"
},
{
"path": "app/projects/[projectId]/eval-tasks/hooks/useEvalTasks.js",
"chars": 3661,
"preview": "'use client';\n\nimport { useState, useEffect, useCallback } from 'react';\n\n/**\n * 评估任务列表 Hook\n */\nexport default function"
},
{
"path": "app/projects/[projectId]/eval-tasks/page.js",
"chars": 6002,
"preview": "'use client';\n\nimport { useState } from 'react';\nimport { useParams, useRouter } from 'next/navigation';\nimport {\n Cont"
},
{
"path": "app/projects/[projectId]/eval-tasks/styles.js",
"chars": 4503,
"preview": "/**\n * 评估任务页面样式\n */\n\nexport const evalTasksStyles = {\n // 页面容器\n pageContainer: {\n py: 3,\n minHeight: '100vh'\n }"
},
{
"path": "app/projects/[projectId]/image-datasets/[datasetId]/page.js",
"chars": 2361,
"preview": "'use client';\n\nimport { Container, Box, CircularProgress, Alert } from '@mui/material';\nimport { useParams } from 'next/"
},
{
"path": "app/projects/[projectId]/image-datasets/components/DatasetContent.js",
"chars": 4446,
"preview": "'use client';\n\nimport { useState, useEffect } from 'react';\nimport { Box, Paper, Typography, Button } from '@mui/materia"
},
{
"path": "app/projects/[projectId]/image-datasets/components/DatasetSidebar.js",
"chars": 612,
"preview": "'use client';\n\nimport { Box } from '@mui/material';\nimport MetadataInfo from './MetadataInfo';\nimport MetadataEditor fro"
},
{
"path": "app/projects/[projectId]/image-datasets/components/EmptyState.js",
"chars": 847,
"preview": "'use client';\n\nimport { Box, Typography } from '@mui/material';\nimport ImageSearchIcon from '@mui/icons-material/ImageSe"
},
{
"path": "app/projects/[projectId]/image-datasets/components/ExportImageDatasetDialog.js",
"chars": 4075,
"preview": "'use client';\n\nimport React, { useState } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport {\n Dialo"
},
{
"path": "app/projects/[projectId]/image-datasets/components/ImageDatasetCard.js",
"chars": 6471,
"preview": "'use client';\n\nimport { Card, CardMedia, Box, Chip, Typography, Tooltip, IconButton } from '@mui/material';\nimport Visib"
},
{
"path": "app/projects/[projectId]/image-datasets/components/ImageDatasetFilterDialog.js",
"chars": 2512,
"preview": "'use client';\n\nimport {\n Dialog,\n DialogTitle,\n DialogContent,\n DialogActions,\n Box,\n Typography,\n Select,\n Menu"
},
{
"path": "app/projects/[projectId]/image-datasets/components/ImageDatasetFilters.js",
"chars": 1420,
"preview": "'use client';\n\nimport { Box, Paper, IconButton, InputBase, Button, Badge } from '@mui/material';\nimport SearchIcon from "
},
{
"path": "app/projects/[projectId]/image-datasets/components/ImageDatasetHeader.js",
"chars": 2852,
"preview": "'use client';\n\nimport { Box, Button, Divider, Typography, IconButton, CircularProgress, Paper } from '@mui/material';\nim"
},
{
"path": "app/projects/[projectId]/image-datasets/components/MetadataEditor.js",
"chars": 4338,
"preview": "'use client';\n\nimport { useState, useEffect } from 'react';\nimport { Box, Typography, Divider, Paper } from '@mui/materi"
},
{
"path": "app/projects/[projectId]/image-datasets/components/MetadataInfo.js",
"chars": 4504,
"preview": "'use client';\n\nimport { Box, Typography, Chip, alpha, Divider } from '@mui/material';\nimport { useTranslation } from 're"
},
{
"path": "app/projects/[projectId]/image-datasets/hooks/useImageDatasetDetail.js",
"chars": 2387,
"preview": "import { useState, useEffect, useCallback } from 'react';\nimport axios from 'axios';\nimport { toast } from 'sonner';\nimp"
},
{
"path": "app/projects/[projectId]/image-datasets/hooks/useImageDatasetDetails.js",
"chars": 5342,
"preview": "'use client';\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { useRouter } from 'next/navigation';\nim"
}
]
// ... and 316 more files (download for full content)
About this extraction
This page contains the full source code of the ConardLi/easy-dataset GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 516 files (2.7 MB), approximately 746.3k tokens, and a symbol index with 937 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.