Files
liqiang b119135836
Build latest book artifacts / build (push) Canceled after 0s
dependency resolution / resolve (3.11) (push) Canceled after 0s
dependency resolution / resolve (3.13) (push) Canceled after 0s
deploy-pages / build (push) Canceled after 0s
deploy-pages / deploy (push) Canceled after 0s
i18n consistency check / check (push) Canceled after 0s
provider adoption tests / test (chapter2/context-compression) (push) Canceled after 0s
provider adoption tests / test (chapter2/prompt-injection) (push) Canceled after 0s
provider adoption tests / test (chapter2/system-hint) (push) Canceled after 0s
provider adoption tests / test (chapter3/log-sanitization) (push) Canceled after 0s
web-search-agent tests / test (push) Canceled after 0s
web-search-agent tests / agentbook (push) Canceled after 0s
ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
2026-08-20 13:12:50 +00:00

152 lines
4.7 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
pptx Skill 捆绑的可执行脚本:使用 python-pptx 从结构化大纲生成真实的 .pptx 文件。
这是 Agent Skills「渐进式披露」中第三层(细则 / 捆绑工具)的一部分:
Agent 在读取 SKILL.md 后,得知需要通过 run_skill_script 工具调用本脚本,
并按约定的 JSON schema 传入幻灯片大纲。本脚本负责把大纲落地为 PowerPoint。
payload JSON schema(由 SKILL.md 向 Agent 说明):
{
"title": "演示文稿主标题(字符串)",
"subtitle": "副标题,通常是作者/来源(字符串,可选)",
"slides": [
{"title": "页标题", "bullets": ["要点1", "要点2", ...]},
...
]
}
既可作为库被 importbuild_presentation),也可作为 CLI 直接运行:
python generate_pptx.py outline.json output/deck.pptx
"""
import json
import sys
from pathlib import Path
from pptx import Presentation
from pptx.util import Pt, Inches
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN
# 一套简单的品牌配色,作为设计起点(对应 SKILL.md 提到的「模板 / 设计起点」)
ACCENT = RGBColor(0x1F, 0x4E, 0x79) # 深蓝
DARK = RGBColor(0x22, 0x22, 0x22) # 近黑正文
LIGHT = RGBColor(0xF2, 0xF5, 0xFA) # 浅色背景条
def _set_slide_bg(slide, rgb):
"""给整页填充一个纯色背景。"""
fill = slide.background.fill
fill.solid()
fill.fore_color.rgb = rgb
def _add_title_slide(prs, title, subtitle):
slide = prs.slides.add_slide(prs.slide_layouts[6]) # 6 = 纯空白版式
_set_slide_bg(slide, ACCENT)
# 主标题
box = slide.shapes.add_textbox(Inches(0.8), Inches(2.2), Inches(8.4), Inches(2.0))
tf = box.text_frame
tf.word_wrap = True
p = tf.paragraphs[0]
p.alignment = PP_ALIGN.CENTER
run = p.add_run()
run.text = title
run.font.size = Pt(40)
run.font.bold = True
run.font.color.rgb = RGBColor(0xFF, 0xFF, 0xFF)
# 副标题
if subtitle:
sbox = slide.shapes.add_textbox(Inches(0.8), Inches(4.3), Inches(8.4), Inches(1.0))
stf = sbox.text_frame
stf.word_wrap = True
sp = stf.paragraphs[0]
sp.alignment = PP_ALIGN.CENTER
srun = sp.add_run()
srun.text = subtitle
srun.font.size = Pt(20)
srun.font.color.rgb = RGBColor(0xD5, 0xDE, 0xEB)
def _add_content_slide(prs, title, bullets):
slide = prs.slides.add_slide(prs.slide_layouts[6])
_set_slide_bg(slide, RGBColor(0xFF, 0xFF, 0xFF))
# 顶部标题色条
bar = slide.shapes.add_shape(
1, # MSO_SHAPE.RECTANGLE
Inches(0), Inches(0), Inches(10), Inches(1.1),
)
bar.fill.solid()
bar.fill.fore_color.rgb = ACCENT
bar.line.fill.background()
tf = bar.text_frame
tf.word_wrap = True
tf.margin_left = Inches(0.5)
p = tf.paragraphs[0]
p.alignment = PP_ALIGN.LEFT
run = p.add_run()
run.text = title
run.font.size = Pt(26)
run.font.bold = True
run.font.color.rgb = RGBColor(0xFF, 0xFF, 0xFF)
# 正文要点
body = slide.shapes.add_textbox(Inches(0.7), Inches(1.5), Inches(8.6), Inches(5.2))
btf = body.text_frame
btf.word_wrap = True
for i, bullet in enumerate(bullets):
para = btf.paragraphs[0] if i == 0 else btf.add_paragraph()
para.space_after = Pt(10)
r = para.add_run()
r.text = "• " + str(bullet)
r.font.size = Pt(18)
r.font.color.rgb = DARK
def build_presentation(payload: dict, out_path: str) -> dict:
"""从大纲 payload 构建 pptx,返回 {path, num_slides, titles} 供校验。"""
title = payload.get("title", "Untitled Presentation")
subtitle = payload.get("subtitle", "")
slides = payload.get("slides", [])
if not slides:
raise ValueError("payload.slides 为空,至少需要一页内容")
prs = Presentation()
prs.slide_width = Inches(10)
prs.slide_height = Inches(7.5)
titles = []
# 标题页
_add_title_slide(prs, title, subtitle)
titles.append(title)
# 内容页
for s in slides:
s_title = s.get("title", "")
bullets = s.get("bullets")
if bullets is None:
bullets = []
_add_content_slide(prs, s_title, bullets)
titles.append(s_title)
out = Path(out_path)
out.parent.mkdir(parents=True, exist_ok=True)
prs.save(str(out))
return {"path": str(out), "num_slides": len(list(prs.slides)), "titles": titles}
if __name__ == "__main__":
if len(sys.argv) != 3:
print("用法: python generate_pptx.py <outline.json> <output.pptx>", file=sys.stderr)
sys.exit(1)
payload = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
result = build_presentation(payload, sys.argv[2])
print(json.dumps(result, ensure_ascii=False, indent=2))