feat: 专员 JSON 配置体系 + 配图提示词规则重构

- 新增配图提示词 JSON 规则(image_prompt.json):封面图 + 每 500 字按字数分布
- 新增 Go 配置加载器(weixin_public_account_image_prompt_config.go):从嵌入的 spec 目录读取 JSON 配置
- 新增 weixin_public_account 完整 spec 目录:skill.md, workflow.json, domains.json, policy.json, validators/
- 新增 pj0012 参考文件(pj0012-wechat-image-spec.md):配图规则 + 专员 JSON 定义体系
- 产物 Tab 添加下载按钮 + 整体导出区
- 修复前端 Markdown 渲染 bug + 步骤消息分段问题

所有专员定义向 JSON 配置迁移,以 weixin_public_account 为样板。
This commit is contained in:
eaiadmin
2026-09-24 23:01:06 +08:00
parent 89ae31c998
commit 011f722999
23 changed files with 1642 additions and 18 deletions
@@ -158,9 +158,9 @@
class="oa-btn oa-btn-tiny"
type="button"
:disabled="previewLoading"
@click="openArtifactPreview(file)"
@click="downloadFile(file)"
>
预览
下载
</button>
</div>
</div>
@@ -169,6 +169,50 @@
</template>
</div>
</section>
<!-- 整体导出区:所有格式一次导出 -->
<section class="oa-stack-section">
<div class="oa-stack-head">
<span class="oa-stack-title">整体导出</span>
</div>
<div v-if="hasTask" class="oa-export-row">
<button
class="oa-btn"
type="button"
:disabled="loading"
@click="download('preview_md')"
>
预览 MD
</button>
<button
class="oa-btn"
type="button"
:disabled="loading"
@click="download('preview_html')"
>
预览 HTML
</button>
<button
class="oa-btn"
type="button"
:disabled="loading"
@click="download('pagemd')"
>
分页 MD
</button>
<button
class="oa-btn"
type="button"
:disabled="loading"
@click="download('docx')"
>
Word (.docx)
</button>
</div>
<div v-else class="oa-empty oa-empty-inline">
没有可导出的内容,先运行创作流程。
</div>
</section>
</template>
<template v-else>
@@ -1085,6 +1129,40 @@ async function download(format) {
}
}
// 从产物文件下载(HTML / MD 直接下载,图片通过 API 获取)
async function downloadFile(file) {
loading.value = true
try {
let blob = null
let filename = file.name || 'download'
if (file.kind === 'html' && file.html) {
blob = new Blob([file.html], { type: 'text/html' })
} else if (file.kind === 'markdown' && file.text) {
blob = new Blob([file.text], { type: 'text/markdown' })
} else if (file.kind === 'image' && file.url) {
blob = await fetchWeixinPublicAccountGeneratedImage(file.url)
if (file.name) filename = file.name
}
if (blob) {
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = filename
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
URL.revokeObjectURL(url)
ElMessage.success('下载成功')
}
} catch (error) {
ElMessage.error(error?.message || '下载失败')
} finally {
loading.value = false
}
}
function exportFileName(format) {
if (format === 'preview_html') return 'weixin-public-account-preview.html'
if (format === 'pagemd') return 'weixin-public-account-pagemd.md'
@@ -474,7 +474,13 @@ function renderMarkdownBlock(block) {
const headingMatch = trimmed.match(/^(#{1,3})\s+(.+)$/)
if (headingMatch) {
const level = headingMatch[1].length
return `<h${level}>${renderInlineMarkdown(headingMatch[2])}</h${level}>`
const heading = `<h${level}>${renderInlineMarkdown(headingMatch[2])}</h${level}>`
// If the block has more lines after the heading, render them as additional content
const afterHeading = trimmed.split('\n', 2)[1]
if (afterHeading && afterHeading.trim()) {
return heading + ' ' + renderMarkdownBlock(afterHeading.trim())
}
return heading
}
const lines = trimmed.split('\n')
@@ -792,7 +798,7 @@ function getWeixinPublicAccountStep(payload, stepKey) {
return payload?.workflow?.steps?.[stepKey] || {}
}
function trimWeixinPublicAccountText(value, maxLength = 180) {
function trimWeixinPublicAccountText(value, maxLength = 360) {
const text = String(value || '').trim()
if (!text) return ''
return text.length > maxLength ? `${text.slice(0, maxLength).trim()}...` : text
@@ -876,7 +882,7 @@ function buildWeixinPublicAccountOutlineStageMessage(payload) {
'### 3. 提纲步骤已完成',
sections.length
? `本轮提纲主结构:\n${formatWeixinPublicAccountBulletList(sections)}`
: trimWeixinPublicAccountText(outline, 220) || '本轮提纲已生成。',
: trimWeixinPublicAccountText(outline, 500) || '本轮提纲已生成。',
].filter(Boolean).join('\n\n'),
timestamp: new Date().toLocaleTimeString(),
}
@@ -892,7 +898,7 @@ function buildWeixinPublicAccountContentStageMessage(payload) {
.replace(/^>\s+.*$/gm, '')
.replace(/\n{2,}/g, '\n')
.trim(),
220,
500,
)
return {
role: 'assistant',
@@ -910,15 +916,18 @@ function buildWeixinPublicAccountImagePromptStageMessage(payload) {
const output = step.output || {}
const count = Number(output.count || 0)
const prompts = Array.isArray(output.image_prompts) ? output.image_prompts.slice(0, 4) : []
const lines = [
`已提炼 ${count} 条配图提示词。`,
...(prompts.length
? prompts.map((item, index) => `${index + 1}. [${item?.section || '段落配图'}] ${trimWeixinPublicAccountText(item?.prompt || '', 80)}`)
: []),
]
return {
role: 'assistant',
content: [
'### 5. 配图提示词步骤已完成',
count > 0 ? `已提炼 ${count} 条配图提示词。` : '本轮未提炼出可用配图提示词。',
...(prompts.length
? prompts.map((item, index) => `${index + 1}. [${item?.section || '段落配图'}] ${trimWeixinPublicAccountText(item?.prompt || '', 80)}`)
: []),
].filter(Boolean).join('\n'),
lines.length ? lines.join('\n') : '本轮未提炼出可用配图提示词。',
].filter(Boolean).join('\n\n'),
timestamp: new Date().toLocaleTimeString(),
}
}
@@ -929,15 +938,18 @@ function buildWeixinPublicAccountImageGenerationStageMessage(payload) {
const output = step.output || {}
const count = Number(output.count || 0)
const refs = Array.isArray(output.image_refs) ? output.image_refs.slice(0, 4) : []
const lines = [
`已生成 ${count} 张配图。`,
...(refs.length
? refs.map((item, index) => `${index + 1}. [${item?.section || '段落配图'}] ${trimWeixinPublicAccountText(item?.prompt || '', 60)}`)
: []),
]
return {
role: 'assistant',
content: [
'### 6. 图片生成步骤已完成',
count > 0 ? `已生成 ${count} 张配图。` : '本轮未生成图片结果。',
...(refs.length
? refs.map((item, index) => `${index + 1}. [${item?.section || '段落配图'}] ${trimWeixinPublicAccountText(item?.prompt || '', 60)}`)
: []),
].filter(Boolean).join('\n'),
lines.length ? lines.join('\n') : '本轮未生成图片结果。',
].filter(Boolean).join('\n\n'),
timestamp: new Date().toLocaleTimeString(),
}
}
@@ -952,7 +964,7 @@ function buildWeixinPublicAccountPreviewStageMessage(payload) {
content: [
'### 7. 预览与导出步骤已完成',
`已整合正文${imageCount ? `与 ${imageCount} 张配图` : ''}生成预览稿,可检查排版并导出。`,
].filter(Boolean).join('\n'),
].filter(Boolean).join('\n\n'),
timestamp: new Date().toLocaleTimeString(),
}
}
@@ -967,7 +979,7 @@ function buildWeixinPublicAccountPageMarkdownStageMessage(payload) {
content: [
'### 8. 分页 MD 步骤已完成',
count > 0 ? `已按段落结构生成 ${count} 页分页 MD。` : '分页 MD 已生成。',
].filter(Boolean).join('\n'),
].filter(Boolean).join('\n\n'),
timestamp: new Date().toLocaleTimeString(),
}
}