diff --git a/LOG.md b/LOG.md
index 5e0b362..e97c72c 100644
--- a/LOG.md
+++ b/LOG.md
@@ -1,5 +1,13 @@
# Release Log
+## 0.1.3
+
+- Added a core-code blueprint step before generated files are cached for export.
+- Reworked code generation guidance to avoid fixed full-project templates and focus on the paper's minimal computational contribution.
+- Added local validation so generated code files must exactly match the blueprint file list.
+- Added blueprint metadata to cached code bundles and export README content.
+- Added MiniMax and GLM model providers alongside DeepSeek and Jiekou.
+
## 0.1.2
- Added Jiekou as a model provider alongside DeepSeek.
diff --git a/README.md b/README.md
index 68668ce..7e02e0c 100644
--- a/README.md
+++ b/README.md
@@ -2,28 +2,45 @@
English | [简体中文](README.zh-CN.md)
-Paper2CoreCode is a desktop tool that turns research papers into readable summaries and exportable core code.
+Paper2CoreCode is a desktop tool that turns research papers into readable summaries and exportable minimal core code.
-It is designed for researchers, engineers, and students who want to quickly understand a paper and, when possible, obtain a componentized implementation scaffold.
+It is designed for researchers, engineers, and students who want to quickly understand a paper and, when possible, obtain a small implementation of the paper's core computational contribution.
## What It Does ✨
- 📄 Analyze academic paper PDFs.
-- 🧠 Generate structured paper summaries with DeepSeek / Jiekou.
+- 🧠 Generate structured paper summaries with DeepSeek / Jiekou / MiniMax / GLM.
- 🧮 Render Markdown, tables, and LaTeX formulas clearly.
- 💻 Decide whether the paper needs core code.
+- 🧭 Plan a minimal core-code blueprint before writing files.
- 📦 Export generated core code as a local project folder.
- 🌐 Switch between Chinese and English UI/output.
- 🖥️ Run as a local Electron desktop app.
## Core Workflow 🚀
-1. Select a provider (DeepSeek / Jiekou) and enter your API key in the sidebar.
+1. Select a provider (DeepSeek / Jiekou / MiniMax / GLM) and enter your API key in the sidebar.
2. Choose a model.
3. Select a paper PDF.
4. Start analysis.
5. Read the streamed summary.
-6. Download generated core code if available.
+6. If code is applicable, the model first plans the smallest file set needed for the paper's core contribution.
+7. Download generated core code if the blueprint and files pass local validation.
+
+## Core Code Generation
+
+Paper2CoreCode is intentionally not a full experiment-reproduction generator. It aims to export only the smallest reusable code needed to represent the paper's core computational contribution.
+
+Before code is cached for download, the model must produce a core-code blueprint that describes:
+
+- The inferred paper domain.
+- The core contribution to implement.
+- The minimal implementation boundary.
+- The exact files to generate.
+- The purpose and main symbols for each file.
+- Items intentionally omitted because they are not part of the core contribution.
+
+Generated files must match the blueprint exactly. Extra files are rejected, missing blueprint files are rejected, and unsafe paths are rejected. This helps avoid over-generating training scripts, datasets, baselines, experiment runners, or full application pipelines when the paper only proposes a smaller method such as a loss, module, dispatch rule, signal-processing algorithm, controller, estimator, or objective function.
## Model Providers
@@ -42,13 +59,13 @@ Some Jiekou GPT variants are shown as unsupported and disabled in the model sele
Pull requests run `npm run build` on Windows, macOS, and Linux through GitHub Actions.
-Version tags like `v0.1.2` trigger the release workflow, which builds platform packages for Windows, macOS, and Linux.
+Version tags like `v0.1.3` trigger the release workflow, which builds platform packages for Windows, macOS, and Linux.
## Tech Stack 🛠️
- Electron + TypeScript
- React + Vite
-- DeepSeek / Jiekou API(OpenAI-compatible)
+- DeepSeek / Jiekou / MiniMax / GLM APIs (OpenAI-compatible)
- `pdf-parse`
- `react-markdown` + KaTeX
- `electron-builder`
@@ -61,7 +78,7 @@ Prebuilt packages are published on the [GitHub Releases](https://github.com/lemo
- macOS: `.dmg` and `.zip`
- Linux: `.AppImage` and `.deb`
-Release packages are generated automatically when a version tag like `v0.1.0` is pushed.
+Release packages are generated automatically when a version tag like `v0.1.3` is pushed.
## Development
@@ -90,7 +107,7 @@ Build artifacts are generated in `release/`.
- API keys are stored locally in the app user data directory.
- Scanned PDFs without extractable text are not supported yet.
-- Generated code is cached locally first, then exported by the user.
+- Generated code is cached locally first, then exported by the user after blueprint validation.
- Current desktop builds are unsigned and use the default Electron icon.
## License
diff --git a/README.zh-CN.md b/README.zh-CN.md
index 1f6846b..d7c2153 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -2,28 +2,45 @@
[English](README.md) | 简体中文
-Paper2CoreCode 是一款桌面端论文阅读与核心代码生成工具,可以把论文 PDF 转换为结构化总结,并在适合复现时导出核心代码项目。
+Paper2CoreCode 是一款桌面端论文阅读与最小核心代码生成工具,可以把论文 PDF 转换为结构化总结,并在适合实现时导出最小核心代码。
-它适合研究人员、工程师和学生快速理解论文内容,并获得可继续开发的组件化实现骨架。
+它适合研究人员、工程师和学生快速理解论文内容,并在可行时获得论文核心可计算贡献的小型实现。
## 它能做什么 ✨
- 📄 分析学术论文 PDF。
-- 🧠 使用 DeepSeek / Jiekou 生成结构化论文总结。
+- 🧠 使用 DeepSeek / Jiekou / MiniMax / GLM 生成结构化论文总结。
- 🧮 清晰渲染 Markdown、表格和 LaTeX 公式。
- 💻 判断论文是否需要生成核心代码。
+- 🧭 在写入文件前规划最小核心代码蓝图。
- 📦 将生成的核心代码导出为本地项目文件夹。
- 🌐 支持中文和英文界面/输出切换。
- 🖥️ 作为本地 Electron 桌面应用运行。
## 核心流程 🚀
-1. 在侧边栏选择模型供应商(DeepSeek / Jiekou)并配置 API Key。
+1. 在侧边栏选择模型供应商(DeepSeek / Jiekou / MiniMax / GLM)并配置 API Key。
2. 选择模型。
3. 选择论文 PDF。
4. 开始分析。
5. 阅读实时流式生成的论文总结。
-6. 如果存在核心代码,下载生成的代码项目。
+6. 如果适合生成代码,模型会先规划实现论文核心贡献所需的最小文件集合。
+7. 当蓝图和文件通过本地校验后,下载生成的核心代码。
+
+## 核心代码生成
+
+Paper2CoreCode 并不是完整实验复现生成器。它的目标是只导出表达论文核心可计算贡献所需的最小可复用代码。
+
+在代码被缓存并允许下载前,模型必须先生成核心代码蓝图,说明:
+
+- 推断出的论文领域。
+- 要实现的核心贡献。
+- 最小实现边界。
+- 需要生成的精确文件列表。
+- 每个文件的用途和主要符号。
+- 因为不属于核心贡献而故意省略的内容。
+
+生成文件必须与蓝图完全一致。额外文件会被拒绝,缺少蓝图文件会被拒绝,不安全路径也会被拒绝。这有助于避免在论文只提出较小方法时过度生成训练脚本、数据集、baseline、实验运行器或完整应用流水线,例如论文只提出一个 loss、模块、调度规则、信号处理算法、控制器、估计器或目标函数时,只导出对应核心代码。
## 模型供应商
@@ -42,13 +59,13 @@ API Key 和模型选择会按供应商分别保存在本机应用用户数据目
Pull Request 会通过 GitHub Actions 在 Windows、macOS 和 Linux 上运行 `npm run build`。
-推送类似 `v0.1.2` 的版本标签时,会触发 release workflow,并为 Windows、macOS 和 Linux 构建平台安装包。
+推送类似 `v0.1.3` 的版本标签时,会触发 release workflow,并为 Windows、macOS 和 Linux 构建平台安装包。
## 技术栈 🛠️
- Electron + TypeScript
- React + Vite
-- DeepSeek / Jiekou API(OpenAI-compatible)
+- DeepSeek / Jiekou / MiniMax / GLM APIs(OpenAI-compatible)
- `pdf-parse`
- `react-markdown` + KaTeX
- `electron-builder`
@@ -61,7 +78,7 @@ Pull Request 会通过 GitHub Actions 在 Windows、macOS 和 Linux 上运行 `n
- macOS:`.dmg` 和 `.zip`
- Linux:`.AppImage` 和 `.deb`
-当推送类似 `v0.1.0` 的版本标签时,Release 产物会由 GitHub Actions 自动构建并上传。
+当推送类似 `v0.1.3` 的版本标签时,Release 产物会由 GitHub Actions 自动构建并上传。
## 开发
@@ -90,7 +107,7 @@ npm run dist:linux
- API Key 会保存在本机应用用户数据目录中。
- 暂不支持没有可提取文本的扫描版 PDF。
-- 生成的代码会先缓存在本地,再由用户主动导出。
+- 生成的代码会先在本地通过蓝图校验并缓存,再由用户主动导出。
- 当前桌面端构建未签名,并使用默认 Electron 图标。
## 开源协议
diff --git a/src/main/backend/codeCache.ts b/src/main/backend/codeCache.ts
index c1d41bd..db8f279 100644
--- a/src/main/backend/codeCache.ts
+++ b/src/main/backend/codeCache.ts
@@ -3,9 +3,38 @@ export interface GeneratedFile {
content: string
}
+export interface CodeBlueprintFile {
+ path: string
+ purpose: string
+ mainSymbols: string[]
+ mustInclude: string[]
+ mustNotInclude: string[]
+ inputs?: string[]
+ outputs?: string[]
+ assumptions?: string[]
+ evidence?: string
+}
+
+export interface CodeBlueprint {
+ paperDomain?: string
+ coreContribution: string
+ minimalImplementationBoundary: string
+ files: CodeBlueprintFile[]
+ omitted?: Array<{
+ item: string
+ reason: string
+ }>
+ minimalityCheck?: {
+ whyTheseFilesAreMinimal?: string
+ couldAnyFileBeRemoved?: boolean
+ overGenerationRisk?: string
+ }
+}
+
export interface CodeBundle {
readme: string
files: GeneratedFile[]
+ blueprint?: CodeBlueprint
}
let cached: CodeBundle | null = null
diff --git a/src/main/backend/exportCode.ts b/src/main/backend/exportCode.ts
index 7e25189..c934ddd 100644
--- a/src/main/backend/exportCode.ts
+++ b/src/main/backend/exportCode.ts
@@ -1,6 +1,67 @@
import * as fs from 'fs'
import * as path from 'path'
-import { getCachedCodeBundle } from './codeCache'
+import { CodeBlueprint, getCachedCodeBundle } from './codeCache'
+
+function escapeMarkdownTableCell(value: string): string {
+ return value.replace(/\r?\n/g, ' ').replace(/\|/g, '\\|').trim()
+}
+
+function formatList(items: string[] | undefined): string {
+ if (!items || items.length === 0) return 'Not specified.'
+ return items.map((item) => `- ${item}`).join('\n')
+}
+
+function buildBlueprintReadmeSection(blueprint: CodeBlueprint): string {
+ const generatedFiles = blueprint.files
+ .map((file) => `| ${escapeMarkdownTableCell(file.path)} | ${escapeMarkdownTableCell(file.purpose)} | ${escapeMarkdownTableCell(file.mainSymbols.join(', '))} |`)
+ .join('\n')
+
+ const omitted = blueprint.omitted && blueprint.omitted.length > 0
+ ? blueprint.omitted
+ .map((item) => `| ${escapeMarkdownTableCell(item.item)} | ${escapeMarkdownTableCell(item.reason)} |`)
+ .join('\n')
+ : '| None specified. | Not applicable. |'
+
+ const assumptions = blueprint.files.flatMap((file) => file.assumptions || [])
+ const minimality = blueprint.minimalityCheck?.whyTheseFilesAreMinimal
+
+ return `
+
+## Core Code Scope
+
+This export intentionally contains only the paper's minimal core computational contribution. It does not include experiment reproduction code, baselines, datasets, training scripts, simulators, or full application pipelines unless they are part of the proposed method itself.
+
+### Implemented Core Contribution
+
+${blueprint.coreContribution}
+
+### Minimal Implementation Boundary
+
+${blueprint.minimalImplementationBoundary}
+
+${blueprint.paperDomain ? `### Inferred Paper Domain\n\n${blueprint.paperDomain}\n\n` : ''}### Generated Files
+
+| File | Purpose | Main Symbols |
+|---|---|---|
+${generatedFiles}
+
+### Intentionally Omitted
+
+| Item | Reason |
+|---|---|
+${omitted}
+
+### Assumptions
+
+${formatList(assumptions)}
+
+${minimality ? `### Minimality Check\n\n${minimality}\n` : ''}`
+}
+
+function buildExportReadme(readme: string, blueprint?: CodeBlueprint): string {
+ if (!blueprint) return readme
+ return `${readme.trimEnd()}${buildBlueprintReadmeSection(blueprint)}`
+}
function resolveSafePath(rootDir: string, relativePath: string): string | null {
const normalized = relativePath.replace(/\\/g, '/')
@@ -31,7 +92,7 @@ export function writeCodeFolder(outputDir: string): { ok: true; path: string } |
try {
fs.mkdirSync(outputDir, { recursive: true })
- fs.writeFileSync(path.join(outputDir, 'README.md'), bundle.readme, 'utf-8')
+ fs.writeFileSync(path.join(outputDir, 'README.md'), buildExportReadme(bundle.readme, bundle.blueprint), 'utf-8')
for (const file of bundle.files) {
const target = resolveSafePath(outputDir, file.path)
diff --git a/src/main/backend/paperAnalyzer.ts b/src/main/backend/paperAnalyzer.ts
index 2db0f84..80b3ad1 100644
--- a/src/main/backend/paperAnalyzer.ts
+++ b/src/main/backend/paperAnalyzer.ts
@@ -1,7 +1,7 @@
import { parsePDF } from './pdfParser'
import { callDeepSeek } from './deepseekClient'
import { buildCombinedAnalysisPrompt } from './promptBuilder'
-import { cacheCodeBundle, clearCache, GeneratedFile } from './codeCache'
+import { cacheCodeBundle, clearCache, CodeBlueprint, CodeBlueprintFile, GeneratedFile } from './codeCache'
import { AnalysisProgress, AnalysisResult, AppError, ErrorCodes } from './errors'
import { getActiveSettings } from './settingsStore'
@@ -9,9 +9,39 @@ const SUMMARY_START = ''
const SUMMARY_END = ''
const DECISION_START = ''
const DECISION_END = ''
+const BLUEPRINT_START = ''
+const BLUEPRINT_END = ''
const CODE_BUNDLE_START = ''
const CODE_BUNDLE_END = ''
+const HIGH_RISK_FILE_NAMES = new Set([
+ 'baseline.py',
+ 'config.py',
+ 'dataset.py',
+ 'dataloader.py',
+ 'experiment.py',
+ 'experiment_runner.py',
+ 'inference.py',
+ 'main.py',
+ 'pipeline.py',
+ 'requirements.txt',
+ 'train.py',
+ 'utils.py',
+])
+
+const HIGH_RISK_JUSTIFICATION_TERMS = [
+ 'proposed',
+ 'novel',
+ 'core contribution',
+ 'method itself',
+ 'algorithm',
+ 'procedure',
+ '本文提出',
+ '提出的',
+ '核心贡献',
+ '方法本身',
+]
+
function extractJsonObject(raw: string): unknown {
const trimmed = raw.trim()
const fenced = trimmed.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/)
@@ -53,6 +83,125 @@ function normalizeFileContent(content: string): string {
return content.replace(/^\r?\n/, '').replace(/\r?\n$/, '')
}
+function isRecord(value: unknown): value is Record {
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
+}
+
+function requireNonEmptyString(value: unknown, fieldName: string): string {
+ if (typeof value !== 'string' || value.trim() === '') {
+ throw new AppError(ErrorCodes.API_RESPONSE_INVALID, `模型返回的代码蓝图缺少 ${fieldName}`)
+ }
+
+ return value.trim()
+}
+
+function requireStringArray(value: unknown, fieldName: string): string[] {
+ if (!Array.isArray(value) || value.length === 0) {
+ throw new AppError(ErrorCodes.API_RESPONSE_INVALID, `模型返回的代码蓝图缺少 ${fieldName}`)
+ }
+
+ const strings = value.map((item) => requireNonEmptyString(item, fieldName))
+ return strings
+}
+
+function optionalStringArray(value: unknown, fieldName: string): string[] | undefined {
+ if (value === undefined) return undefined
+ if (!Array.isArray(value)) {
+ throw new AppError(ErrorCodes.API_RESPONSE_INVALID, `模型返回的代码蓝图 ${fieldName} 格式无效`)
+ }
+
+ return value.map((item) => requireNonEmptyString(item, fieldName))
+}
+
+function requiresHighRiskJustification(file: CodeBlueprintFile): boolean {
+ const fileName = file.path.split('/').pop()?.toLowerCase() || ''
+ if (!HIGH_RISK_FILE_NAMES.has(fileName)) return false
+
+ const justification = [file.purpose, file.evidence, ...file.mustInclude].join(' ').toLowerCase()
+ return !HIGH_RISK_JUSTIFICATION_TERMS.some((term) => justification.includes(term))
+}
+
+function parseCodeBlueprint(raw: string): CodeBlueprint {
+ const parsed = extractJsonObject(raw)
+ if (!isRecord(parsed)) {
+ throw new AppError(ErrorCodes.API_RESPONSE_INVALID, '模型返回的代码蓝图无效')
+ }
+
+ const rawFiles = parsed.files
+ if (!Array.isArray(rawFiles) || rawFiles.length === 0) {
+ throw new AppError(ErrorCodes.API_RESPONSE_INVALID, '模型返回的代码蓝图缺少文件规划')
+ }
+
+ const paths = new Set()
+ const files = rawFiles.map((rawFile, index): CodeBlueprintFile => {
+ if (!isRecord(rawFile)) {
+ throw new AppError(ErrorCodes.API_RESPONSE_INVALID, `模型返回的代码蓝图文件 #${index + 1} 无效`)
+ }
+
+ const path = validateGeneratedFilePath(requireNonEmptyString(rawFile.path, 'file.path'))
+ if (!path.startsWith('core_code/')) {
+ throw new AppError(ErrorCodes.API_RESPONSE_INVALID, `代码蓝图文件必须位于 core_code/ 目录: ${path}`)
+ }
+ if (paths.has(path)) {
+ throw new AppError(ErrorCodes.API_RESPONSE_INVALID, `代码蓝图包含重复文件路径: ${path}`)
+ }
+ paths.add(path)
+
+ const file: CodeBlueprintFile = {
+ path,
+ purpose: requireNonEmptyString(rawFile.purpose, 'file.purpose'),
+ mainSymbols: requireStringArray(rawFile.mainSymbols, 'file.mainSymbols'),
+ mustInclude: requireStringArray(rawFile.mustInclude, 'file.mustInclude'),
+ mustNotInclude: requireStringArray(rawFile.mustNotInclude, 'file.mustNotInclude'),
+ inputs: optionalStringArray(rawFile.inputs, 'file.inputs'),
+ outputs: optionalStringArray(rawFile.outputs, 'file.outputs'),
+ assumptions: optionalStringArray(rawFile.assumptions, 'file.assumptions'),
+ evidence: typeof rawFile.evidence === 'string' ? rawFile.evidence.trim() : undefined,
+ }
+
+ if (requiresHighRiskJustification(file)) {
+ throw new AppError(ErrorCodes.API_RESPONSE_INVALID, `高风险文件缺少核心贡献依据: ${path}`)
+ }
+
+ return file
+ })
+
+ const omitted = Array.isArray(parsed.omitted)
+ ? parsed.omitted.map((item, index) => {
+ if (!isRecord(item)) {
+ throw new AppError(ErrorCodes.API_RESPONSE_INVALID, `代码蓝图 omitted #${index + 1} 无效`)
+ }
+ return {
+ item: requireNonEmptyString(item.item, 'omitted.item'),
+ reason: requireNonEmptyString(item.reason, 'omitted.reason'),
+ }
+ })
+ : undefined
+
+ const minimalityCheck = isRecord(parsed.minimalityCheck)
+ ? {
+ whyTheseFilesAreMinimal: typeof parsed.minimalityCheck.whyTheseFilesAreMinimal === 'string'
+ ? parsed.minimalityCheck.whyTheseFilesAreMinimal.trim()
+ : undefined,
+ couldAnyFileBeRemoved: typeof parsed.minimalityCheck.couldAnyFileBeRemoved === 'boolean'
+ ? parsed.minimalityCheck.couldAnyFileBeRemoved
+ : undefined,
+ overGenerationRisk: typeof parsed.minimalityCheck.overGenerationRisk === 'string'
+ ? parsed.minimalityCheck.overGenerationRisk.trim()
+ : undefined,
+ }
+ : undefined
+
+ return {
+ paperDomain: typeof parsed.paperDomain === 'string' ? parsed.paperDomain.trim() : undefined,
+ coreContribution: requireNonEmptyString(parsed.coreContribution, 'coreContribution'),
+ minimalImplementationBoundary: requireNonEmptyString(parsed.minimalImplementationBoundary, 'minimalImplementationBoundary'),
+ files,
+ omitted,
+ minimalityCheck,
+ }
+}
+
function parseTaggedCodeFiles(raw: string): GeneratedFile[] {
const files: GeneratedFile[] = []
const fileRegex = /([\s\S]*?)<\/P2CC_FILE>/g
@@ -67,6 +216,30 @@ function parseTaggedCodeFiles(raw: string): GeneratedFile[] {
return files
}
+function validateFilesAgainstBlueprint(files: GeneratedFile[], blueprint: CodeBlueprint): void {
+ const blueprintPaths = new Set(blueprint.files.map((file) => file.path))
+ const generatedPaths = new Set()
+
+ for (const file of files) {
+ if (file.content.trim() === '') {
+ throw new AppError(ErrorCodes.API_RESPONSE_INVALID, `模型生成了空代码文件: ${file.path}`)
+ }
+ if (!blueprintPaths.has(file.path)) {
+ throw new AppError(ErrorCodes.API_RESPONSE_INVALID, `模型生成了蓝图外文件: ${file.path}`)
+ }
+ if (generatedPaths.has(file.path)) {
+ throw new AppError(ErrorCodes.API_RESPONSE_INVALID, `模型重复生成文件: ${file.path}`)
+ }
+ generatedPaths.add(file.path)
+ }
+
+ for (const path of blueprintPaths) {
+ if (!generatedPaths.has(path)) {
+ throw new AppError(ErrorCodes.API_RESPONSE_INVALID, `模型未生成蓝图声明的文件: ${path}`)
+ }
+ }
+}
+
function removePartialEndTagSuffix(content: string, endTag: string): string {
const max = Math.min(endTag.length - 1, content.length)
@@ -146,6 +319,7 @@ export async function analyzePaper(
let streamedSummary = ''
let summaryClosed = false
let decisionProgressSent = false
+ let blueprintProgressSent = false
let bundleProgressSent = false
await callDeepSeek(
@@ -180,9 +354,14 @@ export async function analyzePaper(
})
}
+ if (!blueprintProgressSent && rawOutput.includes(BLUEPRINT_END)) {
+ blueprintProgressSent = true
+ onProgress({ stage: 'generating_code', message: msg('核心代码蓝图已生成,正在生成最小代码文件...', 'Core code blueprint generated, creating minimal files...') })
+ }
+
if (!bundleProgressSent && rawOutput.includes(CODE_BUNDLE_START)) {
bundleProgressSent = true
- onProgress({ stage: 'generating_code', message: msg('正在生成组件化代码文件...', 'Generating componentized code files...') })
+ onProgress({ stage: 'generating_code', message: msg('正在按蓝图生成核心代码文件...', 'Generating core code files from blueprint...') })
}
}
)
@@ -194,16 +373,23 @@ export async function analyzePaper(
let hasCoreCode = false
if (decision.needed) {
- onProgress({ stage: 'generating_code', message: msg('需要生成核心代码,正在处理组件化代码...', 'Core code is needed, processing componentized code...') })
- const codeContent = extractTaggedContent(rawOutput, CODE_BUNDLE_START, CODE_BUNDLE_END, '模型判断需要生成代码,但缺少代码文件结构')
- const files = parseTaggedCodeFiles(codeContent)
-
- if (files.length === 0) {
- onProgress({ stage: 'generating_code', message: msg('论文不包含可提取的核心代码', 'Paper does not contain extractable core code') })
- } else {
- cacheCodeBundle({ readme: cleanedSummary.trim(), files })
+ onProgress({ stage: 'generating_code', message: msg('需要生成核心代码,正在校验蓝图和代码范围...', 'Core code is needed, validating blueprint and code scope...') })
+ try {
+ const blueprintContent = extractTaggedContent(rawOutput, BLUEPRINT_START, BLUEPRINT_END, '模型判断需要生成代码,但缺少核心代码蓝图')
+ const blueprint = parseCodeBlueprint(blueprintContent)
+ const codeContent = extractTaggedContent(rawOutput, CODE_BUNDLE_START, CODE_BUNDLE_END, '模型判断需要生成代码,但缺少代码文件结构')
+ const files = parseTaggedCodeFiles(codeContent)
+
+ validateFilesAgainstBlueprint(files, blueprint)
+ cacheCodeBundle({ readme: cleanedSummary.trim(), files, blueprint })
hasCoreCode = true
- onProgress({ stage: 'generating_code', message: msg('组件化代码项目已生成', 'Componentized code project has been generated') })
+ onProgress({ stage: 'generating_code', message: msg('最小核心代码已按蓝图生成', 'Minimal core code has been generated from the blueprint') })
+ } catch (err) {
+ if (err instanceof AppError && err.code === ErrorCodes.API_RESPONSE_INVALID) {
+ onProgress({ stage: 'generating_code', message: msg('模型未能生成有效的核心代码蓝图,本次仅保留论文总结', 'Model did not produce a valid core code blueprint; keeping summary only') })
+ } else {
+ throw err
+ }
}
} else {
onProgress({ stage: 'generating_code', message: msg('模型判断无需生成核心代码', 'Model determined core code is not needed') })
diff --git a/src/main/backend/promptBuilder.ts b/src/main/backend/promptBuilder.ts
index 9d44818..188b6ed 100644
--- a/src/main/backend/promptBuilder.ts
+++ b/src/main/backend/promptBuilder.ts
@@ -239,48 +239,70 @@ $$
{"needed": true, "reason": "brief reason"}
-Use "needed": true only if the paper clearly describes at least one implementable algorithm, model architecture, loss/metric, training/inference procedure, or pseudocode that can be translated into reusable code. Use "needed": false for surveys, purely theoretical discussions without implementable procedure, position papers, benchmarks without a new method, or papers lacking enough implementation detail.
+Use "needed": true only if the paper clearly describes at least one implementable computational contribution such as an algorithm, formula, objective, constraint, model component, control rule, signal-processing method, statistical estimator, data-analysis procedure, pseudocode, or other reusable method. Use "needed": false for surveys, purely theoretical discussions without implementable procedure, position papers, benchmarks without a new method, or papers lacking enough implementation detail.
-3. If "needed" is false, stop immediately after . Do NOT output a code bundle.
+3. If "needed" is false, stop immediately after . Do NOT output a code blueprint or code bundle.
-4. If "needed" is true, continue by outputting componentized files inside these exact tags:
+4. If "needed" is true, first output a minimal core code blueprint as strict JSON inside these exact tags:
+
+{
+ "paperDomain": "brief domain inferred from the paper",
+ "coreContribution": "the paper's smallest implementable computational contribution",
+ "minimalImplementationBoundary": "exactly what the generated code should implement, and what it should not implement",
+ "files": [
+ {
+ "path": "core_code/descriptive_file_name.py",
+ "purpose": "why this file is necessary for the minimal core contribution",
+ "mainSymbols": ["function_or_class_name"],
+ "mustInclude": ["specific method elements that must appear in this file"],
+ "mustNotInclude": ["experiments, baselines, pipelines, or unrelated engineering that must not appear"],
+ "inputs": ["expected input data or parameters"],
+ "outputs": ["expected return values or outputs"],
+ "assumptions": ["necessary implementation assumptions because the paper does not specify every engineering detail"],
+ "evidence": "paper section, formula, algorithm, or explicit description that supports this file"
+ }
+ ],
+ "omitted": [
+ {
+ "item": "intentionally omitted item",
+ "reason": "why it is not part of the paper's core contribution"
+ }
+ ],
+ "minimalityCheck": {
+ "whyTheseFilesAreMinimal": "why this file set is the smallest sufficient implementation",
+ "couldAnyFileBeRemoved": false,
+ "overGenerationRisk": "low"
+ }
+}
+
+
+Blueprint rules:
+- Do NOT assume the paper is about AI, machine learning, or software engineering. Infer the domain from the paper.
+- Do NOT use a fixed project template. The file list must be designed from the paper's smallest implementable computational contribution.
+- Each file must be justified by the core contribution. If a file is not necessary for that contribution, omit it.
+- Prefer one or a few focused source files over a complete runnable project.
+- Put generated source files under "core_code/". Do not include README.md in the blueprint.
+- Avoid generic files such as config.py, utils.py, main.py, pipeline.py, dataset.py, dataloader.py, train.py, inference.py, experiment.py, experiment_runner.py, baseline.py, or requirements.txt unless the paper's novel contribution explicitly requires that exact file.
+- Experimental setup, baselines, datasets, training recipes, simulators, and application platforms are NOT core contribution code unless the paper proposes them as the method itself.
+- The blueprint must state what is intentionally omitted so the exported code remains minimal.
+
+5. Then output code files inside these exact tags. The code bundle MUST contain exactly the files listed in P2CC_CODE_BLUEPRINT.files, no more and no fewer:
-
-...
-
-
-...
-
-
-...
-
-
-...
-
-
-...
-
-
-...
-
-
-...
-
-
+
...
Code generation rules:
-- Only implement what is clearly described in the paper. Do NOT add features not mentioned.
+- Only implement what is clearly described in the paper and listed in the blueprint. Do NOT add features not mentioned.
- Do NOT generate README.md. The application creates README.md separately from the paper summary.
- Every file path MUST be relative, use forward slashes, and MUST NOT contain "..".
- Put each generated file in its own ... block.
- Do NOT encode file contents as JSON strings. Write raw file content directly inside the file block.
-- Prefer the folder name "core_code" for source files.
-- Prefer Python for ML/algorithm papers.
-- Keep files componentized and minimal: configuration, model/algorithm, loss/metrics if needed, training or inference, and an example entry point.
-- Include requirements.txt when dependencies are needed.
+- Use the exact paths declared in P2CC_CODE_BLUEPRINT.files.
+- Do not generate extra helper files outside the blueprint.
+- Prefer Python for algorithmic papers, but choose simple, dependency-light code that best fits the paper's method.
+- Keep the implementation reusable as a core component, not as a full experiment reproduction project.
- Use comments to explain key implementation decisions only where helpful.`
return {