ai-agent-book 精选快照(<2MB 代码与文档,来自 github.com/bojieli/ai-agent-book)
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
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
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Strip in-figure titles from checked-in SVGs and crop top whitespace.
|
||||
|
||||
按学术规范:图本身不包含标题(标题写在正文)。本脚本保守地处理
|
||||
`images/` 中已签入的 SVG 文件。
|
||||
|
||||
判定规则(保守):
|
||||
- 第一处出现的 <text> 元素,若同时满足:font-size>=20 且 y<60 且 text-anchor=middle,
|
||||
则视为图标题——删除该元素,并将 viewBox 上沿和 height 各下移/缩小 CROP_PX。
|
||||
- 否则该文件不做任何修改。
|
||||
|
||||
用法:python3 strip_titles.py [--dry-run]
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
IMAGES_DIR = os.path.join(os.path.dirname(__file__), 'images')
|
||||
CROP_PX = 40
|
||||
|
||||
TEXT_RE = re.compile(
|
||||
r'<text\b([^>]*?)>(.*?)</text>',
|
||||
flags=re.DOTALL,
|
||||
)
|
||||
ATTR_RE = re.compile(r'(\w[\w-]*)\s*=\s*"([^"]*)"')
|
||||
SVG_TAG_RE = re.compile(r'<svg\b([^>]*)>', flags=re.DOTALL)
|
||||
VIEWBOX_RE = re.compile(r'viewBox="([^"]+)"')
|
||||
HEIGHT_ATTR_RE = re.compile(r'\bheight="([^"]+)"')
|
||||
|
||||
|
||||
def parse_attrs(attr_str):
|
||||
return {m.group(1): m.group(2) for m in ATTR_RE.finditer(attr_str)}
|
||||
|
||||
|
||||
def process_file(path, dry_run=False):
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# 找第一个 <text>
|
||||
m = TEXT_RE.search(content)
|
||||
if not m:
|
||||
return 'no-text'
|
||||
attrs = parse_attrs(m.group(1))
|
||||
fs = float(attrs.get('font-size', '0') or 0)
|
||||
y = float(attrs.get('y', '0') or 0)
|
||||
anchor = attrs.get('text-anchor', '')
|
||||
if not (fs >= 20 and y < 60 and anchor == 'middle'):
|
||||
return 'no-title'
|
||||
|
||||
# 删除该 <text> 元素
|
||||
new_content = content[: m.start()] + content[m.end():]
|
||||
|
||||
# 调整 viewBox 与 height
|
||||
svg_m = SVG_TAG_RE.search(new_content)
|
||||
if not svg_m:
|
||||
return 'no-svg-tag'
|
||||
svg_attrs_str = svg_m.group(1)
|
||||
|
||||
def replace_viewbox(vbm):
|
||||
parts = vbm.group(1).split()
|
||||
if len(parts) != 4:
|
||||
return vbm.group(0)
|
||||
x0, y0, w, h = parts
|
||||
try:
|
||||
new_y0 = float(y0) + CROP_PX
|
||||
new_h = float(h) - CROP_PX
|
||||
except ValueError:
|
||||
return vbm.group(0)
|
||||
def fmt(v):
|
||||
return str(int(v)) if v == int(v) else str(v)
|
||||
return f'viewBox="{fmt(float(x0))} {fmt(new_y0)} {fmt(float(w))} {fmt(new_h)}"'
|
||||
|
||||
new_svg_attrs = VIEWBOX_RE.sub(replace_viewbox, svg_attrs_str)
|
||||
|
||||
def replace_height(hm):
|
||||
try:
|
||||
new_h = float(hm.group(1)) - CROP_PX
|
||||
new_h_s = str(int(new_h)) if new_h == int(new_h) else str(new_h)
|
||||
return f'height="{new_h_s}"'
|
||||
except ValueError:
|
||||
return hm.group(0)
|
||||
|
||||
new_svg_attrs = HEIGHT_ATTR_RE.sub(replace_height, new_svg_attrs, count=1)
|
||||
new_content = new_content[:svg_m.start()] + f'<svg{new_svg_attrs}>' + new_content[svg_m.end():]
|
||||
|
||||
if not dry_run:
|
||||
with open(path, 'w', encoding='utf-8') as f:
|
||||
f.write(new_content)
|
||||
return 'stripped'
|
||||
|
||||
|
||||
def main():
|
||||
dry_run = '--dry-run' in sys.argv
|
||||
counts = {'stripped': 0, 'no-title': 0, 'no-text': 0, 'no-svg-tag': 0}
|
||||
examples = {k: [] for k in counts}
|
||||
for name in sorted(os.listdir(IMAGES_DIR)):
|
||||
if not name.endswith('.svg'):
|
||||
continue
|
||||
result = process_file(os.path.join(IMAGES_DIR, name), dry_run=dry_run)
|
||||
counts[result] = counts.get(result, 0) + 1
|
||||
if len(examples[result]) < 5:
|
||||
examples[result].append(name)
|
||||
mode = '[DRY-RUN]' if dry_run else '[APPLIED]'
|
||||
print(f'{mode} summary:')
|
||||
for k, v in counts.items():
|
||||
print(f' {k}: {v} e.g. {examples[k]}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user