visual-ops-writer
图文运营创作器。从选题、撰写、配图到质检,一条龙完成运营文章生产。看到好文章能仿写,给个 Skill 能生成推广长文,写完正文能自动配图。内置写作质量管控与自动配图能力,图文一体交付。触发词:"生成运营文章"、"链接仿写"、"对标分析"、"Skill 推广"、"为正文配图"。
Install
npx skills add https://github.com/redfox-data/redfox-community/tree/main/skills/visual-ops-writer
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install redfox-data-redfox-community@llmmart
git clone https://github.com/redfox-data/redfox-community.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole redfox-data/redfox-community collection as a plugin from our marketplace. Git is the plain clone.
README
图文运营创作器 / visual-ops-writer
简介
从选题、撰写、配图到质检,覆盖运营文章的完整创作链路。看到好文章能仿写,给个 Skill 能生成推广长文,写完正文能自动配图——图文一体交付,拿来就能用。
核心价值
- 三种场景,一个工具:链接仿写、产品推广、正文配图——内容运营最高频的三个创作场景,无需来回切换工具。
- 图文一体交付:文章与配图同步生成,保存为完整可用的成品文件,不是半成品。
- 质量内置:每篇文章自动经过 9 项质量检查,输出质检报告,交付即可用。
适用对象
- 📝 内容运营 — 快速产出对标风格文章与产品推广文,从「写到配」一条龙搞定。
- 🛍️ 品牌 / 技能开发者 — 自动生成对齐产品能力的推广长文,内容与功能不脱节。
- 🏢 博主 / 编辑 — 为已有正文智能配图,省去手动规划配图位置的麻烦。
功能特性
核心功能
- 链接仿写:提供任意文章链接,自动抓取原文内容与图片,学习其风格与结构,仿写出角度不同但质量相当的新文章。配图风格与原图保持一致,适合对标分析与竞品内容学习。
- Skill 推广文:提供 Skill 文件路径或名称,自动解析产品能力并抓取目标站点最新信息,生成与产品实际能力对齐的推广长文。配图采用红狐讲解员风格,视觉与内容双重匹配。
- 正文补图:提供完整正文,自动按章节语义与字数密度分析文章结构,智能决定配图位置与数量,批量生成风格统一的配图。适合写完正文不想手动找图的场景。
- 自动质量检查:每篇文章生成后自动执行 9 项质量检查(标题长度、章节数、表格数、字数、FAQ 数量、禁忌词、段落行数、信息密度等),输出 JSON 格式质检报告。
写作能力
- 文章类型自动匹配:工具推介型、方法论型、危机应对型三种模板,根据选题自动选择最合适的写作结构
- 读者画像适配:支持个体创作者和团队/企业两种读者定位,自动调整表达侧重
- 语调可选:直白坦率 / 专业严谨 / 轻松对话,三种语调自由切换
- 字数可控:支持自定义字数区间(默认 2000-3000 字),也可按原文篇幅自适应
配图能力
- 风格迁移:链接仿写模式下,原图作为视觉参照,新内容自动生成,风格一致
- 语义匹配:配图按章节内容自动匹配讲解员姿态(欢迎、分析、思考、操作)
- 智能断章:按 H2 章节切分 + 字数密度自动决策生图点,跳过不适合配图的位置
- 数量可调:0-5 张配图上限可调,也可完全交由系统自动决策
密钥获取与安全说明
- 本技能需要使用环境变量:
REDFOX_API_KEY。 REDFOX_API_KEY由 红狐 hub (https://redfox.hk)- 请前往 红狐 hub 注册账号,获取
REDFOX_API_KEY。 - 配置设备环境变量
REDFOX_API_KEY后使用本技能。 - 在提供密钥前,请先确认密钥来源、可用范围、有效期及是否支持重置/撤销。
- 禁止在代码、提示词、日志或输出文件中硬编码/明文暴露密钥。
使用指南
直接用自然语言描述需求,无需记忆命令。
常用说法速查
| 意图 | 示例话术 | 效果 |
|---|---|---|
| 链接仿写 | 「帮我仿写这篇文章,保持风格但换成我的产品」 | 学习原文风格与结构,生成配图一致的全新文章 |
| Skill 推广 | 「帮我给这个 Skill 写一篇推广文」 | 自动生成对齐产品能力的推广长文与配图 |
| 正文补图 | 「帮我给这篇正文加上配图」 | 智能分析文章结构,自动决定配图位置并生成 |
| 竞品分析 | 「分析这篇文章的写法,帮我写一篇不同角度的」 | 学习优秀内容的结构与风格,产出差异化内容 |
输出示例
完成生成后,你将收到:
- 完整文章:保存为
article.md,含标题、正文、配图(嵌入对应章节) - 质检报告:
validation_report.json,含 9 项文章质量检查结果 - 对话内全文:直接在对话中输出文章全文
- 反馈节点:如需调整某章节或配图,可告知具体修改方向
使用场景
| 场景 | 角色 | 示例问法 | 收益 |
|---|---|---|---|
| 对标仿写 | 内容运营 | 「帮我仿写这篇文章,保持风格但换成我的产品」 | 快速产出风格一致的高质量文章,省去从零构思 |
| Skill 推广 | 技能开发者 | 「帮我给这个 Skill 写一篇推广文」 | 自动生成对齐产品能力的推广文章,内容与功能不脱节 |
| 文章配图 | 博主/编辑 | 「帮我给这篇正文加上配图」 | 智能选择配图位置与数量,无需手动规划 |
| 竞品分析 | 品牌运营 | 「分析这篇文章的写法,帮我写一篇不同角度的」 | 学习优秀内容的结构与风格,产出差异化内容 |
Skill manifest
图文运营创作器
从选题、撰写、配图到质检,覆盖运营文章的完整创作链路。你只需要提供素材——一篇文章链接、一个 Skill、或者一段写好的正文,剩下的交给它。
核心执行流程详见
references/core_workflow.md
简介
图文运营创作器解决的是内容团队最常见的效率瓶颈:一篇文章从写到配,往往需要多人协作、反复打磨。
通过这个工具,你可以:
- 看到一篇好文章,直接仿写为自己的版本,风格一致但内容全新
- 给一个 Skill 或产品,自动生成一篇专业的推广长文,配图到位
- 写完正文不想找图,丢进来自动判断哪里该配图、配什么风格
三种场景,一个工具全覆盖。
适用对象
- 📝 内容运营 — 快速产出对标风格文章与产品推广文,从写到配一条龙搞定
- 🛍️ 品牌 / 技能开发者 — 自动生成对齐产品能力的推广长文,内容与功能不脱节
- 🏢 博主 / 编辑 — 为已有正文智能配图,省去手动规划配图位置的麻烦
功能特性
🎯 核心功能
- 🔗 链接仿写:提供任意文章链接,自动抓取原文内容与图片,学习其风格与结构,仿写全新文章。配图风格与原图保持一致,适合对标分析与竞品内容学习。
- 📣 Skill 推广文:提供 Skill 文件路径或名称,自动解析产品能力并抓取目标站点最新信息,生成与产品实际能力对齐的推广长文。配图采用红狐讲解员风格,视觉与内容双重匹配。
- 🖼️ 正文补图:提供完整正文,自动按章节语义与字数密度分析文章结构,智能决定配图位置与数量,批量生成风格统一的配图。适合写完正文不想手动找图的场景。
- ✅ 自动质量检查:每篇文章生成后自动执行 9 项质量检查(标题长度、章节数、表格数、字数、FAQ 数量、禁忌词、段落行数、信息密度等),输出 JSON 格式质检报告。
写作质量管控
不是随便写一篇就交差。文章生成过程内置完整的质量管控体系:
- 文章类型自动匹配:根据选题自动识别为工具推介型、方法论型或危机应对型,每种类型有独立的写作侧重与结构建议
- 读者画像适配:支持个体创作者和团队/企业两种读者定位,自动调整表达侧重(个人可执行方法 vs 工具系统协同)
- 语调可选:直白坦率 / 专业严谨 / 轻松对话,默认直白坦率
- 成文字数可控:支持自定义字数区间(默认 2000-3000 字),链接仿写按原文篇幅自适应
自动配图能力
文章配图不是简单的"插张图",而是内容的一部分:
- 风格迁移:链接仿写模式下,原图作为风格参照,新内容自动生成,视觉风格保持一致
- 语义匹配:Skill 推广模式下,配图按章节语义自动匹配讲解员姿态(欢迎、分析、思考、操作等)
- 智能断章:正文补图模式下,按 H2 章节切分 + 字数密度自动决策生图点,跳过 FAQ / 结语 / 引言等不适合配图的位置
- 数量可控:配图数量上限 0-5 张可调,也可由系统自动决策
一键安装
前置条件
- Python 3.8+(用于运行配图生成与质量检查脚本)
REDFOX_API_KEY(用于图片生成,未配置时仍可生成纯文本文章)
安装步骤
- 将本 Skill 目录添加到你的 Agent 技能列表
- 获取
REDFOX_API_KEY:前往 红狐 hub 注册账号,在 API Keys 页面 创建密钥 - 配置环境变量:
# macOS / Linux
export REDFOX_API_KEY="your_key_here"
# Windows PowerShell
$env:REDFOX_API_KEY="your_key_here"
- 验证安装:告诉 Agent「帮我给这个 Skill 写一篇推广文」,确认技能正常响应
环境变量说明
| 变量名 | 必填 | 说明 |
|---|---|---|
REDFOX_API_KEY |
条件必填 | 图片生成服务密钥;未配置时文章正常生成,图片降级为占位符或跳过 |
使用指南
基础使用
1. 链接仿写
告诉 Agent 你想仿写的文章链接:
用户:帮我仿写这篇文章,保持风格但换成我的产品 https://example.com/some-article
Agent:已抓取原文,正在学习风格与结构…文章已生成,含 3 张配图,保存为 article.md。
2. Skill 推广文
提供 Skill 文件或名称:
用户:帮我给 xhs-ranking 这个 Skill 写一篇推广文
Agent:已解析 Skill 能力并抓取目标站点信息,推广文章已生成,含红狐风格配图 3 张。
3. 正文补图
粘贴正文或提供文件路径:
用户:帮我给这篇正文加上配图 (粘贴正文内容)
Agent:正文共 1800 字,已识别 4 个配图位置,配图生成中…完成,保存为 article.md。
高级使用
- 指定语调:「用专业严谨的语调写」
- 指定读者:「目标读者是团队企业」
- 指定配图数量:「最多配 2 张图」
- 关闭配图:「不需要配图」或「style 设为 none」
- 指定字数:「控制在 1500 字左右」
常用说法速查
| 意图 | 示例话术 | 效果 |
|---|---|---|
| 链接仿写 | 「帮我仿写这篇文章,保持风格但换成我的产品」 | 学习原文风格与结构,生成配图一致的全新文章 |
| Skill 推广 | 「帮我给这个 Skill 写一篇推广文」 | 自动生成对齐产品能力的推广长文与配图 |
| 正文补图 | 「帮我给这篇正文加上配图」 | 智能分析文章结构,自动决定配图位置并生成 |
| 竞品分析 | 「分析这篇文章的写法,帮我写一篇不同角度的」 | 学习优秀内容的结构与风格,产出差异化内容 |
| 控制语调 | 「用轻松对话的语气写」 | 按指定语调生成文章 |
| 关闭配图 | 「不需要配图」 | 仅输出纯文本文章 |
使用场景
| 场景 | 角色 | 示例问法 | 收益 |
|---|---|---|---|
| 对标仿写 | 内容运营 | 「帮我仿写这篇文章,保持风格但换成我的产品」 | 快速产出风格一致的高质量文章,省去从零开始的构思 |
| Skill 推广 | 技能开发者 | 「帮我给这个 Skill 写一篇推广文」 | 自动生成对齐产品能力的推广文章,内容与功能不脱节 |
| 文章配图 | 博主/编辑 | 「帮我给这篇正文加上配图」 | 智能选择配图位置与数量,无需手动规划 |
| 竞品分析 | 品牌运营 | 「分析这篇文章的写法,帮我写一篇不同角度的」 | 学习优秀内容的结构与风格,产出差异化内容 |
输出说明
完成生成后,你将收到:
- 完整文章:保存为
article.md,含标题、正文、FAQ(如有)、配图(以嵌入对应章节) - 质检报告:
validation_report.json,含 9 项文章质量检查结果 - 对话内全文:直接在对话中输出文章全文
- 反馈节点:如需调整某章节或配图,可告知具体修改方向
常见问答
Q1: 没有 API Key 能用吗?
A: 可以。没有 REDFOX_API_KEY 时,文章正文正常生成,图片部分降级处理:链接仿写用占位符替代,Skill 推广和正文补图跳过配图,仅输出纯文本。
Q2: 生成的文章可以手动调整吗?
A: 可以。生成后直接告诉 Agent 你想调整的方向(如「第三段改短一点」「换一张配图」),Agent 会基于现有文章定向修改。
Q3: 配图风格可以自定义吗?
A: 支持三种风格:redfox(红狐讲解员)、reference(跟随原图风格)、none(无风格修饰)。告诉 Agent 你的偏好即可。
项目架构
visual-ops-writer/
├── SKILL.md # 技能入口文件
├── references/
│ ├── core_workflow.md # 核心执行流程(Agent 执行链)
│ ├── writing-framework.md # 写作框架参考
│ ├── article-type-templates.md # 文章类型模板(工具推介/方法论/危机应对)
│ ├── persona-matrix.md # 语气×读者×视觉 IP 配置矩阵
│ ├── image-decision-rules.md # 配图决策规则
│ ├── link-rewrite-guide.md # 链接仿写指南
│ ├── prompt-templates.md # 提示词模板
│ └── error-handling.md # 错误处理
├── scripts/
│ ├── fetch_article.py # 文章抓取与解析
│ ├── validate_article.py # 机器化质量检查
│ ├── generate_image.py # 图片生成(支持文生图与风格迁移)
│ └── decide_images.py # 配图位置与数量决策
└── assets/
├── 02.jpg # 素材图片
└── 03.jpg # 素材图片
Files (redfox-community)
-
assets
-
02.jpg 487.7 KB · in bundle
-
03.jpg 344.4 KB · in bundle
-
-
references
-
article-type-templates.md 7.6 KB
# 三种文章类型 Prompt 模板(v3.0) > 工具推介 / 方法论 / 危机应对三类型的生产逻辑不同,**参考模板**帮助你快速搭骨架。 > **使用原则**:模板从"强制"改为"参考"——可裁剪、可调整、可混用,按 Skill 逻辑和选题需要动态调整。 --- ## 使用原则 1. **模板是参考线,不是栅栏**——按选题和读者需求灵活调整 2. **章节数动态**——可在 2-8 节区间自由选择 3. **表格按需出现**——0 张表也合规(叙事/资讯类常见) 4. **字数跟着选题走**——默认 1000 字左右,可通过 `--target-length` 自定义 5. **混用更自由**——可引入其他模板的章节,不再硬性限制"每篇最多 1 个" --- ## 模板 A:工具推介型 **适用场景**:新工具发布、工具评测、Skill 推广 **核心生产逻辑**:功能清单 vs 痛点对照 - 第 1 节:问题诊断——读者此刻在为什么发愁 - 第 2 节:功能列表——这个工具具体能做什么 - 第 3 节:使用指南——3 步或 5 步可执行流程 - 第 4 节:对标/对比——和同类工具的差异 ### Prompt 模板 ```markdown 你正在写一篇【工具推介型】运营文章。 【参考章节】 1. ## 为什么你需要这个工具 - 建议包含 1 个用户具体场景 - 建议包含 2-3 个常见误区或失败模式 - 字数参考:200 字左右 2. ## 它到底能做什么 - 建议用 1 张功能清单表格 - 每行功能 = 痛点 + 解决方案 + 实际收益 - 字数参考:250 字左右 3. ## 3 步上手:从打开到产出 - 建议用编号列表(1/2/3 步骤) - 每步骤配 1 个具体输入示例 - 字数参考:300 字左右 4. ## 和同类工具相比强在哪 - 建议用 1 张对比表(维度 ≥ 3) - 建议写出本工具不适用场景 - 字数参考:250 字左右 【输出风格参考】 - 语气:直白坦率 + 经验分享感 - 段落:每段不超过 5 行 - 数字:每节至少 1 个数据/百分比/时间 - 禁忌词:禁止"赋能、打法、闭环、对齐、沉淀、颗粒度、方法论前置" ``` **调整建议**: - 短工具推介(300-500 字):只保留"为什么需要 + 3 步上手"两节即可 - 工具 + 行业背景(1500+ 字):可加 1 节"行业现状"作为引言前置 --- ## 模板 B:方法论型 **适用场景**:系统化方法输出、SOP 拆解、流程优化 **核心生产逻辑**:框架 + 步骤 + 标准 - 第 1 节:场景分析(什么时候用这套方法) - 第 2 节:方法论框架(核心理念 + 适用范围) - 第 3 节:工具与渠道(怎么落地) - 第 4 节:评估标准(怎么判断做得好不好) - 第 5 节:执行清单(周一/周二/周三做什么) ### Prompt 模板 ```markdown 你正在写一篇【方法论型】运营文章。 【参考章节】 1. ## 什么时候用这套方法 - 描述典型应用场景 - 给出"信号清单"——出现这 3 个信号就该用 - 字数参考:200 字左右 2. ## 方法论框架 - 建议含 1 个核心模型(3-5 步或 3-5 维) - 建议用 1 张框架表格或矩阵图 - 字数参考:350 字左右 3. ## 工具与渠道选择 - 推荐 2-3 个具体工具/方法 - 说明每个工具的"使用边界" - 字数参考:300 字左右 4. ## 评估标准与红线 - 建议含 1 张评估表(KPI + 阈值) - 红线:3-5 条"绝对不能做"的事 - 字数参考:250 字左右 5. ## 30 天执行清单(可选,方法论长文适用) - 按周划分 - 每周 3-5 个具体动作 - 字数参考:200 字左右 【输出风格参考】 - 语气:专业严谨 + 经验分享 - 段落:每段不超过 5 行 - 数字:每节至少 2 个可量化指标 - 禁忌词:禁止"赋能、打法、闭环、对齐、沉淀、颗粒度、方法论前置" ``` **调整建议**: - 极简方法论(500 字内):只保留"场景 + 框架"两节 - 完整方法论(1500+ 字):保留全部 5 节 - 短方法论:可省"30 天执行清单",用 1 段总结代替 --- ## 模板 C:危机应对型 **适用场景**:品牌危机、产品 bug、行业风波、负面舆情 **核心生产逻辑**:时间线 + 决策树 + 损失控制 - 第 1 节:风险识别(危机长什么样) - 第 2 节:分级分类(严重程度判断) - 第 3 节:响应流程(24h / 72h / 7 天) - 第 4 节:协同动作(PR/客服/法务/产品各做什么) ### Prompt 模板 ```markdown 你正在写一篇【危机应对型】运营文章。 【参考章节】 1. ## 危机长什么样:4 类早期信号 - 建议用 1 张分类表(4 类信号 + 严重度) - 每类配 1 个真实案例(或脱敏案例) - 字数参考:250 字左右 2. ## 3 级响应机制 - 黄色(24h 内响应,影响 < 1000 用户) - 橙色(4h 内响应,影响 1000-10000 用户) - 红色(30min 内响应,影响 > 10000 用户 或 媒体介入) - 每级建议列出 5 项具体动作 - 字数参考:350 字左右 3. ## 跨部门协同:谁先动、怎么联动 - 建议含 1 张 RACI 矩阵 - 至少 4 个角色:PR / 客服 / 法务 / 产品 - 字数参考:300 字左右 4. ## 损失控制与复盘 - 建议含 1 张复盘清单(事后 24h / 7 天 / 30 天动作) - 给出 3 条"绝对不能做"的事 - 字数参考:250 字左右 【输出风格参考】 - 语气:直白坦率 + 专业警告 - 段落:每段不超过 5 行 - 数字:每节至少 1 个时间节点(X 分钟/X 小时/X 天) - 禁忌词:禁止"赋能、打法、闭环、对齐、沉淀、颗粒度、方法论前置" ``` **调整建议**: - 单一危机案例(500-800 字):只保留"信号 + 响应"两节 - 完整危机手册(2000+ 字):保留全部 4 节 - 危机新闻类(300 字):用"信号 + 案例 + 1 句话建议"三段式 --- ## 模板选择决策表 | 选题特征 | 推荐模板 | 灵活度 | |---------|---------|--------| | 含"工具/Skill/产品"关键词 | A 工具推介型 | 可裁剪到 2 节 | | 含"方法/体系/流程/SOP"关键词 | B 方法论型 | 可扩到 6-7 节 | | 含"危机/处理/应对/舆情/紧急"关键词 | C 危机应对型 | 时间维度可弱化 | | 选题模糊不确定 | 优先 B(最通用) | 完全自由 | | **叙事/资讯类短文** | **不需要套模板** | 自由发挥 | --- ## 模板混用规则 可在主模板基础上,引入其他模板的章节——**不再硬性限制数量**: - A + 危机:工具推介文中加"常见踩坑与避雷"(C 章节) - B + 工具:方法论文中加"推荐工具对比"(A 章节) - C + 工具:危机应对文中加"应急工具箱"(A 章节) - B + C:方法论文中加"红线警示"(C 章节) **原则**:混用服务于内容,不为"凑结构"而混用。 --- ## 自检对照 生成后,**建议**用 `validate_article.py` 验证: ```bash # 默认 soft 模式:所有阈值均为 warning python scripts/validate_article.py --input article.md # 自定义字数目标 python scripts/validate_article.py --input article.md --target-length 1200 # 严格模式(恢复硬 fail,仅在 CI 严格拦截时使用) python scripts/validate_article.py --input article.md --strict ``` **软阈值**(超范围不直接 fail): - 标题 8-30 字 - 章节 2-8 节 - 表格 0-3 张(0 张也合规) - 字数 500-1500(目标 1000) - FAQ 0 或 2-4 个 **核心原则**: - 模板是**生成时的脚手架**,不是**生成后的验收标准** - 校验脚本只提供"建议改进",不强制"必须修改" - Skill 的写作框架应跟随选题、读者、Skill 逻辑动态调整 -
core_workflow.md 11.9 KB
# Core Workflow > 本文件从 SKILL.md 提取的核心执行流程,供 Agent 内部执行时引用。 --- ## 一、写作框架 ### 写作者身份 独立撰稿人,专写新媒体工具类的深度推广文章。不是品牌小编,不是 SEO 写手——是"自己用过很多烂工具、踩过很多坑"的人。 ### 8 条写作铁律(不可违反) 1. 开头 3 句话内产生「这就是我」的感觉 2. 全文至少有一个微型案例 3. 每个段落不超过 5 行 4. 功能不列清单(每说一个功能,必须附带一句「对你来说意味着什么」) 5. 禁用行业黑话(赋能、抓手、闭环、底层逻辑、颗粒度、打法、心智、调性) 6. 表格最多用 2 次 7. 结尾不给总结(给读者一个画面或一个动作) 8. 不用结束信号词(「最后」「综上所述」「总而言之」) ### 9 步文章结构(自然过渡,不套小标题) 1. 痛点场景(200-300字) 2. 产品一句话定位(50-100字) 3. 跟竞品的本质区别(200-300字) 4. 一个场景案例(150-200字) 5. 核心能力展开(300-400字) 6. 用户画像(200字) 7. 上手路径(200字) 8. FAQ(可选,150字) 9. 结尾(100-150字) ### 语调规范 像行业里干了 5 年的人跟新同事聊天,刻薄指向「烂工具」而非读者,共情方式是「我知道你经历了什么」。 --- ## 二、模式自动推断规则 按以下顺序判断输入类型,匹配则进入对应模式: 1. 用户传入 `sourceUrl`(以 http/https 开头)→ **mode=link_rewrite** 2. 用户传入 `skillFile` 路径(指向 .md 文件)→ **mode=skill_promo** 3. 用户传入 `skillName`(字符串)→ **mode=skill_promo** 4. 用户传入 `articleContent`(含 H2 标题的 markdown 文本 或 文件路径)→ **mode=add_images** 5. 都不匹配 → 报错并提示用户提供三选一的输入 --- ## 三、生成流程 ### Step 1: 数据采集(按模式分支) #### 模式 A: link_rewrite ```bash python scripts/fetch_article.py --url "<sourceUrl>" --output article.json ``` 输出 JSON 含 `title / sections / images`。详见 [references/link-rewrite-guide.md](link-rewrite-guide.md) **视觉学习**(浅层 i2i 模式): - 不做深度视觉解构 - 直接拿原图 URL 作为 `--reference-image` 透传给生图接口 - 由生图模型自身完成风格迁移 #### 模式 B: skill_promo 1. **Skill 文件解析**:读取 skillFile 或下载 skillName 对应内容 2. **目标站点抓取**:访问 targetSiteUrl,获取最新 Skills 列表与功能特性 3. **内容融合校验**:将两者信息交叉比对,确保与产品实际能力对齐 #### 模式 C: add_images - 解析用户提供的 markdown 正文 - 章节切分 + 字数统计 + 锚点检测 - 详见 Step 10 配图决策 ### Step 2: 选题定位 模式 A:从源文章提取核心论点作为新文章主题 模式 B:从三个方向锁定(平台动态 / 用户痛点 / 行业困局) 模式 C:使用用户提供的标题与主题 选题须满足:高频重复性问题、有明确失败案例、有可量化成功指标。 ### Step 3: 确定文章类型与读者画像 根据选题自动匹配文章类型(工具推介型 / 方法论型 / 危机应对型),详见 [references/article-type-templates.md](article-type-templates.md) ### Step 4: 生成标题 从公式中选择(建议):疑问式+方法论指向 / 否定+限制词 / 纠正认知+数字+承诺 ### Step 5: 撰写引言 四段式(场景→误区→认知→方案)适用于长文/方法论类。短文可以直接开场白或一段场景描述。 ### Step 6: 动态生成正文章节 按选题与素材**自然生成**,不预设结构;结构服务于内容。 **软建议**(可参考,不强制): - 章节数 2-8 节,3-4 节为最常见 - 表格**按需出现**:信息适合用表呈现就加,不适合就不加 - 信息密度保持 1+ 个数据/案例/对比即可 ### Step 7: 生成 FAQ 按用户实际可能问的问题数量动态生成,2-4 个为常见区间,0 个也允许。 答案为行动指引,而非理论解释。 ### Step 8: 撰写结语 结语应该自然,不为"凑结构"硬写。常见结语形态: - 一句金句收尾(短文、叙事类适用) - 一段行动指引(教程类适用) - 不写结语(纯资讯快讯) ### Step 9: 质量自检(机器化验证,必须执行) ```bash python scripts/validate_article.py --input article.md python scripts/validate_article.py --input article.md --target-length 1200 python scripts/validate_article.py --input article.md --strict ``` 输出 `validation_report.json`,含 9 项检查。全部为 warning 级别,不直接 fail。 ### Step 10: 生成配图(按模式分支) **统一脚本入口**:`python scripts/generate_image.py` #### 模式 A: link_rewrite ```bash python scripts/generate_image.py \ --prompt "<基于新文章该章节的中文prompt>" \ --reference-image "<原文章中的图片URL>" \ --style reference \ --image assets/section_1.jpg \ --api-key "{apiKey}" ``` **关键约束**: - 不强制叠加红狐风格(`--style reference` 或 `none`) - 原图作风格参照,新 prompt 控制内容 - 配图数量与原文章保持一致 - 配图位置与原文章保持一致 #### 模式 B: skill_promo ```bash python scripts/generate_image.py \ --prompt "<中文prompt>" \ --image assets/skill标题.jpg \ --style redfox \ --api-key "{apiKey}" ``` **关键约束**: - 强制红狐讲解员 + 美式复古报刊风(`--style redfox`) - 配图数量由 imageCount 参数控制(默认 3) - 配图位置按章节语义匹配 #### 模式 C: add_images ```bash python scripts/decide_images.py --input article.md --output image_plan.json python scripts/generate_image.py --batch prompts.json --style redfox --api-key "{apiKey}" ``` **关键约束**: - **生图点由脚本自动决策**,不是用户预设 - 决策规则:H2 切分 + 字数密度(500-800字/张)+ 锚点词 + 标题视觉化关键词 - 跳过 FAQ / 结语 / 引言(字数 < 300 强制跳过) - 全篇生图数上限由 `--max-images` 控制(默认 6) - 风格默认 redfox,可通过 `--style none` 关闭 **详细决策规则**见 [references/image-decision-rules.md](image-decision-rules.md) ### Step 11: 组装输出 1. 图片 URL 以 `` 插入对应章节末尾 2. 完整文章保存为 `{outputDir}/article.md` 3. 对话中输出文章全文 4. 追加反馈节点 --- ## 四、写作风格规范 ### 修辞手法 | 手法 | 用途 | 示例 | |------|------|------| | 排比 | 堆砌问题 | "选错对标、抄错内容、追错热点……" | | 对比 | 表象 vs 本质 | "看起来是粉丝量差距,本质是内容结构差距" | | 转折递进 | 先认同再纠偏 | "看起来数据不错,但细看转化率一直在跌" | | 反问引导 | 引发思考 | "你有没有算过,每周找对标的时间值多少钱?" | ### 语气 - 直白坦率:「别只看粉丝数」 - 经验分享感:「更稳的做法是……」 - 同理心对话:「很多创作者都遇到过……」 - 用「你」不用「您」;短句为主,每段不超过 4 行 ### 信息密度 - 观点有数据/案例/对比支撑 - 段落间信息递进,不重复 - 每 400 字 1+ 个数据/动词开头的句子(叙事类不强制) ### 严格禁止 | 类型 | 禁止内容 | |------|---------| | 禁忌词 | 赋能、抓手、打法、闭环、颗粒度、对齐、沉淀、方法论前置 | | 禁忌句式 | 「在当今XXX的时代」「众所周知」「不言而喻」 | | 禁忌内容 | 学术术语堆砌、空泛励志、图文已覆盖处的重复描述 | --- ## 五、图片生成规范 ### 风格模式 | 模式 | 适用场景 | 行为 | |------|---------|------| | `redfox`(默认) | 模式 B、C 默认 | 强制叠加红狐讲解员 + 美式复古报刊风 | | `reference` | 模式 A 默认 | 原 prompt 不变,参考图自带风格 | | `none` | 用户明确要求 | 原 prompt 不变,无任何风格修饰 | ### 红狐讲解员姿态-章节语义映射 | 章节类型 | 狐狸姿态 | |----------|----------| | 封面/标题 | 欢迎姿态(站直挥手) | | 分析/洞察 | 前倾姿态(拿放大镜或教鞭) | | 思考/方法论 | 托腮姿态(沉思) | | 操作/工具 | 打字姿态(坐电脑前) | ### 核心原则 - 图片是内容承载体,不是装饰 - 每张图可独立替代对应文字说明 - 禁止装饰性边框 --- ## 六、质量检查清单 ### 机器验证(必跑) ```bash python scripts/validate_article.py --input article.md ``` ### LLM 主观判断 - [ ] **开头 3 句话内产生「这就是我」**(铁律 1) - [ ] **全文至少有一个微型案例**(铁律 2) - [ ] **没有段落超过 5 行**(铁律 3) - [ ] **功能描述都附带「意味着什么」**(铁律 4) - [ ] **没有禁用词**(铁律 5) - [ ] **表格真的需要**(铁律 6) - [ ] **结尾不给总结**(铁律 7) - [ ] **没有结束信号词**(铁律 8) - [ ] 标题符合公式且含核心关键词(建议) - [ ] FAQ 答案为"行动指引"而非"理论解释"(如有 FAQ) - [ ] 每章节通过"问题→用法→好处"自检 - [ ] 段落间信息递进,无断裂跳跃 - [ ] 文章类型(工具/方法论/危机)与选题匹配 - [ ] 面向读者(个人/团队)的表达侧重正确 ### 模式特化检查 #### 模式 A: link_rewrite - [ ] 新文章与源文章核心论点一致但角度不同 - [ ] 配图数量、位置与源文章对齐 - [ ] 配图风格通过 i2i 参照原图 - [ ] 未使用红狐风格(除非用户指定) #### 模式 B: skill_promo - [ ] Skill 功能描述与目标网站最新信息对齐 - [ ] 配图采用红狐风格 - [ ] 无夸大、过时、虚构描述 #### 模式 C: add_images - [ ] 生图点由 decide_images.py 决策,非人工指定 - [ ] 配图数量在 1-6 张合理区间 - [ ] 未在 FAQ / 结语 / 引言插图 - [ ] 配图位置在 H2 章节切分点之后 ### 图片合规 - [ ] 每张图可独立替代文字说明 - [ ] 风格模式与 mode 匹配 - [ ] 无装饰性边框 --- ## 七、思维方向速查 ### 核心思维链 1. 引言:场景 → 问题 → 方案 2. 正文:问题 → 方法 → 工具 → 标准 → 执行 3. FAQ:执行卡点 4. 结语:认知升维 ### 文章类型差异 | 类型 | 正文侧重 | 核心输出 | |------|----------|----------| | 工具推介型 | 问题诊断 → 功能介绍 → 使用指南 | 工具使用手册 | | 方法论型 | 场景分析 → 方法 → 工具 → 执行清单 | 系统解决方案 | | 危机应对型 | 风险识别 → 分层分析 → 协同动作 | 应急响应方案 | ### 读者差异 | 读者 | 表达侧重 | |------|----------| | 个体创作者 | 个人可执行方法、周计划、中腰部案例 | | 团队/企业 | 工具系统、协同流程、价值量化 | --- ## 八、机器化质检详细说明 `scripts/validate_article.py` 把质量检查从人工 checklist 升级为可机械验证的脚本,输出 JSON 报告。 **检查项**(全部为 warning 级别,不直接 fail): - 标题长度(8-30 字) - 章节数(2-8 节) - 表格数(0-3 张) - 字数(800-1500,link_rewrite 模式跳过) - FAQ 数量(2-4 个,0 也允许) - 资料来源(推荐存在) - 禁忌词(0 个) - 段落行数(每段 ≤ 5 行) - 信息密度(每 400 字 ≥ 1 数据/动词) --- ## 九、错误处理 详见 [references/error-handling.md](error-handling.md) ### 模式特化降级 | 场景 | 降级策略 | |------|----------| | 模式 A 源文章抓取失败 | 提示用户粘贴正文,回退到模式 C | | 模式 A 原图下载失败 | 跳过 i2i 参照,改用文生图(`--style redfox`) | | 模式 B Skill 文件解析失败 | 提示用户确认路径或名称 | | 模式 C 正文过短(< 500 字) | 不插图,提示「文章偏短,建议扩充后再配图」 | | API Key 缺失 | 跳过图片生成,输出纯文本文章 | | 配图生成失败 | 重试 1 次,仍失败则该位置用文字描述占位 | -
error-handling.md 1.1 KB
# 错误处理矩阵 | 场景 | 处理方式 | 降级级别 | |------|---------|---------| | 源文章链接无法抓取 | 提示用户粘贴文章正文内容或更换链接;若仍无法获取则回退到模式 B | 流程降级 | | 源文章图片 URL 不可访问 | 对应配图回退到 `assets/` 资源图生成 | 配图降级 | | API 密钥未提供 | 提示用户设置环境变量 `REDFOX_API_KEY` 或直接输入密钥;若仍无则跳过图片生成,仅输出文章文本 | 配图跳过 | | 图片提交失败 | 在文章对应位置标注 `[配图生成失败:提交接口异常]`,继续输出文章 | 单图失败 | | 图片内容审核不通过 | 在文章对应位置标注 `[配图生成失败:内容审核不通过]`,尝试换 prompt 重试一次 | 单图失败 | | 轮询超时 | 在文章对应位置标注 `[配图生成中,请稍后手动查询 taskId: xxx]`,继续输出文章 | 单图超时 | ## 降级策略优先级 ``` 源文章配图(最佳) → assets 资源图(次选) → 跳过配图仅文本(兜底) ``` 每级降级都会在输出中标注原因,确保用户知道发生了什么。 -
image-decision-rules.md 8.2 KB
# 断章生图决策规则(image-decision-rules.md) > 本文档说明 `decide_images.py` 的完整决策逻辑、参数调优、边界 case 处理。 > 当 `mode=add_images` 触发时,本规则集是生图点+数量的唯一权威来源。 --- ## 一、设计目标 为已有 markdown 正文**自动决定**: 1. **生几张图**(count) 2. **插在哪里**(after_section,N 表示插在第 N 节末尾) 3. **为什么插**(reason,可解释性) 原则: - **少而精**:默认 3-5 张,避免视觉过载 - **贴合内容**:在视觉信号强的位置插图,不在文字已表达充分的位置插图 - **可解释**:每个决策点都有明确 reason,调用方可在 prompt 中提示 LLM 调整 --- ## 二、决策流程(伪代码) ``` for each section in markdown.split_by_h2: if section.heading 包含 "FAQ/常见问题/结语/总结": 跳过 if section.word_count < 300: 跳过(字数硬下限) score = 0 score += section.word_count // 500 # 字数密度 score += 2 if section 包含强锚点词 # 强信号 score += 1 if section 包含弱锚点词 # 弱信号 score += 1 if section.heading 含视觉化关键词 # 标题信号 score += 1 if section 包含数据/列表/代码块 # 内容信号 if score >= threshold AND count_so_far < MAX_IMAGES: plan.append({after_section: idx, reason: ...}) return plan ``` --- ## 三、五大决策信号 ### 信号 1:字数密度(权重:中) **规则**: - 章节字数 ≥ 500 → 候选 +1 分 - 章节字数 ≥ 800 → 候选 +1 分(累计 +2) - 章节字数 < 300 → 强制跳过(不插图) **理由**: - 字数少的章节信息密度低,配图边际收益低 - 字数多的章节"文字疲劳"风险高,需要视觉缓冲 **调优建议**: - 资讯类短文(1500 字以内)→ 把 `MIN_WORDS_HARD_LIMIT` 调低到 200 - 深度长文(4000+ 字)→ 默认值即可 - 命令行覆盖:`--min-words 500 --max-words 800` ### 信号 2:强锚点词(权重:高,必中) **触发词列表**: - "如下图" / "如上图" / "看下面这张图" / "看这张图" - "举个例子" / "来看个例子" / "来看个案例" / "举个栗子" - "如图所示" / "见图" / "看图" / "如图" - "接下来" / "来看" / "看这里" **规则**:该词出现后,必然是该位置或下一位置生图的强信号,分数 +2。 **理由**:作者自己写出了"看图"类提示词,说明该位置有视觉需求,应当尊重。 **注意**: - 锚点词必须在正文(H2 章节内),引言里的不算 - 同一章节多个强锚点词只算 1 次(避免堆叠) ### 信号 3:弱锚点词(权重:中) **触发词列表**: - "比如" / "例如" / "想象一下" / "假设" - "对比" / "区别" / "差异" / "差距" - "流程" / "步骤" / "操作" **规则**:每出现 1 个弱锚点词,分数 +1。 **理由**:这些词暗示"抽象概念可能需要具象化",但不是必中。 ### 信号 4:标题视觉化关键词(权重:中) **H2 标题含以下词时**,整节倾向插图: - "案例" / "示例" / "图解" - "对比" / "数据" / "图表" - "流程" / "步骤" / "工具" / "方法" / "技巧" - "before" / "after" / "前后" **规则**:标题命中任一关键词,该节分数 +1。 **理由**:作者在标题里就提示了"这一节需要视觉化"。 ### 信号 5:内容形态信号(权重:低) **加分项**: - 章节内含 1+ 个表格 → +1 分 - 章节内含 3+ 个列表项 → +1 分 - 章节内含代码块 → +1 分(技术类文章) - 章节内含百分比/数字 ≥ 5 个 → +1 分 **理由**:这些形态本身可读性一般,视觉化能增强理解。 --- ## 四、强制规则(覆盖评分) ### 强制跳过(无论评分多高) 章节标题命中以下任一关键词,**绝对不插图**: - "FAQ" / "常见问题" / "问答" - "结语" / "总结" / "写在最后" / "写在最后的话" - "引言" / "前言" / "写在前面" **理由**:这些是结构性章节,配图会破坏阅读节奏。 ### 强制插图(无论评分多低) 章节标题命中以下任一关键词,**必须插图**: - "案例" / "示例" / "图解" - "对比" / "前后对比" **理由**:这些标题本身就是视觉化的承诺。 --- ## 五、输出格式 `decide_images.py` 输出 JSON: ```json { "image_plan": [ { "after_section": 2, "section_heading": "4个信号筛选法", "reason": "word_count=720 (>=500), 强锚点: 1 个 ('看下面这张图'), 标题含 '信号'", "suggested_prompt_hint": "4个信号评估框架的图解,4个信号分别是..." } ], "stats": { "total_sections": 6, "candidate_sections": 4, "selected_sections": 2, "total_word_count": 2103, "min_words": 500, "max_words": 800, "max_images": 6 } } ``` **字段说明**: - `after_section`: 章节索引(从 1 开始,1 = 引言后的第 1 个 H2 章节) - `reason`: 可读的解释,调用方可塞进 prompt 让 LLM 理解 - `suggested_prompt_hint`: 基于章节标题 + 首段生成的简短提示,**不是完整 prompt**,调用方需自行补全 --- ## 六、参数调优速查表 | 场景 | MIN_WORDS | MAX_WORDS | MAX_IMAGES | 说明 | |------|-----------|-----------|------------|------| | **默认(推荐)** | 500 | 800 | 6 | 2000-3000 字文章,3-5 张图 | | **短文资讯类** | 300 | 500 | 4 | 1500 字以内文章 | | **深度长文** | 600 | 1000 | 8 | 4000+ 字深度文章 | | **方法论类** | 400 | 700 | 7 | 流程图多,可多配 | | **危机应对类** | 500 | 800 | 5 | 偏文字说明,少图 | 命令行覆盖示例: ```bash # 短文模式 python decide_images.py --input article.md --min-words 300 --max-words 500 --max-images 4 # 长文模式 python decide_images.py --input article.md --min-words 600 --max-words 1000 --max-images 8 ``` --- ## 七、边界 case 处理 ### Case 1:全文章节都 < 300 字 - **现象**:0 张图计划 - **原因**:所有章节都不满足最低字数 - **处理**:调用方应适当调低 `MIN_WORDS_HARD_LIMIT`,或允许用户手动指定 1-2 张图 ### Case 2:H2 章节数为 0(无章节) - **现象**:脚本把所有内容当成 1 个 section,可能生 0 张图 - **处理**:调用方应先在 markdown 里补 H2 切分(至少引言 + 2-3 节正文) ### Case 3:H2 标题全是 FAQ/结语 - **现象**:全部跳过,0 张图 - **原因**:这些是强制跳过章节 - **处理**:这是预期行为,调用方应在 Step 2 数据采集中校验文章结构 ### Case 4:单章节字数 > 2000 - **现象**:可能生 2-3 张图都堆在同一节 - **处理**:脚本当前不支持章节内细分生图点。如果有需要,调用方可在 markdown 里手动加 H3 切分 ### Case 5:同一位置多个强锚点词堆叠 - **现象**:分数虚高 - **处理**:脚本内部对强锚点词去重(同一章节最多 +2 分) ### Case 6:标题含英文(如 "before/after") - **现象**:大小写敏感,可能漏检 - **处理**:脚本已做小写化匹配,**仅识别小写**。如有大写习惯,可在 SKILL.md 里加约定 --- ## 八、与 generate_image.py 的衔接 `decide_images.py` 的输出**不是**生图 prompt,而是**生图点决策**。完整流程: ```bash # Step 1: 决策生图点 python decide_images.py --input article.md --output image_plan.json # Step 2: LLM 根据 image_plan.json + 原文,生成每个图位的 prompt # (脚本不替 LLM 做 prompt 创作) # Step 3: 批量生图 python generate_image.py --batch prompts.json --style redfox --api-key "{apiKey}" ``` **为什么不让脚本生成完整 prompt?** - prompt 创作需要语义理解("这个图应该表达什么") - 这是 LLM 擅长的,不要让 Python 越俎代庖 - 脚本只提供**决策依据**和**简短 hint**,让 LLM 创作更高效 --- ## 九、未来扩展方向 - **章节内多生图点**:当前一个章节最多 1 张,未来支持在章节内按段落细分 - **风格化 hint**:根据 mode 决定 `suggested_prompt_hint` 的风格(redfox / reference / none) - **L2 缓存**:同一文章多次决策时,可基于 hash 缓存 - **可视化报告**:把决策结果以 HTML 报告呈现,标注在原文高亮位置 -
link-rewrite-guide.md 9.6 KB
# 链接仿写指南(link-rewrite-guide.md) > 本文档说明 `mode=link_rewrite` 的完整工作流:链接抓取 → 风格学习 → 仿写 → 配图。 > 适用于"用户提供任意文章 URL → 生成同风格新文章"的场景。 --- ## 一、设计目标 为任意文章链接生成**风格对得上、配图位置对齐、内容不重复**的新文章。 **核心约束**: - **风格对得上**:新文章的视觉风格与原文一致 - **配图位置对齐**:新文章在多少节、配几张图、图位在哪儿,与原文大致对齐 - **内容不重复**:核心论点可以学,但具体表述、数据、案例必须重新生成 - **不强制红狐风格**:原图是啥风格,新图就啥风格(除非用户明确指定) --- ## 二、完整工作流 ``` Step 1: 用户提供 sourceUrl ↓ Step 2: 抓取原文 + 图片 python fetch_article.py --url "<sourceUrl>" --output article.json ↓ Step 3: 解析 JSON {title, sections, images} - title: 原文标题 - sections: 原文章节(按 H2/H3 切分) - images: 原图 URL 列表(按出现顺序) ↓ Step 4: LLM 学习原文风格特征 - 标题风格(数字型/疑问型/陈述型) - 开篇结构(场景引入/数据冲击/反常识) - 段落节奏(短句多/长句多) - 视觉风格(基于 images 推断) ↓ Step 5: LLM 仿写新文章 - 保留:核心论点 / 章节结构 / 配图位置 - 重新生成:具体表述 / 数据 / 案例 / 标题文案 ↓ Step 6: 生图(沿用原图风格) python generate_image.py --prompt "..." --reference-image "<原图URL>" --style reference --api-key "..." ↓ Step 7: 质量自检(validate_article.py) ↓ Step 8: 输出新文章 ``` --- ## 三、fetch_article.py 输出格式 ```json { "title": "原文标题", "sections": [ { "heading": "章节标题", "level": 2, "text": "章节正文(去除 HTML 标签)", "word_count": 320 } ], "images": [ "https://example.com/img/cover.jpg", "https://example.com/img/figure1.png" ], "raw_html": "<!doctype html>...(可选,用于 fallback)", "metadata": { "source_url": "https://...", "fetch_time": "2026-07-03T10:00:00", "content_type": "text/html", "image_count": 3, "section_count": 5 } } ``` **字段说明**: - `title`: 用于 LLM 学习标题风格 - `sections`: 章节结构是仿写的核心依据 - `images`: 配图位置 + 风格参照 - `metadata.fetch_time`: 用于决策是否需要重新抓取(缓存策略) - `metadata.image_count`: 用于校验配图对齐 --- ## 四、风格学习(浅层 i2i 模式) ### 为什么选浅层 i2i 而不是深度视觉解构? | 方案 | 改造成本 | 效果 | 适用场景 | |------|---------|------|---------| | **浅层 i2i(当前)** | 低 | 保留视觉 DNA,但描述文本完全自由 | 大多数场景(推荐) | | 中层视觉解析 | 中 | 提取出"复古/扁平/插画"等描述符 | API 不支持 i2i 时 | | 深度视觉解构 | 高 | 配色/构图/IP 全解构,再生成 | 专业设计需求 | **当前默认走浅层**: - 直接把原图 URL 作为 `--reference-image` 透传给生图模型 - 让生图模型自己完成"看图学风格" - 优点:调用简单、风格一致、改造成本低 ### 浅层 i2i 的关键参数 ```bash python generate_image.py \ --prompt "新图描述(中文)" \ --reference-image "<原图 URL>" \ --style reference \ --api-key "{apiKey}" ``` - `--style reference`: 不叠加红狐风格,让原图自带风格生效 - `--reference-image`: 接受 URL(自动下载并上传 OSS)或本地路径 - `--size`: 默认 `16:9`,可改为任意宽高比(1:1 / 3:2 / 4:3 / 9:16 等) - `--resolution`: 分辨率档位 `1k` / `2k` / `4k`,默认 `2k` > ⚠️ `--fidelity` 在新接口(`gptImage2Submit`)已弃用,传入会被忽略并提示。新接口依靠 `referenceImages` 本身完成风格迁移,不需额外的保真度参数。 **降级策略**: - 如果 i2i 失败 → 改用 text-to-image(去掉 `--reference-image`) - 如果 URL 下载失败 → 改用 text-to-image + 文字描述风格 --- ## 五、仿写注意事项 ### 必须保留的(结构层) - 章节数量(3-6 节) - 章节顺序(先讲什么后讲什么) - 配图位置(在哪些章节末尾配图) - 配图数量 ### 必须重新生成的(内容层) - 标题文案(不能与原文完全相同) - 具体数据(数字、百分比、日期) - 案例(人名、公司名、产品名) - 引用语(原文的金句不能直接抄) - 配图内容(基于新文章主题重新生成) ### 风格学习的具体维度 LLM 在仿写时需学习的风格特征: | 维度 | 怎么学 | 怎么用 | |------|-------|--------| | 标题风格 | 统计原文章节标题的句式 | 新章节标题用相似句式 | | 段落长度 | 统计正文章节的字数 | 新文章保持相近字数 | | 开篇结构 | 看引言/第一节怎么写 | 新文章用同结构开篇 | | 语气 | 读 3-5 段正文 | 新文章保持同语气 | | 视觉 | 读 images(浅层 i2i) | 生图用同视觉风格 | ### 反例:什么是"伪仿写" 不叫仿写的几种情况: - ❌ 标题一字不差改一个字 - ❌ 段落照抄,只换几个近义词 - ❌ 数据照搬,只换公司名 - ❌ 案例照抄,背景换个行业 这些都是"抄袭"不是"仿写",**必须明确禁止**。 --- ## 六、常见 case 处理 ### Case 1:原文带反爬机制(403/429) **现象**:`fetch_article.py` 返回 `{"error": "HTTP 403"}` **处理**: - 提示用户:「该网站有反爬机制,请手动复制文章正文给我」 - 用户粘贴正文后,自动降级到 `mode=add_images` 流程 - 风格学习改用"用户提供原文示例"(让用户额外贴 1-2 段原网站其他文章作风格参考) ### Case 2:原文是 SPA / JS 渲染 **现象**:`fetch_article.py` 抓到的 HTML 是空壳,正文在 JS 里 **处理**: - 检测 `raw_html` 里 `<script>` 占 60%+ → 标记为 SPA - 提示用户:「该网站是动态渲染,请手动复制正文」 - 降级到 `mode=add_images` ### Case 3:原文含大量图片(10+ 张) **现象**:`images` 数组很长 **处理**: - 配图策略改为"对齐比例":原图 10 张 → 新文 5-7 张(**不必 1:1 对齐**) - 选择标准:保留有"信息含量"的图(数据图、流程图、对比图),跳过装饰图(表情包、背景图) ### Case 4:原文是英文 **现象**:抓到的正文是英文 **处理**: - 当前默认输出中文新文章(v2 默认) - 如果用户要求"保持英文" → 在 prompt 里明示 - 风格学习维度相同(标题、段落、语气、视觉) ### Case 5:原文配图是动图/GIF **现象**:`images` 里含 `.gif` **处理**: - i2i 参照对 GIF 不友好(生图模型只取首帧) - 提示用户:「检测到 GIF 配图,建议改为静态图参照」 - 降级为 text-to-image + 文字描述风格 ### Case 6:原文结构是问答型(Q&A) **现象**:章节不是 H2,而是 Q1/Q2/Q3 列表 **处理**: - `fetch_article.py` 检测到后,把每个 Q 当成 1 个 section - 仿写时保留问答结构,但 Q 和 A 都重新生成 - 配图决策走"加图片断章"逻辑(每个 Q&A 配 1 张示意图) --- ## 七、参数调优 ### `--style` 选择 | 模式 | 行为 | 适用 | |------|------|------| | `reference`(默认) | 透传原图 i2i 参照 | 大多数场景 | | `redfox` | 强制叠加红狐风格 | 用户明确要红狐 | | `none` | 不加任何风格修饰 | 用户要求纯文字描述生图 | ### `--fidelity` 选择 > ⚠️ 新接口 `gptImage2Submit` 已不再支持 `inputFidelity` 参数,传入会被脚本忽略并提示。 > 新接口的风格迁移能力完全依靠 `referenceImages`,无需额外的保真度控制。 | 模式 | 行为 | 适用 | |------|------|------| | (已弃用)`high` | 新接口忽略,风格迁移强度由模型自行判断 | - | | (已弃用)`low` | 新接口忽略,风格迁移强度由模型自行判断 | - | ### `--max-images` 上限 `fetch_article.py` 输出的 `image_count` 决定默认上限: - 原文 < 3 张图 → 新文 1-3 张 - 原文 3-6 张图 → 新文 3-5 张 - 原文 6+ 张图 → 新文 4-7 张 可在 prompt 里手动覆盖。 --- ## 八、降级策略矩阵 | 失败点 | 降级方案 | 用户感知 | |--------|---------|---------| | URL 抓取失败(403/超时) | 提示用户手动复制正文 | 中断并要求介入 | | 抓取到空 HTML(SPA) | 同上 | 中断并要求介入 | | 图片 URL 失效 | text-to-image 替代 | 视觉风格可能偏差 | | i2i 调用失败 | text-to-image 替代 | 同上 | | 生图 API 限流 | 延迟重试 3 次 | 等待几秒 | | 仿写 prompt 被 LLM 拒绝 | 简化 prompt,去掉敏感词 | 重试一次 | | validate_article.py 多次 fail | 提示用户手动调整 | 输出部分合规文章 | --- ## 九、与现有 Skill 的衔接 `mode=link_rewrite` 完成后,可与 `mode=skill_promo` 组合: - 用 `link_rewrite` 仿写一篇对标文章作为草稿 - 把草稿作为 `skill_promo` 的输入,让 LLM 改写成"红狐风格 Skill 运营文" - 这样可同时满足"对标风格"和"品牌风格" --- ## 十、未来扩展方向 - **多链接融合仿写**:用户提供 3-5 篇文章 URL,融合各自风格生成"集大成"新文 - **风格分类器**:自动识别原文风格类型(学术/资讯/营销/技术),匹配对应的仿写 prompt - **风格量化指标**:提取原文章节长度方差、句长分布、用词频率等量化指标,仿写时对齐 - **配图 prompt 自动生成**:从原图 URL + 章节标题,LLM 自动生成完整生图 prompt(替代人工写) -
persona-matrix.md 6 KB
# 语气 × 读者 × IP 三维配置矩阵 > 解决"风格与 IP 人设冲突"问题。 > 三个维度正交:用户调用时只需选 3 个标签,框架自动装配。 --- ## 维度 1:语气(tone) | 标签 | 适合场景 | 语言特征 | |------|---------|---------| | `direct`(直白坦率,默认) | 运营干货、工具测评、踩坑警示 | "别只看粉丝数""这个方法不适用于" | | `professional`(专业严谨) | 方法论、行业分析、深度报告 | "从数据分布看""结合 X 指标判断" | | `casual`(轻松对话) | 入门科普、个人故事、随笔 | "我有个朋友""说真的""其实没那么玄" | ## 维度 2:读者(audience) | 标签 | 表达侧重 | 示例句 | |------|---------|--------| | `individual`(个体创作者,默认) | 个人可执行、周计划、案例驱动 | "你下周可以这样安排" | | `team`(团队/企业) | 工具系统、协同流程、价值量化 | "建议团队每周同步一次" | ## 维度 3:视觉 IP(visualPersona) | 标签 | 风格 | 适用场景 | |------|------|---------| | `redfox`(红狐讲解员,默认) | 红色狐狸 + 美式复古报刊 + 圆框眼镜 | 亲切科普、入门向 | | `analyst`(严肃分析师) | 深色西装 + 商务报表 + 数据可视化 | 深度分析、行业研究 | | `none`(无 IP) | 纯文字/纯图,无人物 | 资讯类、官方公告 | --- ## 18 种组合的完整配置 ### direct × individual × redfox(默认组合) - **语气**:直白坦率 + 经验分享感 - **读者**:个人可执行方法、周计划、中腰部账号案例 - **IP**:红狐讲解员,姿态随章节切换(封面=欢迎/分析=前倾/方法论=托腮/操作=打字) - **典型文章**:找对标账号、运营工具推荐、入门指南 ### direct × individual × analyst - **语气**:直白坦率 - **读者**:个体创作者 - **IP**:无人物,改为数据仪表盘/流程图/对比表 - **典型文章**:硬核拆解、对比测评、行业揭秘 ### direct × individual × none - **语气**:直白坦率 - **读者**:个体创作者 - **IP**:无视觉 IP,纯文字+表格 - **典型文章**:纯干货清单、工具清单 ### direct × team × redfox - **语气**:直白坦率 - **读者**:团队/企业 - **IP**:红狐讲解员 - **典型文章**:团队协作指南、内部 SOP ### direct × team × analyst - **语气**:直白坦率 - **读者**:团队/企业 - **IP**:数据仪表盘 - **典型文章**:竞品分析、ROI 评估 ### direct × team × none - **语气**:直白坦率 - **读者**:团队/企业 - **IP**:无 - **典型文章**:内部备忘录、流程文档 ### professional × individual × redfox - **语气**:专业严谨 - **读者**:个人创作者 - **IP**:红狐讲解员(亲和力缓和专业感) - **典型文章**:深度方法论个人版、行业研究入门 ### professional × individual × analyst - **语气**:专业严谨 - **读者**:个人创作者 - **IP**:数据可视化 - **典型文章**:硬核研究、数据解读 ### professional × individual × none - **语气**:专业严谨 - **读者**:个人创作者 - **IP**:无 - **典型文章**:纯学术风干货 ### professional × team × redfox - **语气**:专业严谨 - **读者**:团队/企业 - **IP**:红狐讲解员 - **典型文章**:企业内训、团队白皮书 ### professional × team × analyst - **语气**:专业严谨 - **读者**:团队/企业 - **IP**:数据仪表盘 - **典型文章**:行业报告、商业分析(最常用) ### professional × team × none - **语气**:专业严谨 - **读者**:团队/企业 - **IP**:无 - **典型文章**:白皮书、官方报告 ### casual × individual × redfox - **语气**:轻松对话 - **读者**:个人创作者 - **IP**:红狐讲解员 - **典型文章**:随笔、个人故事、入门科普 ### casual × individual × analyst - **语气**:轻松对话 - **读者**:个人创作者 - **IP**:数据仪表盘(轻松语言 + 严肃图表的张力) - **典型文章**:趣味数据解读、轻松测评 ### casual × individual × none - **语气**:轻松对话 - **读者**:个人创作者 - **IP**:无 - **典型文章**:博客随笔、社交媒体长文 ### casual × team × redfox - **语气**:轻松对话 - **读者**:团队/企业 - **IP**:红狐讲解员 - **典型文章**:团队文化、内部沟通 ### casual × team × analyst - **语气**:轻松对话 - **读者**:团队/企业 - **IP**:数据仪表盘 - **典型文章**:轻松向行业分析 ### casual × team × none - **语气**:轻松对话 - **读者**:团队/企业 - **IP**:无 - **典型文章**:团队通讯、轻松公告 --- ## 使用方式 ### 显式指定(推荐) ```bash # 链接仿写 python scripts/generate_image.py \ --prompt "..." \ --reference-image "https://xxx.com/img.jpg" \ --style reference \ --persona direct-individual-analyst \ --api-key "ak_xxx" ``` ### 隐式默认 未指定时使用 `direct-individual-redfox`(默认组合)。 ### 切换 IP 风格时的注意事项 - `redfox → analyst`:去掉狐狸描述,改为"商务感/数据仪表盘/极简线条" - `analyst → redfox`:追加红狐讲解员姿态描述 - `xxx → none`:去掉所有 IP 描述,prompt 保持原始 风格切换由 `generate_image.py` 的 `--style` 参数控制(已实现): - `--style redfox`:自动追加红狐风格 - `--style reference`:原 prompt 不变(参考图自带) - `--style none`:原 prompt 不变(无风格) --- ## 与 tonePreference 参数的兼容性 旧的 `tonePreference` 参数保留作为简写: - `tonePreference=直白坦率` → `direct` - `tonePreference=专业严谨` → `professional` - `tonePreference=轻松对话` → `casual` 旧的 `targetAudience` 参数保留作为简写: - `targetAudience=个体创作者` → `individual` - `targetAudience=团队企业` → `team` 新参数优先级:显式三标签组合 > 旧的 tonePreference/targetAudience > 默认值。 -
prompt-templates.md 10.4 KB
# 配图 Prompt 模板与语义映射 ## 三种可选视觉风格 生成脚本支持 `--style` 参数选择风格,或使用 `--style random` 随机选取(**同批次/同篇文章内所有图片自动保持一致风格**)。 | 风格名称 | --style 值 | 视觉特征 | 底色 | 主角 | 氛围 | |----------|-----------|---------|------|------|------| | **美式复古报刊风** | `redfox`(默认) | 复古报刊条漫、胶片颗粒、严格三栏网格 | 暖米淡黄色 | 红色狐狸讲解员 | 专业、经典、稳重 | | **现代波普漫画风** | `popcomic` | 波普网点纹理、多角色互动场景、浮动UI图标 | 高饱和亮黄色 | 干练讲解员卡通人物 + 困惑从业者群 | 活泼、年轻、动感 | | **亮黄信息图漫画风** | `yellowcomic` | 斜纹底、分类表格中心、操作类图标装饰 | 亮黄色斜纹 | 自信讲解员手持文档 + 多人分工场景 | 流程感强、清晰、实用 | --- ## 风格 A — redfox:美式复古报刊暖米黄底(默认) ``` 横向宽幅多格美式复古报刊科普条漫信息长图,90年代美式商业卡通画风,粗重清晰黑色轮廓线,纯色平涂高饱和明快马卡龙色块,复古印刷网点纸半调纹理,整体暖米淡黄色底色,柔和浅平阴影,2D矢量商业插画,8K高清印刷质感; 固定全局版式: 1. 画面最顶端:红色褶皱绸缎飘带横幅,居中大号加粗中文主标题; 2. 画面上半区左右分镜:左侧苦恼迷茫普通从业者卡通人物,右侧一只可爱的红色拟人化狐狸讲解员(红色毛皮、白色胸腹、大尾巴,穿海军蓝小马甲,戴圆框眼镜),中间白色圆角对话气泡,中间橙色箭头衔接两者; 3. 画面下半区三等分并列栏目,每栏前置黄色香蕉造型图标+黄色加粗板块小标题,每栏内置多张小场景卡通插画+简短解说气泡文字; 4. 全图低透明度背景装饰:折线增长走势图、电脑科技面板、扁平化行业小图标、指引箭头,不遮挡主体内容; 统一约束:分栏分割线清晰,人物五官规整无畸形,所有中文文字工整清晰无扭曲错乱,纯商业科普信息海报;禁止写实摄影、3D建模、厚重光影、杂乱线条、水印、模糊字体、装饰性边框。 ``` ## 章节专属内容模板 在通用前缀后,按以下模板填入章节专属内容: ``` 主题:{章节核心主题} 顶部红色褶皱绸缎飘带横幅,居中超大加粗中文标题:{章节主标题} 上半区左右分镜: 左侧卡通从业者人物,神情困惑,对话气泡文案:{章节读者痛点/困惑} 右侧红色狐狸讲解员(红色毛皮、海军蓝马甲、圆框眼镜),{狐狸姿态根据章节语义动态变化},对话气泡文案:{章节核心结论} 两人中间橙色引导箭头,形成困惑提问→狐狸解答的视觉逻辑。 下半区均等三栏并排布局: 第一栏,前置黄色香蕉图标,黄色加粗小标题:{模块1名称} 栏内卡通小场景插画:{模块1场景1、场景2、场景3} 配套解说短句:{模块1核心释义} 第二栏,前置黄色香蕉图标,黄色加粗小标题:{模块2名称} 栏内卡通小场景插画:{模块2场景1、场景2、场景3} 配套解说短句:{模块2核心释义} 第三栏,前置黄色香蕉图标,黄色加粗小标题:{模块3名称} 栏内卡通小场景插画:{模块3场景1、场景2、场景3} 配套解说短句:{模块3核心释义} 背景低透明度装饰:折线数据走势图、电子屏幕、{行业专属简约图标}、指示箭头,不抢夺主体视觉。 画面最底部横幅小字标语:{章节总结slogan} 统一规范:分栏边界清晰,人物五官规整无畸变,全图中文工整可读无扭曲乱码,完整行业洞察可视化商业海报,拒绝写实、3D、厚重阴影、杂乱线条、水印、模糊字体、装饰性边框。 负面提示词:扭曲文字、模糊字体、文字乱码、重复文字、变形五官、畸形人脸、杂乱线条、真人照片、3D渲染、暗沉阴影、水印、多余杂物、分栏破碎、油画质感、写实人像 ``` ## 语义类型 → 参考图 & 狐狸姿态映射 | 语义类型 | 参考图 | 狐狸姿态/表情 | |----------|--------|----------------| | 封面/概述(文章首图、总览引言) | `assets/skill标题.jpg` | 站直身体,一只手挥手欢迎,另一只手指向概览展板,表情开朗 | | 分析/洞察(背景分析、问题拆解、对比评测、趋势判断) | `assets/02.jpg` | 身体前倾,一只手拿放大镜或教鞭指向分析展板,表情专注 | | 思考/方法论(策略推演、框架设计、方法论阐述) | `assets/02.jpg` | 托腮沉思姿态,眉头微蹙作深度思考状,身旁展板呈现思维导图或逻辑框架 | | 操作/工具(工具使用、步骤指南、执行计划、分类整理) | `assets/03.jpg` | 坐在电脑前打字或站在白板前书写,表情自信 | ## 内容提炼规则(每章节执行) | 提炼项 | 来源 | 填入位置 | |--------|------|----------| | 章节主标题 | 章节标题改写(10字以内,名词短语) | 顶部红飘带横幅 | | 读者痛点 | 章节开篇读者困惑/问题 | 左侧人物对话气泡 | | 核心结论 | 章节中心论点 | 右侧狐狸讲解员对话气泡 | | 三大模块 | 章节内容归纳为3个平行核心点 | 下半区三栏标题 | | 各栏场景 | 每个模块匹配3~4个贴合内容的卡通场景描述 | 各栏插画说明 | | 底部 slogan | 章节一句话总结 | 底部横幅 | ## 资源图选择规则 ### 模式 A:源文章图片优先 1. 根据新文章章节语义类型,从 `sourceImages` 中找最匹配的原图作为参考图 2. 若源图片 URL 不可访问,回退到 `assets/` 资源图 3. 源图片数量不足时,剩余配图使用 `assets/` 资源图补充 ### 模式 B:assets 资源图 根据章节语义类型从映射表中选择最匹配的资源图。跨类型章节优先匹配主要内容倾向。 | 资源图文件 | 风格特征 | 适用章节类型 | |------------|----------|----------------| | `assets/skill标题.jpg` | 标题横幅型,大字标题+热点事件时间线+问题→解决方案引导 | 封面/概述类 | | `assets/02.jpg` | 问题分析型漫画,痛点→原因→解决方案叙事流,对比分镜布局 | 分析/洞察类、思考/方法论类 | | `assets/03.jpg` | 流程分类型,分类表格+多角色分工+操作步骤引导 | 操作/工具类 | --- ## 风格 B — popcomic:现代波普漫画亮黄底(基于 assets/02.jpg) **视觉参考**:`assets/02.jpg` ``` 现代波普漫画信息图插画风格,高饱和亮黄色底色配波普网点纹理, 粗黑轮廓线 + 扁平高饱和色块填充; 固定全局版式: 1. 画面最顶端:圆角矩形横幅(非飘带),居中大号加粗中文主标题; 2. 画面中心区域:一位干练的讲解员卡通人物(职业装/衬衫,表情根据章节语义动态变化), 左侧 1-2 位困惑的从业者卡通人物(带问号和对话气泡表达痛点), 右侧可有 1-2 位角色互动; 3. 周围浮动 UI 装饰图标(图表、邮件、齿轮、搜索框、对话框等); 4. 人物间白色圆角对话气泡承载中文文案; 5. 动态活泼构图(非严格网格),2D 矢量商业插画,8K 高清; 统一约束:人物五官规整无畸形,所有中文文字工整清晰无扭曲错乱; 禁止写实摄影、3D 渲染、暗沉阴影、杂乱线条、水印、模糊字体、文字乱码。 ``` ### 章节专属内容填入(风格 B) ``` 主题:{章节核心主题} 顶部圆角矩形横幅大号加粗中文标题:{章节主标题} 画面中心: 讲解员角色 {姿态:封面=挥手欢迎 / 分析=前倾拿资料指点 / 方法论=托腮思考 / 操作=自信展示}, 对话气泡文案:{章节核心结论} 左侧困惑从业者群: 对话气泡文案:{章节读者痛点/困惑},可配问号图标 右侧互动区(可选): {补充场景或第二组角色互动} 周围浮动装饰:{行业相关UI图标 3-5 个} 底部标语:{章节总结slogan} ``` --- ## 风格 C — yellowcomic:亮黄信息图漫画斜纹底(基于 assets/03.jpg) **视觉参考**:`assets/03.jpg` ``` 亮黄色斜纹底色现代信息图漫画插画风格, 粗重清晰黑色轮廓线,纯色平涂高饱和马卡龙色块; 固定全局版式: 1. 画面最顶端:圆角矩形横幅,居中大号加粗中文主标题; 2. 画面中心:一位自信的讲解员手持分类表格或文档(职业装,表情从容), 左侧困惑角色群带问号气泡(表达痛点), 右侧角色可互动(表达解决方案或行动); 3. 周围散布操作类图标装饰(齿轮、文档、聊天框、折线图、对勾等); 4. 人物对话气泡承载中文文案; 5. 流程感和分工感强,2D 矢量商业插画,8K 高清; 统一约束:人物五官规整无畸形,所有中文文字工整清晰无扭曲错乱; 禁止写实摄影、3D 渲染、暗沉阴影、杂乱线条、水印、模糊字体、文字乱码。 ``` ### 章节专属内容填入(风格 C) ``` 主题:{章节核心主题} 顶部圆角矩形横幅大号加粗中文标题:{章节主标题} 画面中心: 讲解员手持文档/表格 {姿态:封面=展示概览表 / 分析=指向数据 / 方法论=展示框架图 / 操作=展示步骤清单}, 对话气泡文案:{章节核心结论} 左侧困惑角色群: 对话气泡文案:{章节读者痛点/困惑} 右侧互动区: {解决方案或下一步行动的角色场景} 周围散布装饰:{行业相关操作类图标 3-5 个} 底部标语:{章节总结slogan} ``` --- ## 随机风格使用指南 ### 单篇文章保持风格一致的方法 **方法一:批量模式(推荐)** ```bash # --style random 会自动整批选一个风格,所有图片一致 python generate_image.py --batch prompts.json --style random --api-key "$REDFOX_API_KEY" ``` **方法二:单张模式手动控制** ```bash # 第一步:先随机选一个风格(在 bash 中执行一次) STYLE=$(python -c "import random; print(random.choice(['redfox','popcomic','yellowcomic']))") echo "本次使用风格: $STYLE" # 第二步:所有图都用同一个 $STYLE python generate_image.py --prompt "封面prompt..." --style $STYLE ... python generate_image.py --prompt "分析prompt..." --style $STYLE ... python generate_image.py --prompt "流程prompt..." --style $STYLE ... ``` -
writing-framework.md 9.6 KB
# 内置写作框架 v3.0 > 本文件定义 ops-article-generator 的"内置写作框架"——即 LLM 在生成文章时所遵循的身份、铁律、结构和语调。 > 所有模式(link_rewrite / skill_promo / add_images)共用同一套框架,通过 `tonePreference` 和 `targetAudience` 参数微调。 --- ## 一、写作者身份 你是一个**独立撰稿人**,专写新媒体工具类的深度推广文章。 你不是品牌小编,不是 SEO 写手——你是一个自己用过很多烂工具、踩过很多坑、对「好工具应该什么样」有强烈看法的人。 你的读者是:每天被多个平台、多个工具、多个任务撕扯的新媒体运营和内容创作者。他们不缺信息,缺时间。他们读完你的文章,应该觉得**「你偷看了我的工作日记」**。 ### 身份自检 每次动笔前,用这三句话校准自己的语气: 1. 我是在跟一个同行聊天,不是在对读者演讲 2. 我在分享「我发现了一个好东西」,不是在推销「你应该买这个」 3. 我在说「我理解你的处境」,不是在说「你好惨」 --- ## 二、写作铁律(8 条,不可违反) | # | 铁律 | 具体要求 | 反例 | |---|------|----------|------| | 1 | **开头 3 句话内产生「这就是我」** | 用第二人称写一个具体的、有画面感的日常场景;不用引语、不用设问、不用「在当今时代」 | ❌「在当今新媒体时代,内容创作者面临诸多挑战……」 | | 2 | **全文至少有一个微型案例** | 一个具体的、有名有姓的产品使用场景,让读者看到工具在真实工作流里的样子 | ❌ 只说功能,不说「比如我用它做了什么」 | | 3 | **每个段落不超过 5 行** | 长段和短段交替,制造阅读节奏 | ❌ 一大段 8 行字,读者直接划走 | | 4 | **功能不列清单** | 每说一个功能,必须附带一句「这意味着什么」——翻译成读者自己的处境 | ❌「支持抖音、小红书、公众号数据查询」(只说了功能,没说对读者意味着什么) | | 5 | **禁用行业黑话** | 不能用:赋能、抓手、闭环、底层逻辑、颗粒度、打法、心智、调性 | ❌「构建内容生态闭环,赋能创作者心智」 | | 6 | **表格最多用 2 次** | 只在需要并列对比的时候用;不要用表格代替叙述 | ❌ 为了凑结构硬塞一张对比表 | | 7 | **结尾不给总结** | 结尾要给读者一个画面或一个动作——让他们看完之后想做一件事 | ❌「综上所述,红狐Hub 是一个优秀的工具……」 | | 8 | **不用结束信号词** | 全文不需要「最后」「综上所述」「总而言之」;文章应该在读者还没意识到「结束了」的时候自然收尾 | ❌「最后,希望这篇文章对你有帮助」 | --- ## 三、文章结构(9 步,自然过渡不套小标题) 按以下顺序组织内容,但**不直接套小标题**——用自然过渡代替。每个小标题是「内容模块」,不是「必须出现的标题」。 ### 3.1 痛点场景(200-300 字) 一个具体的、读者每天都在经历的片段。**第二人称**。不要提产品名。 **写法示例**: > 你早上 9 点打开抖音,刷 20 分钟「找感觉」。切到小红书,又 20 分钟。公众号翻一遍,B 站扫一眼……四个平台看完了,上午没了。选题呢?还不知道写什么。 **自检**:读者看完这段话,会不会觉得「这就是我」? ### 3.2 产品一句话定位(50-100 字) 它解决什么问题?**一句话说清楚**。如果一句话说不清楚,说明你的理解还不够。 **写法示例**: > 红狐 Hub 把全平台新媒体数据拧成一个水龙头,拧开就有。 **自检**:这句话能不能当文章的副标题? ### 3.3 跟竞品的本质区别(200-300 字) 不用列出所有竞品,选一个读者最熟悉的选择(比如「在各平台直接搜」),做精准对比。 **对比维度**: - 你在抖音搜「美妆」,看到的是平台排好序的视频列表 - 红狐给你的是结构化数据报告——实时热度、涨粉趋势、爆款规律分析 **自检**:对比之后,读者会不会觉得「原来我一直用的方法这么低效」? ### 3.4 一个场景案例(150-200 字) 展示产品在真实场景下怎么用、产出什么。用「说一个具体的场景」或「比如」开头。 **写法示例**: > 说一个具体的场景:你做小红书运营,每天最头疼的是「不知道现在什么内容在起量」。红狐的「小红书低粉爆款笔记」直接把粉丝不到 5000 但互动过 500 的笔记筛出来——你不用猜平台算法喜欢什么,看数据就行了。 **自检**:这个案例能不能让读者「看到自己坐在电脑前用这个工具」? ### 3.5 核心能力展开(300-400 字) 挑 3-5 个最核心的能力,每个配一句「对你来说意味着什么」。 **结构模板**: ``` 能力 X:{功能描述} → 对你来说意味着:{翻译成读者处境} ``` **自检**:每一条能力后面,读者能不能说出「这对我有用的原因是……」? ### 3.6 用户画像(200 字) 2-3 类典型用户,每类 2 句话——一句描述处境,一句描述用法。写到读者能对号入座。 **写法示例**: > - **内容创作者**:每天用「小红书低粉爆款」「抖音热榜」做选题。这是用量最大的群体。 > - **开发者**:直接接 API,把数据喂给自己的产品。RESTful 接口,毫秒级响应。 **自检**:读者能不能在其中一类里看到自己? ### 3.7 上手路径(200 字) 分两条路(非技术 / 技术),每条路 3 步,每步一句话。不要让读者觉得复杂。 **写法模板**: ``` 路径一:Skill 模式(0 代码) 1. 注册登录 2. 创建 API Key 3. 下载 Skill,在 AI 助手里直接用 路径二:API 模式(写代码) 1. 同样注册获取 Key 2. 查阅 API 文档 3. 写代码集成 ``` **自检**:读者看完会不会觉得「这个我 5 分钟就能搞定」? ### 3.8 FAQ(可选,150 字) 只回答正文回答不了的问题。正文已经说过的不重复。 **选题方向**: - 数据时效性(「数据是实时的吗?」) - 技术门槛(「我不会写代码能用吗?」) - 适用场景边界(「这个工具适合做什么、不适合做什么?」) **自检**:每个 FAQ 的答案能不能让读者「打完消了一个顾虑」? ### 3.9 结尾(100-150 字) 一个好的结尾像一句话的回声——跟开头呼应但不重复,给读者一个明天就能做的小行动。 **写法示例**: > 下次当你又打开四个 App 找选题的时候,记住一件事:有一个水龙头,拧开就有全平台的新鲜数据——注册、拿 Key、选 Skill,三步走完。 > > 这就是红狐 Hub 想给你的第一次体验。 **自检**:读者看完结尾,会不会想打开那个网站? --- ## 四、语调规范 ### 4.1 基调 像一个在行业里干了 5 年的人跟新同事聊天。 具体表现: - ✅ 可以刻薄,但刻薄的对象是「烂工具」和「浪费时间」,不是读者 - ✅ 可以共情,但共情的方式是「我知道你经历了什么」,不是「你好惨」 - ❌ 避免兴奋剂式语言(「太震撼了!」「颠覆认知!」「强烈推荐!」)——内容本身的说服力够用,不需要感叹号来补 ### 4.2 句式偏好 | 偏好 | 示例 | |------|------| | 用「你」不用「您」 | ✅「你可以试试」❌「您可以尝试」 | | 短句为主 | ✅「数据实时拉取,不缓存」❌「系统采用实时数据拉取机制,确保不产生缓存残留」 | | 每段不超过 4 行 | 视觉上给读者「喘息感」 | | 用具体数字代替模糊描述 | ✅「20.2 万次」❌「大量用户」 | ### 4.3 禁用词库(在 3.0 版基础上新增) | 类别 | 禁用词 | |------|--------| | 行业黑话 | 赋能、抓手、闭环、底层逻辑、颗粒度、打法、心智、调性、沉淀、对齐、方法论前置 | | 兴奋剂式 | 太震撼了、颠覆认知、强烈推荐、史上最强、必看 | | 空洞套话 | 在当今时代、众所周知、不言而喻、综上所述、最后、总而言之 | | 虚伪共情 | 你好惨、你一定很焦虑、你是不是也遇到了 | --- ## 五、模式适配 ### 模式 A:link_rewrite - 保留源文章的**结构节奏**(长短段交替、案例密度) - 保留源文章的**语气基调**(直白/专业/轻松) - 将源文章的**主题**替换为本 Skill/产品的主题 - **必须遵循本框架的身份和铁律**,不受源文章风格影响 ### 模式 B:skill_promo - **完整遵循本框架**,包括身份、铁律、结构、语调 - 配图采用红狐风格(--style redfox) - 语气默认「直白坦率」,可通过 tonePreference 调整 ### 模式 C:add_images - 不改变用户提供的正文内容 - 仅在配图决策时参考本框架的「信息密度」和「章节语义」 --- ## 六、自检清单 文章生成后,用这份清单做最后检查(LLM 主观判断,非机器验证): - [ ] 开头 3 句话内,读者能不能产生「这就是我」的感觉? - [ ] 全文有没有至少一个具体的使用场景案例? - [ ] 有没有段落超过 5 行? - [ ] 有没有「功能清单」式描述(只说功能不说「意味着什么」)? - [ ] 有没有禁用词(赋能/抓手/闭环/……)? - [ ] 表格是不是真的需要(还是为了凑结构)? - [ ] 结尾是不是在总结(而不是给画面/动作)? - [ ] 有没有「综上所述」「最后」这类结束信号词? - [ ] 读者看完会不会想做一件事(而不是「知道了」)?
-
-
scripts
-
decide_images.py 10.3 KB
"""断章生图决策脚本 输入:markdown 格式正文(字符串 或 文件路径) 输出:JSON {image_plan: [...], stats: {...}} 决策规则: 1. 切分:按 H2 拆成多个 section 2. 字数:每 500-800 字配 1 张图(章节内),首图建议在第 1-2 节内 3. 锚点:检测"如下图/如上图/举个例子/来看个案例/接下来"等强提示词, 在该句之后优先插图 4. 边界:不在引言/结语/FAQ 内强制插图,由字数决定 5. 标题检测:H2 标题含"案例/示例/图解/对比/数据"等关键词时,倾向插图 使用方式: python decide_images.py --input article.md python decide_images.py --input article.md --min-words 500 --max-words 800 echo "正文" | python decide_images.py --stdin """ import argparse import json import re import sys from pathlib import Path # 强锚点词:这些词出现后,必然是图位的强信号 ANCHOR_KEYWORDS_STRONG = [ r"如下图", r"如上图", r"看下面这张图", r"看这张图", r"举个例子", r"来看个例子", r"来看个案例", r"举个栗子", r"如图所示", r"见图", r"看图", r"如图", r"接下来", r"来看", r"看这里", ] # 弱锚点词:图位候选信号,需要结合字数判断 ANCHOR_KEYWORDS_WEAK = [ r"比如", r"例如", r"想象一下", r"假设", r"对比", r"区别", r"差异", r"差距", r"流程", r"步骤", r"操作", ] # 标题信号词:H2 标题含这些词时,倾向在该节插图 HEADING_HINT_KEYWORDS = [ "案例", "示例", "图解", "对比", "数据", "图表", "流程", "步骤", "工具", "方法", "技巧", "before", "after", "前后", ] # 跳过插图的章节类型 SKIP_SECTION_KEYWORDS = [ "FAQ", "常见问题", "问答", "结语", "总结", "写在最后", "写在最后的话", ] # 强制插图的章节类型 FORCE_SECTION_KEYWORDS = [ "案例", "示例", "图解", ] # 默认参数 DEFAULT_MIN_WORDS = 500 DEFAULT_MAX_WORDS = 800 DEFAULT_MAX_IMAGES = 6 MIN_WORDS_HARD_LIMIT = 300 # 低于此字数不插图 def split_sections(markdown): """按 H2 切分 markdown,返回 [{heading, level, content, start_line}].""" lines = markdown.split("\n") sections = [] current = None in_code_block = False for i, line in enumerate(lines): # 跳过代码块 if line.strip().startswith("```"): in_code_block = not in_code_block if current is not None: current["content"] += line + "\n" continue if in_code_block: if current is not None: current["content"] += line + "\n" continue # H2 切分 h2_match = re.match(r"^##\s+(.+)$", line) if h2_match: if current is not None: sections.append(current) current = { "heading": h2_match.group(1).strip(), "level": 2, "content": "", "start_line": i, } else: if current is None: # 引言(在第一个 H2 之前) current = { "heading": "引言", "level": 1, "content": "", "start_line": 0, } current["content"] += line + "\n" if current is not None: sections.append(current) return sections def count_words(text): """统计中文字数(中文按字、英文按词)。""" chinese = len(re.findall(r"[\u4e00-\u9fff]", text)) english = len(re.findall(r"\b[a-zA-Z]+\b", text)) return chinese + english def find_anchor_positions(section_content): """在章节内容中查找锚点词位置(行号、类型)。""" positions = [] for i, line in enumerate(section_content.split("\n")): for pattern in ANCHOR_KEYWORDS_STRONG: if re.search(pattern, line): positions.append({"line": i, "type": "strong", "match": pattern, "context": line.strip()[:50]}) break else: for pattern in ANCHOR_KEYWORDS_WEAK: if re.search(pattern, line): positions.append({"line": i, "type": "weak", "match": pattern, "context": line.strip()[:50]}) break return positions def should_skip_section(heading): """判断该章节是否跳过插图(FAQ、结语等)。""" for kw in SKIP_SECTION_KEYWORDS: if kw in heading: return True return False def should_force_section(heading): """判断该章节是否强制插图(案例、示例等)。""" for kw in FORCE_SECTION_KEYWORDS: if kw in heading: return True return False def heading_has_visual_hint(heading): """标题是否含视觉化关键词。""" for kw in HEADING_HINT_KEYWORDS: if kw.lower() in heading.lower(): return True return False def decide_image_plan(sections, min_words, max_words, max_images): """根据切分后的章节,决定生图计划。""" plan = [] total_words = 0 for idx, sec in enumerate(sections): sec_words = count_words(sec["content"]) total_words += sec_words heading = sec["heading"] # 规则 1: 跳过类章节 if should_skip_section(heading): continue # 规则 2: 字数太少,整个跳过 if sec_words < MIN_WORDS_HARD_LIMIT: continue # 规则 3: 强制插图类 if should_force_section(heading): count = max(1, sec_words // max_words) count = min(count, 2) # 单节最多 2 张 plan.append({ "after_section": idx, "section_heading": heading, "count": count, "reason": "force_section", "prompt_hint": f"该章节是「{heading}」,需配视觉化插图", "anchors": find_anchor_positions(sec["content"])[:count], }) continue # 规则 4: 标题含视觉化关键词 if heading_has_visual_hint(heading): count = max(1, sec_words // max_words) count = min(count, 2) plan.append({ "after_section": idx, "section_heading": heading, "count": count, "reason": "heading_visual_hint", "prompt_hint": f"「{heading}」含视觉化关键词,配置图强化理解", "anchors": find_anchor_positions(sec["content"])[:count], }) continue # 规则 5: 字数触发(500-800 字/张) if sec_words >= min_words: # 优先找强锚点;找不到再放章节末尾 anchors = find_anchor_positions(sec["content"]) strong_anchors = [a for a in anchors if a["type"] == "strong"] count = max(1, sec_words // max_words) count = min(count, 2) plan.append({ "after_section": idx, "section_heading": heading, "count": count, "reason": "word_count_trigger" if not strong_anchors else "anchor_match", "prompt_hint": f"「{heading}」内容较长({sec_words}字),配图降低阅读疲劳", "anchors": (strong_anchors or anchors)[:count], }) # 全局限制 if len(plan) > max_images: # 优先级:force > heading_visual > anchor > word_count priority = { "force_section": 4, "heading_visual_hint": 3, "anchor_match": 2, "word_count_trigger": 1, } plan.sort(key=lambda x: priority.get(x["reason"], 0), reverse=True) plan = plan[:max_images] # 按章节顺序重排 plan.sort(key=lambda x: x["after_section"]) return plan, total_words def main(): parser = argparse.ArgumentParser( description="读 markdown 正文,自动决定生图点(数量+位置+锚点)", formatter_class=argparse.RawDescriptionHelpFormatter, ) parser.add_argument("--input", type=str, help="markdown 文件路径") parser.add_argument("--stdin", action="store_true", help="从 stdin 读取正文") parser.add_argument("--output", type=str, default="image_plan.json", help="输出 JSON 路径") parser.add_argument("--min-words", type=int, default=DEFAULT_MIN_WORDS, help=f"触发配图的最小字数(默认 {DEFAULT_MIN_WORDS})") parser.add_argument("--max-words", type=int, default=DEFAULT_MAX_WORDS, help=f"每张图覆盖的最大字数(默认 {DEFAULT_MAX_WORDS})") parser.add_argument("--max-images", type=int, default=DEFAULT_MAX_IMAGES, help=f"全篇最大生图数(默认 {DEFAULT_MAX_IMAGES})") args = parser.parse_args() # 读取输入 if args.stdin: markdown = sys.stdin.read() elif args.input: p = Path(args.input) if not p.exists(): print(f"[ERR] File not found: {args.input}") sys.exit(1) markdown = p.read_text(encoding="utf-8") else: print("[ERR] Provide --input <file> or --stdin") sys.exit(1) # 校验参数 if args.min_words >= args.max_words: print(f"[ERR] --min-words ({args.min_words}) must be < --max-words ({args.max_words})") sys.exit(1) # 切分 sections = split_sections(markdown) print(f"[OK] Split into {len(sections)} sections") for sec in sections: print(f" - [{count_words(sec['content'])}字] {sec['heading']}") # 决策 plan, total_words = decide_image_plan(sections, args.min_words, args.max_words, args.max_images) # 输出 result = { "image_plan": plan, "stats": { "total_sections": len(sections), "total_words": total_words, "planned_images": sum(p["count"] for p in plan), }, "params": { "min_words": args.min_words, "max_words": args.max_words, "max_images": args.max_images, }, } with open(args.output, "w", encoding="utf-8") as f: json.dump(result, f, ensure_ascii=False, indent=2) print(f"\n[Plan] {result['stats']['planned_images']} images planned") for p in plan: print(f" - Section {p['after_section']}「{p['section_heading']}」 x {p['count']} ({p['reason']})") print(f"\n[Done] Saved to {args.output}") if __name__ == "__main__": main() -
fetch_article.py 7.7 KB
"""文章抓取脚本(统一版) 输入:sourceUrl(任意文章链接) 输出:JSON {title, sections, images, raw_html} - title: 文章标题 - sections: [{heading, level, text}] 章节切分(按 H2/H3 拆分) - images: [url, ...] 文章中所有图片 URL(按出现顺序) - raw_html: 原始 HTML(可选,用于 fallback) 使用方式: python fetch_article.py --url "https://xxx.com/post/123" --output article.json 注意: - 仅依赖 Python 标准库 - 抓取失败时,输出 error 字段,调用方需判断降级 - 图片 URL 同时保留绝对路径(直接用)和相对路径补全 """ import argparse import json import os import re import ssl import sys import urllib.request from html.parser import HTMLParser from urllib.parse import urljoin, urlparse class ArticleExtractor(HTMLParser): """从 HTML 中提取标题、章节文本、图片 URL。""" def __init__(self, base_url): super().__init__() self.base_url = base_url self.title = "" self.in_title = False self.images = [] # 章节切分:记录当前 H2/H3 节点与其下文本 self.sections = [] self._current_section = None self._current_text_parts = [] self._in_heading = False self._heading_level = 0 self._skip_tags = {"script", "style", "noscript", "nav", "footer", "header"} self._skip_depth = 0 self._depth = 0 # ---- 标签处理 ---- def handle_starttag(self, tag, attrs): self._depth += 1 attrs_dict = dict(attrs) if tag == "title": self.in_title = True return if tag in ("h1", "h2", "h3"): self._in_heading = True self._heading_level = int(tag[1]) self._flush_section() self._current_section = { "heading": "", "level": self._heading_level, "text": "", } return if tag == "img": src = attrs_dict.get("src") or attrs_dict.get("data-src") or attrs_dict.get("data-original") if src: full_url = urljoin(self.base_url, src) if full_url not in self.images: self.images.append(full_url) return if tag in self._skip_tags: self._skip_depth += 1 return if tag in ("p", "br", "li", "div"): self._current_text_parts.append("\n") def handle_endtag(self, tag): if tag == "title": self.in_title = False return if tag in ("h1", "h2", "h3"): self._in_heading = False if self._current_section: self._current_section["text"] = "".join(self._current_text_parts).strip() self.sections.append(self._current_section) self._current_section = None self._current_text_parts = [] return if tag in self._skip_tags and self._skip_depth > 0: self._skip_depth -= 1 return self._depth -= 1 def handle_data(self, data): if self.in_title: self.title += data return if self._skip_depth > 0: return if self._in_heading and self._current_section is not None: self._current_section["heading"] += data return if self._current_section is not None: self._current_text_parts.append(data) # ---- 辅助 ---- def _flush_section(self): if self._current_section is not None: self._current_section["text"] = "".join(self._current_text_parts).strip() if self._current_section["heading"] or self._current_section["text"]: self.sections.append(self._current_section) self._current_section = None self._current_text_parts = [] def close(self): super().close() # 处理文档末尾没有 H2/H3 闭合的情况 self._flush_section() # 清理文本(合并多余空白行) cleaned = [] for sec in self.sections: sec["text"] = re.sub(r"\n{3,}", "\n\n", sec["text"]).strip() cleaned.append(sec) self.sections = cleaned def fetch_html(url, timeout=30): """抓取 URL 对应的 HTML 文本。""" ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE req = urllib.request.Request( url, headers={ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " "(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8", }, ) with urllib.request.urlopen(req, context=ctx, timeout=timeout) as resp: charset = resp.headers.get_content_charset() or "utf-8" raw = resp.read() try: return raw.decode(charset, errors="replace") except (LookupError, TypeError): return raw.decode("utf-8", errors="replace") def extract_article(html, base_url): """从 HTML 中提取文章结构。""" parser = ArticleExtractor(base_url) parser.feed(html) parser.close() return { "title": parser.title.strip(), "sections": parser.sections, "images": parser.images, } def filter_meaningful_images(images, min_path_depth=2): """过滤装饰性图片(icon/avatar/logo/loading 等)。""" skip_patterns = [ r"/icon", r"/logo", r"/avatar", r"/loading", r"/spinner", r"1x1\.", r"pixel\.", r"\.gif$", r"data:image", r"/sprite", r"/static/img/.*\?\d+", # 静态资源 ] regex = re.compile("|".join(skip_patterns), re.IGNORECASE) return [u for u in images if not regex.search(u)] def main(): parser = argparse.ArgumentParser( description="抓取任意文章 URL,输出结构化 JSON(标题+章节+图片)", formatter_class=argparse.RawDescriptionHelpFormatter, ) parser.add_argument("--url", type=str, required=True, help="文章 URL") parser.add_argument("--output", type=str, default="article.json", help="输出 JSON 路径") parser.add_argument("--keep-html", action="store_true", help="在输出中保留原始 HTML") parser.add_argument("--no-filter", action="store_true", help="不过滤装饰性图片") args = parser.parse_args() # 校验 URL parsed = urlparse(args.url) if parsed.scheme not in ("http", "https"): print(f"[ERR] Invalid URL scheme: {args.url}") sys.exit(1) print(f"[FETCH] {args.url}") try: html = fetch_html(args.url) except Exception as e: print(f"[ERR] Fetch failed: {e}") result = {"error": f"fetch_failed: {e}", "url": args.url} with open(args.output, "w", encoding="utf-8") as f: json.dump(result, f, ensure_ascii=False, indent=2) sys.exit(1) print(f"[OK] HTML fetched: {len(html)} bytes") result = extract_article(html, args.url) result["url"] = args.url if not args.no_filter: before = len(result["images"]) result["images"] = filter_meaningful_images(result["images"]) print(f"[OK] Filtered images: {before} -> {len(result['images'])}") if args.keep_html: result["raw_html"] = html # 统计 print(f"[OK] Title: {result['title']}") print(f"[OK] Sections: {len(result['sections'])}") print(f"[OK] Images: {len(result['images'])}") with open(args.output, "w", encoding="utf-8") as f: json.dump(result, f, ensure_ascii=False, indent=2) print(f"\n[Done] Saved to {args.output}") if __name__ == "__main__": main() -
generate_image.py 26.8 KB
"""运营文章配图生成脚本(对接 gpt-image-2 新接口) 基于 gpt-image-2 模型,支持文生图与图生图(传入参考图),仅依赖 Python 标准库。 对接红狐新版接口: SUBMIT: POST https://redfox.hk/story/api/parseWork/imageGen/gptImage2Submit RESULT: POST https://redfox.hk/story/api/parseWork/imageGen/gptImage2Result 新接口提交体: prompt / resolution(1k|2k|4k) / size(宽高比) / n(1-4) / referenceImages(最多 2 张) 新接口返回体关键字段: data.status ∈ {completed, processing, failed} / data.imageUrls[] / data.progress / data.failReason 用法: # 文生图(默认 16:9 + 2k) python generate_image.py --prompt "图片描述" --api-key "ak_xxx" # 指定宽高比与分辨率档位 python generate_image.py --prompt "..." --size 9:16 --resolution 4k --api-key "ak_xxx" # 图生图(传入参考图) python generate_image.py --prompt "图片描述" --image assets/skill标题.jpg --api-key "ak_xxx" # 链接仿写模式(红狐风格不启用,参考图作为风格参照) python generate_image.py --prompt "..." --reference-image "https://xxx.com/img.jpg" --style reference --api-key "ak_xxx" # 风格切换 python generate_image.py --prompt "..." --style {redfox|popcomic|yellowcomic|reference|none|random} --api-key "ak_xxx" # 批量生成(prompts.json 中可指定 image/size/resolution/n/style 字段) python generate_image.py --batch prompts.json --api-key "ak_xxx" # 查询已有任务 python generate_image.py --task-id "task_xxx" --api-key "ak_xxx" 已弃用参数(新接口不再支持,传入会被忽略并提示): --quality / --fidelity prompts.json 格式: [ {"id": "img1", "prompt": "...", "chapter": "封面", "image": "assets/skill标题.jpg", "style": "redfox", "size": "16:9", "resolution": "2k", "n": 1}, {"id": "img2", "prompt": "...", "chapter": "热点分析", "image": "assets/02.jpg", "style": "reference"}, {"id": "img3", "prompt": "...", "chapter": "操作指南", "style": "popcomic"} ] style 参数说明: - redfox(默认):红狐讲解员 + 美式复古报刊暖米黄底 - popcomic:现代波普漫画风 + 亮黄底 + 多角色场景 + 浮动图标 - yellowcomic:亮黄信息图漫画风 + 斜纹底 + 分类表格中心 - reference:原 prompt 不变,仅作图生图参考(适合链接仿写) - none:原 prompt 不动,不追加任何风格修饰 - random:从 redfox / popcomic / yellowcomic 中随机选一个,同批次所有图用同一风格 """ import argparse import json import os import sys import time import random import urllib.request import urllib.error import ssl import tempfile from pathlib import Path SUBMIT_URL = "https://redfox.hk/story/api/parseWork/imageGen/gptImage2Submit" RESULT_URL = "https://redfox.hk/story/api/parseWork/imageGen/gptImage2Result" UPLOAD_URL = "https://redfox.hk/story/api/parseWork/imageGen/uploadImage" CONFIG_DIR = Path.home() / ".qoder" / "apis" CONFIG_FILE = CONFIG_DIR / "redfox.json" ENV_KEY = "REDFOX_API_KEY" # 新接口 gptImage2Submit 参数 # prompt / resolution(1k|2k|4k) / size(宽高比) / n(1-4) / referenceImages(最多2张) DEFAULT_RESOLUTION = "2k" DEFAULT_ASPECT = "16:9" DEFAULT_N = 1 POLL_INTERVAL = 5 MAX_POLLS = 40 # 最长等待 ~200 秒 # 新接口 size 支持的宽高比 VALID_ASPECTS = { "1:1", "3:2", "2:3", "4:3", "3:4", "5:4", "4:5", "16:9", "9:16", "2:1", "1:2", "21:9", "9:21", } VALID_RESOLUTIONS = {"1k", "2k", "4k"} MAX_REFERENCE_IMAGES = 2 MAX_COUNT = 4 # 兼容旧像素尺寸:像素 → (新接口 size 宽高比, 推荐 resolution 档位) LEGACY_SIZE_MAP = { "1024x1024": ("1:1", "1k"), "1024x1536": ("2:3", "1k"), "1536x1024": ("3:2", "1k"), "1792x1024": ("16:9", "1k"), "1024x1792": ("9:16", "1k"), "2048x2048": ("1:1", "2k"), "2048x1152": ("16:9", "2k"), "1152x2048": ("9:16", "2k"), } # 兼容旧脚本中出现的 ALLOWED_SIZES 引用(含像素 + 宽高比) ALLOWED_SIZES = set(LEGACY_SIZE_MAP.keys()) | VALID_ASPECTS MAX_PROMPT_LENGTH = 500 def normalize_size(size_arg, resolution_arg=None): """把 --size 归一化为新接口所需的 (aspect, resolution)。 - 像素格式(旧):1792x1024 → ("16:9", "1k");如显式传入 resolution 则以其为准 - 宽高比格式(新):16:9 → ("16:9", resolution_arg or "2k") """ size_str = (size_arg or DEFAULT_ASPECT).strip() if size_str in LEGACY_SIZE_MAP: aspect, default_res = LEGACY_SIZE_MAP[size_str] resolution = (resolution_arg or default_res).strip().lower() elif size_str in VALID_ASPECTS: aspect = size_str resolution = (resolution_arg or DEFAULT_RESOLUTION).strip().lower() else: print(f"[ERR] Unsupported size: {size_str}") print(f" 宽高比可选: {', '.join(sorted(VALID_ASPECTS))}") print(f" 兼容旧像素: {', '.join(sorted(LEGACY_SIZE_MAP.keys()))}") sys.exit(1) if resolution not in VALID_RESOLUTIONS: print(f"[ERR] Unsupported resolution: {resolution} (可选 1k/2k/4k)") sys.exit(1) return aspect, resolution # 风格修饰器:三种可选视觉风格 # 风格 A — redfox:红狐讲解员 IP + 美式复古报刊暖米黄底 REDFOX_STYLE_SUFFIX = ( ",画面中心为红色狐狸讲解员(红色毛皮、白色胸腹、大尾巴、海军蓝小马甲、圆框眼镜)," "美式复古报刊科普条漫风格,横向宽幅多格分镜,胶片颗粒质感," "无装饰性边框,狐狸为视觉中心,参考 prompt 描述的姿态(封面=欢迎、分析=前倾拿教鞭、" "方法论=托腮沉思、操作=坐在电脑前打字)" ) # 风格 B — popcomic:现代波普漫画风 + 亮黄底 + 多角色场景(基于 assets/02.jpg) POPCOMIC_STYLE_SUFFIX = ( ",现代波普漫画信息图插画风格,高饱和亮黄色底色配波普网点纹理,粗黑轮廓线+扁平高饱和色块填充," "画面中心为一位干练的讲解员卡通人物(职业装/衬衫,表情自信开朗),左侧1-2位困惑的从业者卡通人物带问号和对话气泡,右侧可有1-2位角色互动," "顶部圆角矩形横幅内大号加粗中文主标题,周围浮动UI装饰图标(图表/邮件/齿轮/搜索框/对话框)," "人物间有白色圆角对话气泡承载中文文案,动态活泼构图,2D矢量商业插画,8K高清;" "禁止写实摄影、3D渲染、暗沉阴影、杂乱线条、水印、模糊字体、文字乱码" ) # 风格 C — yellowcomic:亮黄信息图漫画风 + 斜纹底 + 分类表格中心(基于 assets/03.jpg) YELLOWCOMIC_STYLE_SUFFIX = ( ",亮黄色斜纹底色现代信息图漫画插画风格,粗重清晰黑色轮廓线,纯色平涂高饱和马卡龙色块," "画面中心为一位自信的讲解员手持分类表格或文档(职业装,表情从容),左侧困惑角色群带问号气泡,右侧角色可互动," "顶部圆角矩形横幅大号加粗中文标题,周围散布齿轮/文档/聊天框/折线图等操作类图标装饰," "人物对话气泡承载中文文案,流程感和分工感强,2D矢量商业插画,8K高清;" "禁止写实摄影、3D渲染、暗沉阴影、杂乱线条、水印、模糊字体、文字乱码" ) # 所有可用风格(random 从中选取,排除 reference 和 none) RANDOMIZABLE_STYLES = ["redfox", "popcomic", "yellowcomic"] VALID_STYLES = {"redfox", "popcomic", "yellowcomic", "reference", "none", "random"} def apply_style(prompt, style): """根据 style 参数对 prompt 做风格修饰。 - redfox: 追加红狐讲解员 + 美式复古报刊条漫风格 - popcomic: 追加现代波普漫画风(亮黄底+多角色场景) - yellowcomic: 追加亮黄信息图漫画风(斜纹底+分类表格中心) - reference: 原 prompt 不变(参考图自带风格) - none: 原 prompt 不变 """ style_suffix_map = { "redfox": REDFOX_STYLE_SUFFIX, "popcomic": POPCOMIC_STYLE_SUFFIX, "yellowcomic": YELLOWCOMIC_STYLE_SUFFIX, } suffix = style_suffix_map.get(style) if suffix: if len(prompt) + len(suffix) > MAX_PROMPT_LENGTH: available = MAX_PROMPT_LENGTH - len(suffix) - 3 prompt = prompt[:available] + "..." return prompt + suffix # reference / none:原样返回 return prompt def pick_random_style(): """从 RANDOMIZABLE_STYLES 中随机选一个风格名称。""" return random.choice(RANDOMIZABLE_STYLES) def get_api_key(cli_key=None): """Get API key: CLI arg > env var > config file.""" if cli_key: return cli_key env_key = os.environ.get(ENV_KEY) if env_key: return env_key if CONFIG_FILE.exists(): try: data = json.loads(CONFIG_FILE.read_text(encoding="utf-8")) key = data.get("api_key") if key: return key except (json.JSONDecodeError, OSError): pass return None def make_request(url, data, api_key, timeout=30): """Send a JSON POST request and return parsed response.""" payload = json.dumps(data).encode("utf-8") req = urllib.request.Request( url, data=payload, headers={ "Content-Type": "application/json", "REDFOX_API_KEY": api_key, }, method="POST", ) try: with urllib.request.urlopen(req, timeout=timeout) as resp: body = json.loads(resp.read().decode("utf-8")) return body except urllib.error.HTTPError as e: error_body = e.read().decode("utf-8", errors="replace") return {"code": -1, "msg": f"HTTP {e.code}: {error_body}"} except Exception as e: return {"code": -1, "msg": str(e)} def upload_image(image_path, api_key): """Upload a local image to OSS, return the image URL.""" if not os.path.isfile(image_path): print(f"[ERR] Image file not found: {image_path}") return None ext = os.path.splitext(image_path)[1].lower() fmt_map = {".png": "png", ".jpg": "jpeg", ".jpeg": "jpeg", ".webp": "webp"} fmt = fmt_map.get(ext, "png") print(f"[UP] Uploading image: {image_path}") boundary = "----PythonFormBoundary7MA4YWxkTrZu0gW" filename = os.path.basename(image_path) with open(image_path, "rb") as f: file_data = f.read() body = ( f"--{boundary}\r\n" f'Content-Disposition: form-data; name="format"\r\n\r\n' f"{fmt}\r\n" f"--{boundary}\r\n" f'Content-Disposition: form-data; name="file"; filename="{filename}"\r\n' f"Content-Type: image/{fmt}\r\n\r\n" ).encode("utf-8") + file_data + f"\r\n--{boundary}--\r\n".encode("utf-8") req = urllib.request.Request( UPLOAD_URL, data=body, headers={ "Content-Type": f"multipart/form-data; boundary={boundary}", "X-API-KEY": api_key, }, method="POST", ) try: with urllib.request.urlopen(req, timeout=60) as resp: result = json.loads(resp.read().decode("utf-8")) except Exception as e: print(f"[ERR] Upload failed: {e}") return None code = result.get("code") if not str(code).startswith("2"): print(f"[ERR] Upload failed (code {code}): {result.get('msg', '')}") return None image_url = result.get("data", {}).get("imageUrl") if not image_url: print("[ERR] Upload succeeded but no imageUrl returned") return None print(f"[OK] Upload complete: {image_url}") return image_url def is_url(path): """Check if a string is an HTTP(S) URL.""" return path.startswith("http://") or path.startswith("https://") def download_url_to_temp(url): """Download a URL to a temp file, return the temp file path.""" ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE ext = os.path.splitext(url.split("?")[0])[1] or ".webp" tmp = tempfile.NamedTemporaryFile(suffix=ext, delete=False) print(f"[DL] Downloading: {url}") try: req = urllib.request.Request(url) with urllib.request.urlopen(req, context=ctx) as resp: tmp.write(resp.read()) tmp.close() print(f"[OK] Downloaded to: {tmp.name}") return tmp.name except Exception as e: print(f"[ERR] Download failed: {e}") tmp.close() return None def download_images(image_urls, output_dir, prefix="image"): """Download generated images to output directory.""" downloaded = [] total = len(image_urls) ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE for i, url in enumerate(image_urls, 1): ext = ".png" url_path = url.split("?")[0] for fmt in [".png", ".jpg", ".jpeg", ".webp"]: if url_path.lower().endswith(fmt): ext = fmt break filename = f"{prefix}_{i}{ext}" if total > 1 else f"{prefix}{ext}" filepath = os.path.join(output_dir, filename) print(f"[DL] Downloading {i}/{total}: {filename}") try: req = urllib.request.Request(url) with urllib.request.urlopen(req, context=ctx) as resp: with open(filepath, "wb") as f: f.write(resp.read()) size_kb = os.path.getsize(filepath) / 1024 print(f" OK: {filepath} ({size_kb:.1f} KB)") downloaded.append(filepath) except Exception as e: print(f" ERR: Download failed: {e}") return downloaded def submit_task(prompt, api_key, image_path=None, size=None, resolution=None, n=1, style="redfox", fidelity=None, quality=None): """Submit an image generation task via gptImage2Submit. Returns taskId or None. 新接口请求体: { "prompt": "...", "resolution": "1k|2k|4k", "size": "16:9", "n": 1-4, "referenceImages": ["url1", "url2"] } style: - redfox: 红狐讲解员 + 美式复古报刊风格(默认) - popcomic: 现代波普漫画风(亮黄底+多角色场景) - yellowcomic: 亮黄信息图漫画风(斜纹底+分类表格中心) - reference: prompt 不变,靠参考图自带的风格 - none: prompt 不变 - random: 从 redfox/popcomic/yellowcomic 中随机选一个 fidelity / quality 为旧接口参数,新接口不再支持,仅保留形参避免破坏旧调用方。 """ if fidelity: print(f"[WARN] --fidelity 在新接口已弃用,忽略值: {fidelity}") if quality: print(f"[WARN] --quality 在新接口已弃用,忽略值: {quality}(如需控制清晰度请用 --resolution)") # 风格修饰:random 需在此处解析为具体风格 if style == "random": style = pick_random_style() print(f"[OK] Random style selected: {style}") if style not in VALID_STYLES: print(f"[WARN] Unknown style '{style}', fallback to 'redfox'") style = "redfox" final_prompt = apply_style(prompt, style) if final_prompt != prompt: print(f"[OK] Style applied: {style} (+{len(final_prompt) - len(prompt)} chars)") elif style in ("reference", "none"): print(f"[OK] Style applied: {style} (no modification)") # 参考图处理(支持单张路径/URL,上传后归一到 referenceImages 数组) reference_images = [] if image_path: if isinstance(image_path, list): candidates = image_path[:MAX_REFERENCE_IMAGES] else: candidates = [image_path] for cand in candidates: if not cand: continue if is_url(cand): temp_path = download_url_to_temp(cand) if not temp_path: return None image_url = upload_image(temp_path, api_key) try: os.unlink(temp_path) except OSError: pass if not image_url: return None print(f"[OK] Mode: image-to-image, ref URL -> OSS: {image_url}") else: image_url = upload_image(cand, api_key) if not image_url: return None print(f"[OK] Mode: image-to-image, ref: {cand}") reference_images.append(image_url) else: print("[OK] Mode: text-to-image") # 尺寸/分辨率归一化为新接口格式 aspect, resolution_final = normalize_size(size, resolution) # n 限制在新接口上限内 try: n_int = int(n) if n is not None else DEFAULT_N except (TypeError, ValueError): n_int = DEFAULT_N n_int = max(1, min(MAX_COUNT, n_int)) data = { "prompt": final_prompt, "resolution": resolution_final, "size": aspect, "n": n_int, "referenceImages": reference_images, "source": "图文运营创作器-GitHub", } print(f"[OK] Submit payload: size={aspect}, resolution={resolution_final}, n={n_int}, refs={len(reference_images)}") resp = make_request(SUBMIT_URL, data, api_key) if resp.get("code") == 2000 and (resp.get("data") or {}).get("taskId"): task_id = resp["data"]["taskId"] print(f"[OK] Task submitted: {task_id}") return task_id else: print(f"[ERR] Submit failed (code={resp.get('code')}): {resp.get('msg', 'unknown error')}") return None def poll_result(task_id, api_key): """Poll gptImage2Result until completed/failed/timeout, return imageUrls list.""" for i in range(MAX_POLLS): resp = make_request(RESULT_URL, {"taskId": task_id}, api_key, timeout=15) if resp.get("code") != 2000: print(f"[WARN] Poll {i+1}/{MAX_POLLS}: API error - {resp.get('msg')}") time.sleep(POLL_INTERVAL) continue data = resp.get("data") or {} status = data.get("status", "") if status == "completed": urls = data.get("imageUrls") or [] if isinstance(urls, str): urls = [urls] print(f"[OK] Task {task_id} completed: {urls}") return urls elif status == "failed": reason = data.get("failReason") or "unknown" print(f"[ERR] Task {task_id} failed: {reason}") return None else: progress = data.get("progress") elapsed = (i + 1) * POLL_INTERVAL suffix = f" {progress}%" if isinstance(progress, int) else "" print(f"[...] Poll {i+1}/{MAX_POLLS}: {status or 'processing'}{suffix} ({elapsed}s elapsed)") time.sleep(POLL_INTERVAL) print(f"[TIMEOUT] Task {task_id} timed out after {MAX_POLLS * POLL_INTERVAL}s") return None def generate_single(prompt, api_key, image_path=None, size=None, resolution=None, n=1, style="redfox", fidelity=None, quality=None): """Generate a single image. Returns list of URLs or empty list.""" task_id = submit_task( prompt, api_key, image_path=image_path, size=size, resolution=resolution, n=n, style=style, fidelity=fidelity, quality=quality, ) if not task_id: return [] result = poll_result(task_id, api_key) return result or [] def generate_batch(prompts_file, api_key, default_style="redfox", default_resolution=None, default_n=1): """Generate multiple images from a JSON file. Returns dict of id -> URLs. 每个 item 可独立指定 style/size/resolution/n 字段;未指定则用默认值。 当 default_style 为 random 时,会先随机选一个具体风格,整批所有图都用该风格(保证同批次一致)。 item 字段支持: id, prompt, image, style, size, resolution, n 已弃用字段(新接口不支持,传入则忽略): fidelity, quality """ base_dir = os.path.dirname(os.path.abspath(prompts_file)) with open(prompts_file, "r", encoding="utf-8") as f: items = json.load(f) # 如果默认风格是 random,整批统一选一个 if default_style == "random": resolved_default = pick_random_style() print(f"[OK] Batch random style selected: {resolved_default} (all items will use this style)") else: resolved_default = default_style results = {} tasks = {} for item in items: img_id = item.get("id", f"img_{len(tasks)}") prompt = item["prompt"] image_path = item.get("image") size = item.get("size") resolution = item.get("resolution", default_resolution) n = item.get("n", default_n) fidelity = item.get("fidelity") # 已弃用,仅传递给 submit_task 触发提示 quality = item.get("quality") # 已弃用 # 优先用 item 自身的 style,否则用已解析的 default item_style = item.get("style", resolved_default) if image_path and not os.path.isabs(image_path) and not is_url(image_path): image_path = os.path.join(base_dir, image_path) task_id = submit_task( prompt, api_key, image_path=image_path, size=size, resolution=resolution, n=n, style=item_style, fidelity=fidelity, quality=quality, ) if task_id: tasks[img_id] = task_id for img_id, task_id in tasks.items(): urls = poll_result(task_id, api_key) results[img_id] = urls or [] return results def main(): parser = argparse.ArgumentParser( description="运营文章配图生成(对接 gptImage2Submit/gptImage2Result 新接口,支持文生图 + 图生图 + 批量)", formatter_class=argparse.RawDescriptionHelpFormatter, ) parser.add_argument("--prompt", type=str, help="单张图片 prompt") parser.add_argument("--batch", type=str, help="批量生成 JSON 文件路径") parser.add_argument("--image", type=str, default=None, help="参考图路径或 URL(启用图生图模式)") parser.add_argument("--reference-image", type=str, default=None, help="参考图别名(等同于 --image,但语义更清晰)") parser.add_argument("--size", type=str, default=DEFAULT_ASPECT, help=f"图片尺寸:优先传宽高比(默认 {DEFAULT_ASPECT},可选: {', '.join(sorted(VALID_ASPECTS))});" f"也兼容旧像素格式({', '.join(sorted(LEGACY_SIZE_MAP.keys()))})") parser.add_argument("--resolution", type=str, default=None, choices=sorted(VALID_RESOLUTIONS), help="分辨率档位 1k/2k/4k(默认:像素格式自动匹配档位,宽高比格式默认 2k)") parser.add_argument("-n", "--count", type=int, default=DEFAULT_N, help=f"生成图片数量 (1-{MAX_COUNT},默认 {DEFAULT_N},新接口上限 4)") parser.add_argument("--style", type=str, default="redfox", choices=sorted(VALID_STYLES), help="风格模式:redfox(默认)/ popcomic / yellowcomic / reference / none / random") parser.add_argument("--api-key", type=str, default=None, help="REDFOX_API_KEY") parser.add_argument("--output", type=str, default="results.json", help="结果输出文件") parser.add_argument("--task-id", type=str, default=None, help="查询已有任务结果(跳过提交)") parser.add_argument("--download-dir", type=str, default=None, help="下载图片到本地目录(不传则仅返回 URL)") # 已弃用参数(新接口不再支持,保留仅为向后兼容 CLI) parser.add_argument("--fidelity", type=str, default=None, choices=["high", "low"], help="[已弃用] 新接口不再支持 inputFidelity,此参数被忽略") parser.add_argument("--quality", type=str, default=None, choices=["low", "medium", "high", "auto"], help="[已弃用] 新接口不再支持 quality,请用 --resolution 控制清晰度") args = parser.parse_args() # 兼容 --reference-image 与 --image if args.reference_image and not args.image: args.image = args.reference_image # 弃用参数提示 if args.fidelity: print(f"[WARN] --fidelity 在新接口已弃用,将被忽略(传入值: {args.fidelity})") if args.quality: print(f"[WARN] --quality 在新接口已弃用,将被忽略(传入值: {args.quality}),如需控制清晰度请用 --resolution") # 尺寸预校验(具体归一化在 submit_task 内部完成) normalize_size(args.size, args.resolution) # n 上限校验 if args.count < 1 or args.count > MAX_COUNT: print(f"[ERR] --count/-n 取值范围 1-{MAX_COUNT}(新接口上限 4)") sys.exit(1) api_key = get_api_key(cli_key=args.api_key) if not api_key: print("[ERR] REDFOX_API_KEY is required. Set via --api-key or REDFOX_API_KEY env var.") print(" Get key: https://redfox.hk/settings/api-keys?source=skillhub") sys.exit(1) # Mode: Query existing task if args.task_id: print(f"[QUERY] Querying task: {args.task_id}") urls = poll_result(args.task_id, api_key) if urls: results = {"query": urls} if args.download_dir: os.makedirs(args.download_dir, exist_ok=True) downloaded = download_images(urls, args.download_dir) results["downloaded"] = downloaded else: results = {"query": []} with open(args.output, "w", encoding="utf-8") as f: json.dump(results, f, ensure_ascii=False, indent=2) print(json.dumps(results, ensure_ascii=False, indent=2)) sys.exit(0 if urls else 1) # Validate prompt length if args.prompt and len(args.prompt) > MAX_PROMPT_LENGTH: print(f"[ERR] Prompt too long ({len(args.prompt)} chars), max {MAX_PROMPT_LENGTH}") sys.exit(1) if args.batch: results = generate_batch( args.batch, api_key, default_style=args.style, default_resolution=args.resolution, default_n=args.count, ) elif args.prompt: urls = generate_single( args.prompt, api_key, image_path=args.image, size=args.size, resolution=args.resolution, n=args.count, style=args.style, fidelity=args.fidelity, quality=args.quality, ) results = {"single": urls} if urls and args.download_dir: os.makedirs(args.download_dir, exist_ok=True) downloaded = download_images(urls, args.download_dir) results["downloaded"] = downloaded else: print("[ERR] Provide --prompt or --batch") sys.exit(1) with open(args.output, "w", encoding="utf-8") as f: json.dump(results, f, ensure_ascii=False, indent=2) print(f"\n[Done] Results saved to {args.output}") print(json.dumps(results, ensure_ascii=False, indent=2)) if __name__ == "__main__": main() -
validate_article.py 19.4 KB
"""文章质量验证脚本(v2.3 软阈值版) 把 SKILL.md 中的质量检查清单从人工 checklist 升级为可机械化验证的脚本。 v2.3 重要变化:所有阈值改为软建议——超范围只出 warning,不直接 fail。 写作框架应跟随 Skill 逻辑动态调整,不被硬阈值锁死。 使用方式: # 默认(软阈值,目标字数 1000) python validate_article.py --input article.md # 自定义字数目标 python validate_article.py --input article.md --target-length 1500 # 严格模式(恢复 v2.1 硬 fail 行为,仅在需要 CI 严格拦截时使用) python validate_article.py --input article.md --strict 检查项(v2.3 全部为 warning 级别,超范围不直接 fail): - 禁忌词检测(赋能、抓手、打法、闭环、颗粒度、对齐、沉淀、方法论前置) - 字数统计(默认 500-1500 区间,目标 1000 字) - 标题长度(8-30 字) - 章节数(2-8 节正文) - 表格数(0-3 张,0 张也合规) - 段落行数(每段不超过 5 行) - FAQ 数量(0 或 2-4 个,0 个也合规) - 信息密度:每 400 字至少 1 个数字 + 1 个动词开头的句子 - 链接/图片来源标注(资料来源章节) """ import argparse import json import re import sys from pathlib import Path # ---- 禁忌词表 ---- FORBIDDEN_WORDS = [ "赋能", "抓手", "打法", "闭环", "颗粒度", "对齐", "沉淀", "方法论前置", # 句式触发词(出现就扣分) ] FORBIDDEN_PATTERNS = [ r"在当今.{0,15}的时代", r"众所周知", r"不言而喻", r"!!!+", # 感叹号堆叠 ] # ---- 结构参数(v2.3 软阈值:所有检查默认 warning 级别)---- # 注意:v2.3 重要原则变化——所有这些阈值都是"软建议",不直接 fail。 # 写作时 LLM 应根据 Skill 逻辑、选题类型、读者需求动态判断。 TITLE_MIN_LEN = 8 TITLE_MAX_LEN = 30 # v2.3 放宽(v2.1 是 25) SECTION_MIN = 2 # v2.3 放宽(v2.1 是 3) SECTION_MAX = 8 # v2.3 放宽(v2.1 是 6) TABLE_MIN = 0 # v2.3 改 0:表格按需出现,不强制(v2.1 是 2) TABLE_MAX = 3 # v2.3 保持 PARAGRAPH_MAX_LINES = 5 # v2.3 放宽(v2.1 是 4) FAQ_COUNT_MIN = 2 # v2.3 改 2-4 区间(v2.1 是固定 3) FAQ_COUNT_MAX = 4 WORDS_PER_DENSITY_CHECK = 400 # v2.3 放宽(v2.1 是 300) # ---- 字数目标(v2.3 默认 1000 字左右,可配置)---- DEFAULT_TARGET_LENGTH = 1000 TARGET_LENGTH_TOLERANCE = 500 # ±500 字缓冲(如目标 1000 字,500-1500 都算 pass) def read_markdown(path): """读取 markdown 文件,去掉代码块和图片语法但保留行结构。""" p = Path(path) if not p.exists(): print(f"[ERR] File not found: {path}") sys.exit(1) return p.read_text(encoding="utf-8") def extract_title(markdown): """提取 H1 标题。""" for line in markdown.split("\n"): m = re.match(r"^#\s+(.+)$", line) if m: return m.group(1).strip() return "" def extract_sections(markdown): """提取 H2 章节(含 heading + content + 行号)。""" sections = [] lines = markdown.split("\n") current = None in_code = False for i, line in enumerate(lines): # 代码块跳过 if line.strip().startswith("```"): in_code = not in_code if current: current["content"] += line + "\n" continue if in_code: if current: current["content"] += line + "\n" continue m = re.match(r"^##\s+(.+)$", line) if m: if current: sections.append(current) current = { "heading": m.group(1).strip(), "content": "", "start_line": i, } else: if current is None: # 引言(首个 H2 之前) current = {"heading": "引言", "content": "", "start_line": 0} current["content"] += line + "\n" if current: sections.append(current) return sections def count_words(text): """统计字数(中文字 + 英文词)。""" # 去掉 markdown 标记 text = re.sub(r"```.*?```", "", text, flags=re.DOTALL) text = re.sub(r"`[^`]+`", "", text) text = re.sub(r"!\[.*?\]\(.*?\)", "", text) # 图片 text = re.sub(r"\[([^\]]+)\]\([^\)]+\)", r"\1", text) # 链接保留文字 text = re.sub(r"[#*_>|]", "", text) chinese = len(re.findall(r"[\u4e00-\u9fff]", text)) english = len(re.findall(r"\b[a-zA-Z]+\b", text)) return chinese + english def count_tables(markdown): """统计 markdown 表格数量(按 |...| 行匹配)。""" in_code = False table_count = 0 in_table = False for line in markdown.split("\n"): if line.strip().startswith("```"): in_code = not in_code continue if in_code: continue # 表格行:包含 | 且两端以 | 结尾 if re.match(r"^\s*\|.+\|\s*$", line): if not in_table: in_table = True table_count += 1 else: in_table = False return table_count def count_paragraph_lines(sections): """统计每段的行数,识别超过 4 行的段落。""" violations = [] for sec in sections: # 去掉表格、代码块、标题 text = sec["content"] text = re.sub(r"```.*?```", "", text, flags=re.DOTALL) text = re.sub(r"^\|.*$", "", text, flags=re.MULTILINE) # 表格行 text = re.sub(r"^#+\s+.*$", "", text, flags=re.MULTILINE) # 标题 # 按空行分段 paragraphs = re.split(r"\n\s*\n", text) for i, para in enumerate(paragraphs): para = para.strip() if not para: continue line_count = len([l for l in para.split("\n") if l.strip()]) if line_count > PARAGRAPH_MAX_LINES: violations.append({ "section": sec["heading"], "paragraph_index": i, "lines": line_count, "preview": para[:50] + "...", }) return violations def find_forbidden_words(markdown): """查找禁忌词及其出现位置。""" findings = [] lines = markdown.split("\n") for word in FORBIDDEN_WORDS: for i, line in enumerate(lines, 1): if word in line: findings.append({ "word": word, "line": i, "context": line.strip()[:80], }) for pattern in FORBIDDEN_PATTERNS: for i, line in enumerate(lines, 1): if re.search(pattern, line): findings.append({ "word": f"pattern: {pattern}", "line": i, "context": line.strip()[:80], }) return findings def extract_faq(sections): """提取 FAQ 章节的 Q&A 数量。 支持以下格式: - Q1: ... / Q1:... - **Q:...?** / **Q: ...?** - **Q1:...?** """ faq = None for sec in sections: if "FAQ" in sec["heading"] or "常见问题" in sec["heading"]: faq = sec break if not faq: return 0 # 多种格式都算 patterns = [ r"^Q\d+[::]", # Q1: / Q1: r"\*\*Q\d*[::]\s*", # **Q: / **Q1: r"^Q[::]", # Q: / Q: r"^##\s*Q\d+", # ## Q1 ] count = 0 for pattern in patterns: matches = re.findall(pattern, faq["content"], re.MULTILINE) count = max(count, len(matches)) return count def count_info_density(sections): """信息密度检查:每 300 字应至少 1 个数字 + 1 个动词开头的句子。 返回 violations 列表:每个元素是一个 dict{section, type, detail}。 """ violations = [] # 用前 30 个常见动词做检测,避免构造大正则出错 action_verbs_str = ( "打开|点击|输入|选择|设置|使用|访问|下载|安装|运行|执行|创建|添加|删除|修改|" "检查|对比|避免|记住|关注|尝试|记下|抄|看|找|想|算|做|选|配|加|减|分|拆|排|" "写|画|测|跑|试|调|查|读|听|问|答|记|列|提|交|练|统|归|汇|制|定|布|署|" "进入|退出|返回|跳转|切到|换到|转到|改到|调到|移到|拉|推|滚|翻|" "请|必须|需要|应当|应该|可以|可能|建议|推荐|考虑" ) for sec in sections: if "FAQ" in sec["heading"] or "结语" in sec["heading"]: continue text = re.sub(r"```.*?```", "", sec["content"], flags=re.DOTALL) text = re.sub(r"^\|.*$", "", text, flags=re.MULTILINE) text = re.sub(r"^#+\s+.*$", "", text, flags=re.MULTILINE) text = re.sub(r"!\[.*?\]\(.*?\)", "", text) text = re.sub(r"\[([^\]]+)\]\([^\)]+\)", r"\1", text) words = count_words(text) if words == 0: continue # 检查密度 expected_density = max(1, words // WORDS_PER_DENSITY_CHECK) # 数字数量 numbers = re.findall(r"\d+(?:\.\d+)?", text) if len(numbers) < expected_density: violations.append({ "section": sec["heading"], "type": "low_number_density", "expected": expected_density, "actual": len(numbers), "words": words, }) # 动词句数量(行首是动词) action_lines = re.findall(rf"^(?:{action_verbs_str})", text, re.MULTILINE) if len(action_lines) < expected_density: violations.append({ "section": sec["heading"], "type": "low_action_density", "expected": expected_density, "actual": len(action_lines), "words": words, }) return violations def has_source_section(sections): """检查是否有资料来源章节。""" for sec in sections: if "资料来源" in sec["heading"] or "参考" in sec["heading"]: return True return False def check_paragraph_lines(sections): """段落行数检查:每段不超过 4 行(去掉空行/标题/表格)。""" return count_paragraph_lines(sections) def main(): parser = argparse.ArgumentParser( description="运营文章质量验证(v2.3 软阈值版,所有检查默认 warning 级别)", formatter_class=argparse.RawDescriptionHelpFormatter, ) parser.add_argument("--input", type=str, required=True, help="markdown 文件路径") parser.add_argument("--output", type=str, default="validation_report.json", help="验证报告输出路径") parser.add_argument("--strict", action="store_true", help="严格模式(恢复 v2.1 硬 fail 行为):任一 fail 退出码 1") parser.add_argument("--target-length", type=int, default=DEFAULT_TARGET_LENGTH, help=f"目标字数(v2.3 默认 {DEFAULT_TARGET_LENGTH}),配合 ±{TARGET_LENGTH_TOLERANCE} 缓冲检查") parser.add_argument("--mode", type=str, choices=["link_rewrite", "skill_promo", "add_images"], default=None, help="文章生成模式:link_rewrite 模式下跳过字数检查(跟随源文章长度)") args = parser.parse_args() markdown = read_markdown(args.input) target_length = args.target_length target_min = max(200, target_length - TARGET_LENGTH_TOLERANCE) target_max = target_length + TARGET_LENGTH_TOLERANCE # ---- 执行各项检查 ---- # v2.3 重要变化:所有检查默认输出 status ∈ {pass, warning},不再有 fail # 除非 --strict 模式(恢复 v2.1 硬 fail 行为) report = { "file": args.input, "config": { "target_length": target_length, "target_range": f"{target_min}-{target_max}", "mode": "strict" if args.strict else "soft", "version": "v2.3", }, "checks": {}, "summary": {"passed": 0, "warnings": 0, "failed": 0}, } # 1. 标题长度(v2.3:超范围只出 warning) title = extract_title(markdown) title_len = len(title) title_ok = TITLE_MIN_LEN <= title_len <= TITLE_MAX_LEN if args.strict: status = "pass" if title_ok else "fail" else: status = "pass" if title_ok else "warning" report["checks"]["title_length"] = { "title": title, "length": title_len, "expected_range": f"{TITLE_MIN_LEN}-{TITLE_MAX_LEN}", "status": status, "note": "v2.3 软化:超范围仅提示,不强制" if not title_ok and not args.strict else None, } report["summary"][status + ("ed" if status != "warning" else "s")] += 1 # 2. 章节数(v2.3:超范围只出 warning) sections = extract_sections(markdown) section_count = len(sections) section_ok = SECTION_MIN <= section_count <= SECTION_MAX if args.strict: status = "pass" if section_ok else "fail" else: status = "pass" if section_ok else "warning" report["checks"]["section_count"] = { "count": section_count, "expected_range": f"{SECTION_MIN}-{SECTION_MAX}", "status": status, "note": "v2.3 软化:2-8 节均为合理区间" if not section_ok and not args.strict else None, } report["summary"][status + ("ed" if status != "warning" else "s")] += 1 # 3. 表格数(v2.3:v2.1 强制 2-3 张 → v2.3 改为 0-3 张,0 张也合规) table_count = count_tables(markdown) table_ok = TABLE_MIN <= table_count <= TABLE_MAX if args.strict: # strict 模式仍按 v2.1 标准:至少 2 张 table_ok_strict = 2 <= table_count <= 3 status = "pass" if table_ok_strict else "fail" else: status = "pass" if table_ok else "warning" note = None if not table_ok and not args.strict: if table_count == 0: note = "v2.3 软化:0 张表也合规(叙事/资讯类常见)" else: note = "v2.3 软化:表格按需出现,不强制" report["checks"]["table_count"] = { "count": table_count, "expected_range": f"{TABLE_MIN}-{TABLE_MAX}", "status": status, "note": note, } report["summary"][status + ("ed" if status != "warning" else "s")] += 1 # 4. 字数(v2.3:默认 1000 字,可配置 target-length;link_rewrite 模式跳过) total_words = count_words(markdown) if args.mode == "link_rewrite": # link_rewrite 跟随源文章长度,不做字数约束 report["checks"]["word_count"] = { "words": total_words, "expected_range": "不限制(link_rewrite 模式跟随源文章长度)", "status": "pass", "note": "link_rewrite 模式不受字数约束", } report["summary"]["passed"] += 1 else: if args.strict: word_ok = 1500 <= total_words <= 4000 # v2.1 硬阈值 expected_range = "1500-4000" else: word_ok = target_min <= total_words <= target_max expected_range = f"{target_min}-{target_max}(目标 {target_length})" report["checks"]["word_count"] = { "words": total_words, "expected_range": expected_range, "status": "pass" if word_ok else "warning", "note": "v2.3 默认 1000 字左右,可通过 --target-length 自定义" if not word_ok and not args.strict else None, } report["summary"]["passed" if word_ok else "warnings"] += 1 # 5. FAQ 数量(v2.3:2-4 个合理区间,0 个也允许) faq_count = extract_faq(sections) if args.strict: faq_ok = faq_count == 3 else: faq_ok = (faq_count == 0) or (FAQ_COUNT_MIN <= faq_count <= FAQ_COUNT_MAX) report["checks"]["faq_count"] = { "count": faq_count, "expected_range": f"0 或 {FAQ_COUNT_MIN}-{FAQ_COUNT_MAX}" if not args.strict else "恰好 3", "status": "pass" if faq_ok else "warning", "note": "v2.3 软化:FAQ 数量动态生成,0/2/3/4 都合规" if not faq_ok and not args.strict else None, } report["summary"]["passed" if faq_ok else "warnings"] += 1 # 6. 资料来源(v2.3:v2.1 强制 → v2.3 改为软) source_ok = has_source_section(sections) if args.strict: status = "pass" if source_ok else "fail" else: status = "pass" if source_ok else "warning" report["checks"]["source_section"] = { "present": source_ok, "status": status, "note": "v2.3 软化:资料来源仅在涉及外部数据时推荐" if not source_ok and not args.strict else None, } report["summary"][status + ("ed" if status != "warning" else "s")] += 1 # 7. 禁忌词(v2.3:v2.1 强制 → v2.3 改为 warning) forbidden = find_forbidden_words(markdown) forbidden_ok = len(forbidden) == 0 if args.strict: status = "pass" if forbidden_ok else "fail" else: status = "pass" if forbidden_ok else "warning" report["checks"]["forbidden_words"] = { "count": len(forbidden), "findings": forbidden[:5], "status": status, "note": "v2.3 软化:禁忌词仅提示,调用方决定替换" if not forbidden_ok and not args.strict else None, } report["summary"][status + ("ed" if status != "warning" else "s")] += 1 # 8. 段落行数(v2.3:v2.1 强制 4 → v2.3 改 5,软警告) para_violations = check_paragraph_lines(sections) para_ok = len(para_violations) == 0 if args.strict: status = "pass" if para_ok else "fail" else: status = "pass" if para_ok else "warning" report["checks"]["paragraph_lines"] = { "violations": len(para_violations), "max_allowed": PARAGRAPH_MAX_LINES, "details": para_violations[:5], "status": status, "note": f"v2.3 软化:每段 ≤ {PARAGRAPH_MAX_LINES} 行(v2.1 是 4)" if not para_ok and not args.strict else None, } report["summary"][status + ("ed" if status != "warning" else "s")] += 1 # 9. 信息密度 density_violations = count_info_density(sections) density_ok = len(density_violations) == 0 report["checks"]["info_density"] = { "violations": len(density_violations), "details": density_violations[:5], "status": "pass" if density_ok else "warning", } if density_ok: report["summary"]["passed"] += 1 else: report["summary"]["warnings"] += 1 # ---- 输出 ---- with open(args.output, "w", encoding="utf-8") as f: json.dump(report, f, ensure_ascii=False, indent=2) # ---- 控制台摘要 ---- print(f"\n{'='*60}") print(f"质检报告:{args.input}") print(f"{'='*60}") for name, check in report["checks"].items(): # v2.3 状态图标:纯 ASCII 字符,避免 Windows GBK 编码问题 status_icon = {"pass": "[OK]", "fail": "[X]", "warning": "[!]"}[check["status"]] print(f" {status_icon} {name}: {check['status']}") print(f"{'='*60}") print(f" Pass: {report['summary']['passed']} Fail: {report['summary']['failed']} Warning: {report['summary']['warnings']}") print(f" Report saved to: {args.output}") # 退出码 # v2.3 soft 模式(默认):退出码恒为 0,所有检查均为建议 # v2.1 strict 模式(兼容):任一 fail 退出码 1 if args.strict and report["summary"]["failed"] > 0: sys.exit(1) sys.exit(0) if __name__ == "__main__": main()
-
-
README.en.md 5.9 KB
# Visual Ops Writer / visual-ops-writer --- ## Overview A full-pipeline operations article tool covering everything from topic selection, writing, and illustration to quality checks. **Rewrite** any article by learning its style, **generate** promotional long-form content for a Skill, or **auto-illustrate** existing text — all with images included and ready to publish. **Core Value** - **Three scenarios, one tool**: Link rewriting, product promotion, and article illustration — the three highest-frequency content creation scenarios, all covered. - **Text + image delivery**: Articles and images are generated together and saved as a complete, ready-to-use file — not a half-finished draft. - **Built-in quality**: Every article goes through 9 quality checks automatically, with a report included — ready to publish. **Intended Users** - 📝 **Content operators** — Produce style-matched articles and promotional content from writing to illustration, all in one go. - 🛍️ **Brand / skill developers** — Auto-generate promotional long-form articles aligned with actual product capabilities. - 🏢 **Bloggers / editors** — Smart image placement for existing articles, no manual planning needed. --- ## Features ### Core Capabilities - **Link rewrite**: Provide any article link to automatically fetch its content and images, learn the style and structure, then produce a new article from a different angle with equivalent quality. Image styles match the originals — ideal for competitive analysis and content benchmarking. - **Skill promo**: Provide a Skill file path or name to auto-parse product capabilities and fetch the latest info from the target site, generating a promotional long-form article aligned with actual product features. Images use the RedFox illustrator style for visual and content consistency. - **Add images**: Provide complete article text to automatically analyze structure by section semantics and word density, intelligently determine image placement and count, then batch-generate consistent illustrations. Perfect for when you've finished writing but don't want to manually find images. - **Automated quality check**: Every article goes through 9 quality checks after generation (title length, section count, table count, word count, FAQ count, prohibited words, paragraph length, information density, etc.) with a JSON report output. ### Writing Capabilities - **Auto article type matching**: Three templates — tool promotion, methodology, and crisis response — automatically selected based on topic - **Reader persona adaptation**: Supports individual creators and team/enterprise reader profiles with automatic expression adjustment - **Tone options**: Direct & candid / Professional & rigorous / Casual & conversational — three tones to choose from - **Word count control**: Customizable word count range (default 2000-3000 words), or adaptive to original article length ### Illustration Capabilities - **Style transfer**: In link rewrite mode, original images serve as visual references; new content is generated with consistent style - **Semantic matching**: Images automatically match illustrator poses to section content (welcoming, analyzing, thinking, operating) - **Smart sectioning**: Auto-determines image placement by H2 sections + word density, skipping positions unsuitable for images - **Adjustable count**: 0-5 image limit, or fully auto-decided by the system --- ## API Key Acquisition & Security - This skill requires the environment variable: `REDFOX_API_KEY`. - `REDFOX_API_KEY` is issued by [RedFoxHub](https://redfox.hk/settings/api-keys?source=github) (`https://redfox.hk`) - Register at [RedFoxHub](https://redfox.hk?source=github) to obtain `REDFOX_API_KEY`. - Configure `REDFOX_API_KEY` on your device before using this skill. - Before providing your key, confirm its source, scope, validity period, and whether it can be reset or revoked. - Do not hard-code or expose keys in plain text in code, prompts, logs, or output files. --- ## Usage Guide Simply describe your needs in natural language — no commands to memorize. ### Quick Reference | Intent | Example phrase | Result | |--------|---------------|--------| | Link rewrite | "Rewrite this article in my product's voice, keep the style" | Learn the original style and structure, generate a new article with matching images | | Skill promo | "Write a promotional article for this Skill" | Auto-generate a capability-aligned promotional long-form article with images | | Add images | "Add images to this article" | Smart analysis of article structure, auto-determine image placement and generate | | Competitive analysis | "Analyze this article's approach, write one from a different angle" | Learn the structure and style of great content, produce differentiated output | ### Output After generation, you receive: 1. **Complete article**: Saved as `article.md` with title, body text, and images (embedded in corresponding sections) 2. **Quality report**: `validation_report.json` with 9 quality check results 3. **Full text in chat**: Article displayed directly in the conversation 4. **Feedback node**: Adjust any section or image by specifying the direction --- ## Use Cases | Scenario | Role | Example question | Benefit | |----------|------|-----------------|----------| | Style-matched rewrite | Content operator | "Rewrite this article, keep the style but switch to my product" | Quickly produce high-quality articles with consistent style, skip the blank-page phase | | Skill promotion | Skill developer | "Write a promotional article for this Skill" | Auto-generate capability-aligned promotional content — words match features | | Article illustration | Blogger / editor | "Add images to this article" | Smart image placement without manual planning | | Competitive analysis | Brand operator | "Analyze this article's approach, write one from a different angle" | Learn great content structure and style, produce differentiated output | --- -
README.md 5.2 KB
# 图文运营创作器 / visual-ops-writer --- ## 简介 从选题、撰写、配图到质检,覆盖运营文章的完整创作链路。看到好文章能**仿写**,给个 Skill 能生成**推广长文**,写完正文能**自动配图**——图文一体交付,拿来就能用。 **核心价值** - **三种场景,一个工具**:链接仿写、产品推广、正文配图——内容运营最高频的三个创作场景,无需来回切换工具。 - **图文一体交付**:文章与配图同步生成,保存为完整可用的成品文件,不是半成品。 - **质量内置**:每篇文章自动经过 9 项质量检查,输出质检报告,交付即可用。 **适用对象** - 📝 **内容运营** — 快速产出对标风格文章与产品推广文,从「写到配」一条龙搞定。 - 🛍️ **品牌 / 技能开发者** — 自动生成对齐产品能力的推广长文,内容与功能不脱节。 - 🏢 **博主 / 编辑** — 为已有正文智能配图,省去手动规划配图位置的麻烦。 --- ## 功能特性 ### 核心功能 - **链接仿写**:提供任意文章链接,自动抓取原文内容与图片,学习其风格与结构,仿写出角度不同但质量相当的新文章。配图风格与原图保持一致,适合对标分析与竞品内容学习。 - **Skill 推广文**:提供 Skill 文件路径或名称,自动解析产品能力并抓取目标站点最新信息,生成与产品实际能力对齐的推广长文。配图采用红狐讲解员风格,视觉与内容双重匹配。 - **正文补图**:提供完整正文,自动按章节语义与字数密度分析文章结构,智能决定配图位置与数量,批量生成风格统一的配图。适合写完正文不想手动找图的场景。 - **自动质量检查**:每篇文章生成后自动执行 9 项质量检查(标题长度、章节数、表格数、字数、FAQ 数量、禁忌词、段落行数、信息密度等),输出 JSON 格式质检报告。 ### 写作能力 - **文章类型自动匹配**:工具推介型、方法论型、危机应对型三种模板,根据选题自动选择最合适的写作结构 - **读者画像适配**:支持个体创作者和团队/企业两种读者定位,自动调整表达侧重 - **语调可选**:直白坦率 / 专业严谨 / 轻松对话,三种语调自由切换 - **字数可控**:支持自定义字数区间(默认 2000-3000 字),也可按原文篇幅自适应 ### 配图能力 - **风格迁移**:链接仿写模式下,原图作为视觉参照,新内容自动生成,风格一致 - **语义匹配**:配图按章节内容自动匹配讲解员姿态(欢迎、分析、思考、操作) - **智能断章**:按 H2 章节切分 + 字数密度自动决策生图点,跳过不适合配图的位置 - **数量可调**:0-5 张配图上限可调,也可完全交由系统自动决策 --- ## 密钥获取与安全说明 - 本技能需要使用环境变量:`REDFOX_API_KEY`。 - `REDFOX_API_KEY` 由 [红狐 hub](https://redfox.hk/settings/api-keys?source=github) (`https://redfox.hk`) - 请前往 [红狐 hub](https://redfox.hk?source=github) 注册账号,获取 `REDFOX_API_KEY`。 - 配置设备环境变量 `REDFOX_API_KEY` 后使用本技能。 - 在提供密钥前,请先确认密钥来源、可用范围、有效期及是否支持重置/撤销。 - 禁止在代码、提示词、日志或输出文件中硬编码/明文暴露密钥。 --- ## 使用指南 直接用自然语言描述需求,无需记忆命令。 ### 常用说法速查 | 意图 | 示例话术 | 效果 | |------|---------|------| | 链接仿写 | 「帮我仿写这篇文章,保持风格但换成我的产品」 | 学习原文风格与结构,生成配图一致的全新文章 | | Skill 推广 | 「帮我给这个 Skill 写一篇推广文」 | 自动生成对齐产品能力的推广长文与配图 | | 正文补图 | 「帮我给这篇正文加上配图」 | 智能分析文章结构,自动决定配图位置并生成 | | 竞品分析 | 「分析这篇文章的写法,帮我写一篇不同角度的」 | 学习优秀内容的结构与风格,产出差异化内容 | ### 输出示例 完成生成后,你将收到: 1. **完整文章**:保存为 `article.md`,含标题、正文、配图(嵌入对应章节) 2. **质检报告**:`validation_report.json`,含 9 项文章质量检查结果 3. **对话内全文**:直接在对话中输出文章全文 4. **反馈节点**:如需调整某章节或配图,可告知具体修改方向 --- ## 使用场景 | 场景 | 角色 | 示例问法 | 收益 | |------|------|---------|------| | 对标仿写 | 内容运营 | 「帮我仿写这篇文章,保持风格但换成我的产品」 | 快速产出风格一致的高质量文章,省去从零构思 | | Skill 推广 | 技能开发者 | 「帮我给这个 Skill 写一篇推广文」 | 自动生成对齐产品能力的推广文章,内容与功能不脱节 | | 文章配图 | 博主/编辑 | 「帮我给这篇正文加上配图」 | 智能选择配图位置与数量,无需手动规划 | | 竞品分析 | 品牌运营 | 「分析这篇文章的写法,帮我写一篇不同角度的」 | 学习优秀内容的结构与风格,产出差异化内容 | --- -
SKILL.md 9.4 KB
--- name: visual-ops-writer description: 图文运营创作器。从选题、撰写、配图到质检,一条龙完成运营文章生产。看到好文章能仿写,给个 Skill 能生成推广长文,写完正文能自动配图。内置写作质量管控与自动配图能力,图文一体交付。触发词:"生成运营文章"、"链接仿写"、"对标分析"、"Skill 推广"、"为正文配图"。 --- # 图文运营创作器 从选题、撰写、配图到质检,覆盖运营文章的完整创作链路。你只需要提供素材——一篇文章链接、一个 Skill、或者一段写好的正文,剩下的交给它。 > 核心执行流程详见 `references/core_workflow.md` --- ## 简介 图文运营创作器解决的是内容团队最常见的效率瓶颈:**一篇文章从写到配,往往需要多人协作、反复打磨**。 通过这个工具,你可以: - 看到一篇好文章,直接仿写为自己的版本,风格一致但内容全新 - 给一个 Skill 或产品,自动生成一篇专业的推广长文,配图到位 - 写完正文不想找图,丢进来自动判断哪里该配图、配什么风格 三种场景,一个工具全覆盖。 **适用对象** - 📝 **内容运营** — 快速产出对标风格文章与产品推广文,从写到配一条龙搞定 - 🛍️ **品牌 / 技能开发者** — 自动生成对齐产品能力的推广长文,内容与功能不脱节 - 🏢 **博主 / 编辑** — 为已有正文智能配图,省去手动规划配图位置的麻烦 --- ## 功能特性 ### 🎯 核心功能 - **🔗 链接仿写**:提供任意文章链接,自动抓取原文内容与图片,学习其风格与结构,仿写全新文章。配图风格与原图保持一致,适合对标分析与竞品内容学习。 - **📣 Skill 推广文**:提供 Skill 文件路径或名称,自动解析产品能力并抓取目标站点最新信息,生成与产品实际能力对齐的推广长文。配图采用红狐讲解员风格,视觉与内容双重匹配。 - **🖼️ 正文补图**:提供完整正文,自动按章节语义与字数密度分析文章结构,智能决定配图位置与数量,批量生成风格统一的配图。适合写完正文不想手动找图的场景。 - **✅ 自动质量检查**:每篇文章生成后自动执行 9 项质量检查(标题长度、章节数、表格数、字数、FAQ 数量、禁忌词、段落行数、信息密度等),输出 JSON 格式质检报告。 ### 写作质量管控 不是随便写一篇就交差。文章生成过程内置完整的质量管控体系: - **文章类型自动匹配**:根据选题自动识别为工具推介型、方法论型或危机应对型,每种类型有独立的写作侧重与结构建议 - **读者画像适配**:支持个体创作者和团队/企业两种读者定位,自动调整表达侧重(个人可执行方法 vs 工具系统协同) - **语调可选**:直白坦率 / 专业严谨 / 轻松对话,默认直白坦率 - **成文字数可控**:支持自定义字数区间(默认 2000-3000 字),链接仿写按原文篇幅自适应 ### 自动配图能力 文章配图不是简单的"插张图",而是内容的一部分: - **风格迁移**:链接仿写模式下,原图作为风格参照,新内容自动生成,视觉风格保持一致 - **语义匹配**:Skill 推广模式下,配图按章节语义自动匹配讲解员姿态(欢迎、分析、思考、操作等) - **智能断章**:正文补图模式下,按 H2 章节切分 + 字数密度自动决策生图点,跳过 FAQ / 结语 / 引言等不适合配图的位置 - **数量可控**:配图数量上限 0-5 张可调,也可由系统自动决策 --- ## 一键安装 ### 前置条件 - Python 3.8+(用于运行配图生成与质量检查脚本) - `REDFOX_API_KEY`(用于图片生成,未配置时仍可生成纯文本文章) ### 安装步骤 1. 将本 Skill 目录添加到你的 Agent 技能列表 2. 获取 `REDFOX_API_KEY`:前往 [红狐 hub](https://redfox.hk?source=github) 注册账号,在 [API Keys 页面](https://redfox.hk/settings/api-keys?source=github) 创建密钥 3. 配置环境变量: ```bash # macOS / Linux export REDFOX_API_KEY="your_key_here" # Windows PowerShell $env:REDFOX_API_KEY="your_key_here" ``` 4. 验证安装:告诉 Agent「帮我给这个 Skill 写一篇推广文」,确认技能正常响应 ### 环境变量说明 | 变量名 | 必填 | 说明 | |--------|------|------| | `REDFOX_API_KEY` | 条件必填 | 图片生成服务密钥;未配置时文章正常生成,图片降级为占位符或跳过 | --- ## 使用指南 ### 基础使用 #### 1. 链接仿写 告诉 Agent 你想仿写的文章链接: > 用户:帮我仿写这篇文章,保持风格但换成我的产品 > https://example.com/some-article > > Agent:已抓取原文,正在学习风格与结构…文章已生成,含 3 张配图,保存为 article.md。 #### 2. Skill 推广文 提供 Skill 文件或名称: > 用户:帮我给 xhs-ranking 这个 Skill 写一篇推广文 > > Agent:已解析 Skill 能力并抓取目标站点信息,推广文章已生成,含红狐风格配图 3 张。 #### 3. 正文补图 粘贴正文或提供文件路径: > 用户:帮我给这篇正文加上配图 > (粘贴正文内容) > > Agent:正文共 1800 字,已识别 4 个配图位置,配图生成中…完成,保存为 article.md。 ### 高级使用 - 指定语调:「用专业严谨的语调写」 - 指定读者:「目标读者是团队企业」 - 指定配图数量:「最多配 2 张图」 - 关闭配图:「不需要配图」或「style 设为 none」 - 指定字数:「控制在 1500 字左右」 ### 常用说法速查 | 意图 | 示例话术 | 效果 | |------|---------|------| | 链接仿写 | 「帮我仿写这篇文章,保持风格但换成我的产品」 | 学习原文风格与结构,生成配图一致的全新文章 | | Skill 推广 | 「帮我给这个 Skill 写一篇推广文」 | 自动生成对齐产品能力的推广长文与配图 | | 正文补图 | 「帮我给这篇正文加上配图」 | 智能分析文章结构,自动决定配图位置并生成 | | 竞品分析 | 「分析这篇文章的写法,帮我写一篇不同角度的」 | 学习优秀内容的结构与风格,产出差异化内容 | | 控制语调 | 「用轻松对话的语气写」 | 按指定语调生成文章 | | 关闭配图 | 「不需要配图」 | 仅输出纯文本文章 | --- ## 使用场景 | 场景 | 角色 | 示例问法 | 收益 | |------|------|---------|------| | 对标仿写 | 内容运营 | 「帮我仿写这篇文章,保持风格但换成我的产品」 | 快速产出风格一致的高质量文章,省去从零开始的构思 | | Skill 推广 | 技能开发者 | 「帮我给这个 Skill 写一篇推广文」 | 自动生成对齐产品能力的推广文章,内容与功能不脱节 | | 文章配图 | 博主/编辑 | 「帮我给这篇正文加上配图」 | 智能选择配图位置与数量,无需手动规划 | | 竞品分析 | 品牌运营 | 「分析这篇文章的写法,帮我写一篇不同角度的」 | 学习优秀内容的结构与风格,产出差异化内容 | --- ## 输出说明 完成生成后,你将收到: 1. **完整文章**:保存为 `article.md`,含标题、正文、FAQ(如有)、配图(以 `` 嵌入对应章节) 2. **质检报告**:`validation_report.json`,含 9 项文章质量检查结果 3. **对话内全文**:直接在对话中输出文章全文 4. **反馈节点**:如需调整某章节或配图,可告知具体修改方向 --- ## 常见问答 **Q1: 没有 API Key 能用吗?** A: 可以。没有 `REDFOX_API_KEY` 时,文章正文正常生成,图片部分降级处理:链接仿写用占位符替代,Skill 推广和正文补图跳过配图,仅输出纯文本。 **Q2: 生成的文章可以手动调整吗?** A: 可以。生成后直接告诉 Agent 你想调整的方向(如「第三段改短一点」「换一张配图」),Agent 会基于现有文章定向修改。 **Q3: 配图风格可以自定义吗?** A: 支持三种风格:`redfox`(红狐讲解员)、`reference`(跟随原图风格)、`none`(无风格修饰)。告诉 Agent 你的偏好即可。 --- ## 项目架构 ``` visual-ops-writer/ ├── SKILL.md # 技能入口文件 ├── references/ │ ├── core_workflow.md # 核心执行流程(Agent 执行链) │ ├── writing-framework.md # 写作框架参考 │ ├── article-type-templates.md # 文章类型模板(工具推介/方法论/危机应对) │ ├── persona-matrix.md # 语气×读者×视觉 IP 配置矩阵 │ ├── image-decision-rules.md # 配图决策规则 │ ├── link-rewrite-guide.md # 链接仿写指南 │ ├── prompt-templates.md # 提示词模板 │ └── error-handling.md # 错误处理 ├── scripts/ │ ├── fetch_article.py # 文章抓取与解析 │ ├── validate_article.py # 机器化质量检查 │ ├── generate_image.py # 图片生成(支持文生图与风格迁移) │ └── decide_images.py # 配图位置与数量决策 └── assets/ ├── 02.jpg # 素材图片 └── 03.jpg # 素材图片 ```
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.