@@ -1,9 +1,67 @@
window . addEventListener ( 'load' , ( ) => {
console . log ( window . material ) ;
console . log ( window . lesson ) ;
editLesson ( window . material . material _id , window . lesson . chapter _name , window . lesson . lesson _name ) ;
editLesson ( window . material . material _id , window . chapter . chapter _name , window . lesson . lesson _name ) ;
} ) ;
// 在页面里复用一个全局 tooltip 节点
let _ _globalTooltip ;
function ensureTooltip ( ) {
if ( ! _ _globalTooltip ) {
_ _globalTooltip = document . createElement ( 'div' ) ;
_ _globalTooltip . className = 'tooltip-portal' ;
document . body . appendChild ( _ _globalTooltip ) ;
}
return _ _globalTooltip ;
}
// 绑定到任意图标:移入→显示;移动→跟随;移出→隐藏
function attachImmediateTooltip ( anchorEl , text ) {
const tip = ensureTooltip ( ) ;
let shown = false ;
function show ( e ) {
tip . textContent = text ;
tip . style . display = 'block' ;
tip . style . opacity = '1' ;
position ( e ) ;
shown = true ;
}
function position ( e ) {
// 以鼠标位置为准(也可用 anchorEl.getBoundingClientRect())
const padding = 8 ;
const tipRect = tip . getBoundingClientRect ( ) ;
let x = e . clientX + 12 ;
let y = e . clientY - tipRect . height - 12 ;
// 简易防出界
const vw = window . innerWidth , vh = window . innerHeight ;
if ( x + tipRect . width + padding > vw ) x = vw - tipRect . width - padding ;
if ( y < padding ) y = e . clientY + 16 ; // 放到下面
tip . style . left = ` ${ x } px ` ;
tip . style . top = ` ${ y } px ` ;
}
function hide ( ) {
tip . style . opacity = '0' ;
// 用小延迟避免闪烁
setTimeout ( ( ) => { if ( ! shown ) return ; tip . style . display = 'none' ; } , 100 ) ;
shown = false ;
}
anchorEl . addEventListener ( 'mouseenter' , show ) ;
anchorEl . addEventListener ( 'mousemove' , position ) ;
anchorEl . addEventListener ( 'mouseleave' , hide ) ;
}
// 你的 renderOutline 里调用:
// if (outline.theme) addThemeWithHelp(tree, outline.theme);
function parseHeadings ( mdText ) {
const lines = mdText . split ( '\n' ) ;
@@ -51,8 +109,13 @@ function parseHeadings(mdText) {
links : { lesson : '' , prompt : '' , score : '' } ,
raw : { lesson : '' , prompt : '' , score : '' } ,
parsed : { lesson : null , prompt : null , score : null } ,
active : { phase : null , step : null } ,
outline : null
} ;
function bustCache ( url ) {
const sep = url . includes ( "?" ) ? "&" : "?" ;
return url + sep + "_ts=" + Date . now ( ) ;
}
// 入口:从你的列表调用
async function editLesson ( material _id , chapter _name , lesson _name ) {
@@ -88,7 +151,11 @@ function parseHeadings(mdText) {
console . log ( CURRENT . links ) ;
// 拉三份 Markdown
const [ t1 , t2 , t3 ] = await Promise . all ( [ fetch ( CURRENT . links . lesson ) . then ( r => r . text ( ) ) , fetch ( CURRENT . links . prompt ) . then ( r => r . text ( ) ) , fetch ( CURRENT . links . score ) . then ( r => r . text ( ) ) ] ) ;
const [ t1 , t2 , t3 ] = await Promise . all ( [
fetch ( bustCache ( CURRENT . links . lesson ) , { cache : "no-store" } ) . then ( r => r . text ( ) ) ,
fetch ( bustCache ( CURRENT . links . prompt ) , { cache : "no-store" } ) . then ( r => r . text ( ) ) ,
fetch ( bustCache ( CURRENT . links . score ) , { cache : "no-store" } ) . then ( r => r . text ( ) )
] ) ;
CURRENT . raw . lesson = t1 ; CURRENT . raw . prompt = t2 ; CURRENT . raw . score = t3 ;
@@ -114,62 +181,370 @@ function parseHeadings(mdText) {
}
}
// 渲染左侧大纲
// ===== 在渲染一级标题时调用 =====
function addThemeWithHelp ( tree , themeText ) {
const li = document . createElement ( 'li' ) ;
li . className = 'lvl1' ;
li . textContent = themeText ;
const help = document . createElement ( 'span' ) ;
help . className = 'help-icon' ;
help . textContent = ' ?' ;
li . appendChild ( help ) ;
// 绑定即时 tooltip( 挂到 body, 不会被裁切)
attachImmediateTooltip (
help ,
` 提示: “阶段”和“步骤”是为了统一大模型中的提示词表述, 满足所在Markdown标题层级即可。 \n \r
(即阶段为##,步骤为###,在正文编辑中不要使用#、##、###(可使用####、#####) `
) ;
tree . appendChild ( li ) ;
}
function renderOutline ( outline ) {
const tree = document . getElementById ( 'outline-tree' ) ;
tree . innerHTML = '' ;
if ( outline . theme ) {
const li = document . createElement ( 'li' ) ; li . className = 'lvl1' ; li . textContent = ` # ${ outline . theme } ` ; tree . appendChild ( li ) ;
}
outline . phases . forEach ( ph => {
const li2 = document . createElement ( 'li' ) ; li2 . className = 'lvl2' ; li2 . textContent = ` ## ${ ph . text } ` ;
tree . appendChild ( li2 ) ;
const steps = outline . stepsByPhase [ ph . text ] || [ ] ;
steps . forEach ( st => {
const li3 = document . createElement ( 'li' ) ;
li3 . className = 'lvl3' ;
li3 . textContent = ` ### ${ st } ` ;
li3 . dataset . phase = ph . text ;
li3 . dataset . step = st ;
li3 . onclick = ( ) => {
loadStepToEditors ( ph . text , st ) ;
highlightActive ( ph . text , st ) ;
} ;
tree . appendChild ( li3 ) ;
} ) ;
tree . innerHTML = '' ; // 清空大纲内容
if ( outline . theme ) addThemeWithHelp ( tree , outline . theme ) ;
// 渲染每个二级标题
outline . phases . forEach ( ph => {
// 二级标题
const li2 = document . createElement ( 'li' ) ;
li2 . className = 'lvl2' ;
li2 . textContent = ` ${ ph . text } ` ; //##
tree . appendChild ( li2 ) ;
// 渲染三级标题(步骤)
const steps = outline . stepsByPhase [ ph . text ] || [ ] ;
steps . forEach ( st => {
const li3 = document . createElement ( 'li' ) ;
li3 . className = 'lvl3' ;
li3 . textContent = ` ${ st } ` ; //###
li3 . dataset . phase = ph . text ;
li3 . dataset . step = st ;
li3 . onclick = ( ) => {
loadStepToEditors ( ph . text , st ) ;
highlightActive ( ph . text , st ) ;
} ;
tree . appendChild ( li3 ) ;
} ) ;
// 在二级标题下添加 "新增三级标题" 按钮
const addStepBtn = document . createElement ( 'button' ) ;
addStepBtn . className = 'add-step-btn' ;
addStepBtn . textContent = '新增步骤' ;
addStepBtn . onclick = ( ) => addStepToPhase ( ph . text ) ;
tree . appendChild ( addStepBtn ) ;
} ) ;
// 在大纲底部添加 "新增二级标题" 按钮
const addPhaseBtn = document . createElement ( 'button' ) ;
addPhaseBtn . className = 'add-phase-btn' ;
addPhaseBtn . textContent = '新增阶段' ;
addPhaseBtn . onclick = ( ) => addPhaseToOutline ( ) ;
tree . appendChild ( addPhaseBtn ) ;
}
// 新增二级标题(章节)
// 新增二级标题(章节 / 阶段)
function addPhaseToOutline ( ) {
const phaseName = ( prompt ( '请输入新的章节(阶段)名称:' ) || '' ) . trim ( ) ;
if ( ! phaseName ) return ;
// 重名校验(防止三份文档与大纲不同步)
const dup = ( CURRENT . outline . phases || [ ] ) . some ( p => p . text === phaseName ) ;
if ( dup ) { alert ( '该阶段名称已存在' ) ; return ; }
// 2) 向三份原文真正写入该阶段骨架
CURRENT . raw . lesson = insertPhaseIntoMarkdown (
CURRENT . raw . lesson , phaseName , PHASE _TEMPLATES . lesson
) ;
CURRENT . raw . prompt = insertPhaseIntoMarkdown (
CURRENT . raw . prompt , phaseName , PHASE _TEMPLATES . prompt
) ;
CURRENT . raw . score = insertPhaseIntoMarkdown (
CURRENT . raw . score , phaseName , PHASE _TEMPLATES . score
) ;
// 3) 重新解析并构建统一大纲
CURRENT . parsed . lesson = parseHeadings ( CURRENT . raw . lesson ) ;
CURRENT . parsed . prompt = parseHeadings ( CURRENT . raw . prompt ) ;
CURRENT . parsed . score = parseHeadings ( CURRENT . raw . score ) ;
CURRENT . outline = buildUnifiedOutline ( CURRENT . parsed . lesson , CURRENT . parsed . prompt , CURRENT . parsed . score ) ;
// 4) 重绘大纲 & 默认高亮新阶段(若你希望直接进入某个步骤,可在此自动新增第一个###)
renderOutline ( CURRENT . outline ) ;
}
/**
* 在 mdText 文末追加一个新的 "## phaseText" 块(若已存在则不重复插入)
* skeleton 为该阶段下的默认正文
*/
function insertPhaseIntoMarkdown ( mdText , phaseText , skeleton ) {
const parsed = parseHeadings ( mdText ) ;
const exists = parsed . tokens . some ( t => t . level === 2 && t . text === phaseText ) ;
if ( exists ) return mdText ; // 已存在则原样返回
const trimmed = mdText . trimEnd ( ) ;
const block = [
'' , '' , // 与上一段隔两行,更稳妥
` ## ${ phaseText } ` ,
'' , // 标题与正文留空行
... ( skeleton ? skeleton . trimEnd ( ) . split ( '\n' ) : [ ] ) ,
'' // 末尾再留一空行
] . join ( '\n' ) ;
return trimmed + block + '\n' ;
}
function addStepToPhase ( phaseText ) {
const stepName = prompt ( '请输入新的步骤名称:' ) ;
if ( ! stepName ) return ;
CURRENT . raw . lesson = insertStepIntoMarkdown (
CURRENT . raw . lesson , phaseText , stepName , STEP _TEMPLATES . lesson
) ;
CURRENT . raw . prompt = insertStepIntoMarkdown (
CURRENT . raw . prompt , phaseText , stepName , STEP _TEMPLATES . prompt
) ;
CURRENT . raw . score = insertStepIntoMarkdown (
CURRENT . raw . score , phaseText , stepName , STEP _TEMPLATES . score
) ;
// 重新解析三份文档并重建统一大纲
CURRENT . parsed . lesson = parseHeadings ( CURRENT . raw . lesson ) ;
CURRENT . parsed . prompt = parseHeadings ( CURRENT . raw . prompt ) ;
CURRENT . parsed . score = parseHeadings ( CURRENT . raw . score ) ;
CURRENT . outline = buildUnifiedOutline ( CURRENT . parsed . lesson , CURRENT . parsed . prompt , CURRENT . parsed . score ) ;
// 重新渲染左侧树,并默认选中新加的步骤(加载到三个编辑器)
renderOutline ( CURRENT . outline ) ;
loadStepToEditors ( phaseText , stepName ) ;
highlightActive ( phaseText , stepName ) ;
}
function highlightActive ( phase , step ) {
document . querySelectorAll ( '#outline-tree li' ) . forEach ( li => li . classList . remove ( 'active' ) ) ;
const target = Array . from ( document . querySelectorAll ( '#outline-tree li.lvl3' ) )
. find ( li => li . dataset . phase === phase && li . dataset . step === step ) ;
if ( target ) target . classList . add ( 'active' ) ;
CURRENT . active . phase = phase
CURRENT . active . step = step
}
/**
* 在 mdText 中,将 `### stepName` 插入到 `## phaseText` 阶段的末尾。
* 若 phase 不存在,则创建 `## phaseText` 再插入。
* skeleton 为插入文本模板(含### 标题本身及正文)。
*/
function insertStepIntoMarkdown ( mdText , phaseText , stepName , skeleton , { allowDuplicate = false } = { } ) {
const parsed = parseHeadings ( mdText ) ;
const { lines , tokens } = parsed ;
// 找到目标阶段(##)
const phaseToken = tokens . find ( t => t . level === 2 && t . text === phaseText ) ;
// 生成插入块(### 标题 + 正文骨架),不强制前后空行,由下面逻辑按需补
const blockLines = [
` ### ${ stepName } ` ,
'' , // 标题后留一行
... ( skeleton ? skeleton . trimEnd ( ) . split ( '\n' ) : [ ] )
] ;
// 如果没找到该阶段:在文末新建阶段并追加该步骤
if ( ! phaseToken ) {
const endIsBlank = mdText . endsWith ( '\n\n' ) ;
const out = [
mdText . trimEnd ( ) ,
endIsBlank ? '' : '' , // 保持原末尾,不多加
'' , // 与原文隔一空行更安全
` ## ${ phaseText } ` ,
'' , // 阶段标题后空行
... blockLines ,
'' // 末尾再空一行
] . join ( '\n' ) ;
return out ;
}
// 收集该阶段内的所有 ### 步骤
const stepsInPhase = tokens . filter ( t =>
t . level === 3 && t . start > phaseToken . start && t . end <= phaseToken . end
) ;
// 已存在同名步骤?
const dup = stepsInPhase . some ( s => s . text === stepName ) ;
if ( dup && ! allowDuplicate ) {
// 已有同名步骤则直接返回原文本(也可改为抛错/提示)
return mdText ;
}
// 计算插入位置:
// - 若有步骤:插入到最后一个步骤“之后”
// - 若无步骤:插入到该阶段“末尾”(不打乱引言)
let insertPos ; // 行号(在其前插入)
if ( stepsInPhase . length > 0 ) {
const lastStep = stepsInPhase [ stepsInPhase . length - 1 ] ;
insertPos = lastStep . end + 1 ;
} else {
insertPos = phaseToken . end + 1 ;
}
// 按需补空行:在插入块前后各确保至少一条空行,避免粘连
const beforeLine = insertPos - 1 >= 0 ? lines [ insertPos - 1 ] : '' ;
const needLeadingBlank = beforeLine && beforeLine . trim ( ) !== '' ;
// 插入块组装
const insertChunk = [
... ( needLeadingBlank ? [ '' ] : [ ] ) , // 前置空行(若上一行非空)
... blockLines ,
'' // 块后空一行
] ;
const newLines = lines . slice ( 0 , insertPos )
. concat ( insertChunk )
. concat ( lines . slice ( insertPos ) ) ;
return newLines . join ( '\n' ) ;
}
// 解析Markdown并定位章节/步骤
function parseHeadings ( mdText ) {
const lines = mdText . split ( '\n' ) ;
const tokens = [ ] ;
for ( let i = 0 ; i < lines . length ; i ++ ) {
const m = lines [ i ] . match ( /^(#{1,3})\s+(.*)$/ ) ;
if ( m ) tokens . push ( { level : m [ 1 ] . length , text : m [ 2 ] . trim ( ) , line : i } ) ;
}
// 确定每个heading的区间( 到下一个同级或上级前一行)
for ( let i = 0 ; i < tokens . length ; i ++ ) {
let j = i + 1 ;
while ( j < tokens . length && tokens [ j ] . level > tokens [ i ] . level ) j ++ ;
let k = i + 1 ;
while ( k < tokens . length && tokens [ k ] . level > tokens [ i ] . level ) k ++ ;
const nextSameOrHigher = k < tokens . length ? tokens [ k ] . line : lines . length ;
tokens [ i ] . start = tokens [ i ] . line ;
tokens [ i ] . end = nextSameOrHigher - 1 ;
}
return { lines , tokens } ;
}
// 从解析结果中抽取一个 heading 段落文本
function sliceSection ( parsed , headingText , level ) {
const t = parsed . tokens . find ( x => x . level === level && x . text === headingText ) ;
if ( ! t ) return '' ;
return parsed . lines . slice ( t . start , t . end + 1 ) . join ( '\n' ) ;
}
function buildOriginSaveUrl ( ) {
return ` /api/materials/save/ ${ encodeURIComponent ( CURRENT . material _id ) } / ${ encodeURIComponent ( CURRENT . chapter _name ) } / ${ encodeURIComponent ( CURRENT . lesson _name ) } ` ;
}
/**
* 把三份文档( lesson/prompt/score) 的 "CDN 外链 + 原文内容" 一次性提交给源站。
* @param {Object} payload 形如:
* {
* lesson: { cdn_link, content },
* prompt: { cdn_link, content },
* score: { cdn_link, content }
* }
*/
async function saveToOrigin ( payload ) {
const res = await fetch ( buildOriginSaveUrl ( ) , {
method : 'POST' ,
headers : { 'Content-Type' : 'application/json' } ,
body : JSON . stringify ( {
// 你也可以改为 docs: [] 的数组结构,后端按需解析
lesson : payload . lesson ,
prompt : payload . prompt ,
score : payload . score ,
} ) ,
credentials : 'include' // 若需要带 cookie/会话,保留;不需要可移除
} ) ;
if ( ! res . ok ) {
const text = await res . text ( ) . catch ( ( ) => '' ) ;
throw new Error ( ` 保存失败( ${ res . status } ): ${ text } ` ) ;
}
return res . json ( ) . catch ( ( ) => ( { } ) ) ;
}
// 保存某个步骤的更新
async function saveUpdatedStep ( ) {
const lEditor = document . getElementById ( 'editor-lesson' ) ;
const pEditor = document . getElementById ( 'editor-prompt' ) ;
const sEditor = document . getElementById ( 'editor-score' ) ;
// 获取原始的文档内容
const originalLesson = CURRENT . raw . lesson ;
const originalPrompt = CURRENT . raw . prompt ;
const originalScore = CURRENT . raw . score ;
// 解析各自的文档结构
const parsedLesson = parseHeadings ( originalLesson ) ;
const parsedPrompt = parseHeadings ( originalPrompt ) ;
const parsedScore = parseHeadings ( originalScore ) ;
phaseText = CURRENT . active . phase
stepText = CURRENT . active . step ;
// 更新具体步骤
const updatedLesson = updateMarkdownContent ( parsedLesson , phaseText , stepText , lEditor . value ) ;
const updatedPrompt = updateMarkdownContent ( parsedPrompt , phaseText , stepText , pEditor . value ) ;
const updatedScore = updateMarkdownContent ( parsedScore , phaseText , stepText , sEditor . value ) ;
// PUT 请求保存更新
try {
const result = await saveToOrigin ( {
lesson : { cdn _link : CURRENT . links . lesson , content : updatedLesson } ,
prompt : { cdn _link : CURRENT . links . prompt , content : updatedPrompt } ,
score : { cdn _link : CURRENT . links . score , content : updatedScore } ,
} ) ;
//result 可包含新的 CDN 链接或版本号,更新到 CURRENT.links / UI
if ( result ? . links ) { CURRENT . links = { ... CURRENT . links , ... result . links } ; }
alert ( '已保存' ) ;
}
catch ( err ) {
console . error ( err ) ;
alert ( '保存失败,请重试' ) ;
}
}
// 更新对应步骤的内容
function updateMarkdownContent ( parsed , phaseText , stepText , newContent ) {
const phaseIndex = parsed . tokens . findIndex ( token => token . level === 2 && token . text === phaseText ) ;
if ( phaseIndex === - 1 ) return parsed . lines . join ( '\n' ) ; // 找不到阶段
const stepIndex = parsed . tokens . findIndex ( token => token . level === 3 && token . text === stepText ) ;
if ( stepIndex === - 1 ) return parsed . lines . join ( '\n' ) ; // 找不到步骤
const startLine = parsed . tokens [ stepIndex ] . start ;
const endLine = parsed . tokens [ stepIndex ] . end ;
// 替换该步骤内容
const lines = [ ... parsed . lines ] ;
lines . splice ( startLine , endLine - startLine + 1 , newContent ) ; // 用新内容替换
return lines . join ( '\n' ) ; // 返回更新后的完整内容
}
// 把“某个步骤”对应的三个片段载入到三个编辑器
function loadStepToEditors ( phaseText , stepText ) {
// 三份文档里都取对应 phase/step 的片段(不存在则回退)
const L = CURRENT . parsed . lesson , P = CURRENT . parsed . prompt , S = CURRENT . parsed . score ;
const lessonPhase = sliceSection ( L , phaseText , 2 ) || CURRENT . raw . lesson ;
const promptPhase = sliceSection ( P , phaseText , 2 ) || CURRENT . raw . prompt ;
const scorePhase = sliceSection ( S , phaseText , 2 ) || CURRENT . raw . score ;
// 在各 phase 片段里再找 ### step
const subL = parseHeadings ( lessonPhase ) ;
const subP = parseHeadings ( promptPhase ) ;
const subS = parseHeadings ( scorePhase ) ;
const lText = sliceSection ( subL , stepText , 3 ) || lessonPhase ;
const pText = sliceSection ( subP , stepText , 3 ) || promptPhase ;
const sText = sliceSection ( subS , stepText , 3 ) || scorePhase ;
document . getElementById ( 'editor-lesson' ) . value = lText ;
document . getElementById ( 'editor-prompt' ) . value = pText ;
document . getElementById ( 'editor-score' ) . value = sText ;
}
function loadStepToEditors ( phaseText , stepText ) {
// 三份文档里都取对应 phase/step 的片段(不存在则回退)
const L = CURRENT . parsed . lesson , P = CURRENT . parsed . prompt , S = CURRENT . parsed . score ;
const lessonPhase = sliceSection ( L , phaseText , 2 ) || CURRENT . raw . lesson ;
const promptPhase = sliceSection ( P , phaseText , 2 ) || CURRENT . raw . prompt ;
const scorePhase = sliceSection ( S , phaseText , 2 ) || CURRENT . raw . score ;
// 在各 phase 片段里再找 ### step
const subL = parseHeadings ( lessonPhase ) ;
const subP = parseHeadings ( promptPhase ) ;
const subS = parseHeadings ( scorePhase ) ;
const lText = sliceSection ( subL , stepText , 3 ) || lessonPhase ;
const pText = sliceSection ( subP , stepText , 3 ) || promptPhase ;
const sText = sliceSection ( subS , stepText , 3 ) || scorePhase ;
document . getElementById ( 'editor-lesson' ) . value = lText ;
document . getElementById ( 'editor-prompt' ) . value = pText ;
document . getElementById ( 'editor-score' ) . value = sText ;
}
// Tab 切换
document . addEventListener ( 'click' , ( e ) => {
if ( e . target . classList . contains ( 'tab-btn' ) ) {
@@ -181,24 +556,57 @@ function parseHeadings(mdText) {
}
} ) ;
// 保存全部(把当前 三个编辑器的文本整体 PUT 回各自 文件)
document . getElementById ( 'save-all-btn' ) ? . addEventListener ( 'click' , async ( ) => {
const l = document . getElementById ( 'editor-lesson' ) . value ;
const p = document . getElementById ( 'editor-prompt' ) . value ;
const s = document . getElementById ( 'editor-score' ) . value ;
// 保存全部:将 三个编辑器中 的文本保存回相应的 markdown 文件
document . getElementById ( 'save-all-btn' ) ? . addEventListener ( 'click' , async ( ) => {
try {
const [ r1 , r2 , r3 ] = await Promise . all ( [
fetch ( CURRENT . links . lesson , { method : 'PUT' , body : l } ) ,
fetch ( CURRENT . links . prompt , { method : 'PUT' , body : p } ) ,
fetch ( CURRENT . links . score , { method : 'PUT' , body : s } ) ,
] ) ;
if ( r1 . ok && r2 . ok && r3 . ok ) alert ( '已保存' ) ;
else alert ( '保存失败,请重试' ) ;
// 将各自的步骤更新保存回文件
await saveUpdatedStep ( ) ;
} catch ( err ) {
console . error ( err ) ; alert ( '保存出错' ) ;
console . error ( '保存错误:' , err ) ;
alert ( '保存失败,请重试' ) ;
}
} ) ;
const STEP _TEMPLATES = {
lesson : ` 在这里编写本步骤的课程内容... ` ,
prompt : ` #### 指令
请严格遵循本步骤的教学目标,输出所需内容。
// 右侧详情开/关
function closeCourseDetails ( ) { document . getElementById ( 'courseDetails' ) . classList . remove ( 'open' ) ; }
function showInputForNewChapter ( ) { /* 省略:你的新增逻辑 */ }
#### 约束
- 使用简明、结构化的格式
- 语言:中文
#### 输出格式
- 小结
- 关键要点(列表) ` ,
score : ` #### 评分标准
- 维度A: ...
- 维度B: ...
- 维度C: ...
#### 打分
请输出 JSON:
{"score": 0-100, "reasons": ["...","..."]} `
} ;
const PHASE _TEMPLATES = {
lesson : ` ### 新步骤
本阶段描述:请在此补充阶段说明。
` ,
prompt : ` ### 新步骤
阶段提示
为该阶段的任务撰写总体引导。
# 约束
- 输出语言:中文
` ,
score : ` ### 新步骤
阶段评分参考
- 维度A: ...
- 维度B: ...
`
} ;