diff --git a/AGENTS.md b/AGENTS.md index 5c06f95..e58a31a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,16 +1,24 @@ # 演示文稿协作约定 -- 本仓库用于“知识库与腾讯 ima 教育应用”培训;参考仓库为 `speech/Ai_education`。 -- 当前课程大纲:`docs/261103_1129-知识库与腾讯ima教育应用培训PPT大纲.md`,是课程结构、逐页主题与讲解内容的维护依据。 -- 后续新增、删除、调整顺序或修改 PPT/HTML 演示内容时,必须在同一次任务中同步更新大纲,不得只修改演示文稿。 -- 涉及课程内容时,同步检查培训目标、课程安排、页码、页数、时长、案例、练习及成果要求,确保与演示内容一致;仅视觉或排版调整无需虚增大纲版本。 -- 修订大纲时保留原文件名和 `created_at`,递增 `revision`,向 `revisions` 追加修改时间与内容,必要时更新 `summary`。 -- 开始修改前先读取当前大纲;任务完成前检查大纲与实际演示内容的一致性。尚未制作或验证的演示与产品操作应如实注明。 -- 保留工作区中不属于当前任务的改动。默认不提交、不推送。 +- 本仓库用于“知识库与腾讯 ima 教育应用”培训,参考项目为 `speech/Ai_education`。 +- 当前大纲为 `docs/261103_1129-知识库与腾讯ima教育应用培训PPT大纲.md`。后续修改PPT或网页课程内容,必须在同一次任务中同步维护大纲,检查目标、页序、页数、时长、案例、练习与成果要求。 +- 修订大纲保留文件名及 `created_at`,递增 `revision`,追加 `revisions`;仅视觉调整无需虚增大纲版本。 -## 当前制作源文件 +## 网页维护 -- 当前 PowerPoint 的内容与版式源码为 `scripts/build.mjs`,构建生成 `src/deck.json` 标题索引、预览和 `outputs/*.pptx`。 -- 不直接编辑生成结果。内容改动需同步源码、大纲与实际标题索引,重新生成并检查。 -- `materials/` 是培训模拟资料,必须保留模拟标识,不得描述为真实课标、学校制度或产品实测结果。 -- 当前使用的是未登录官方首页截图;登录后的操作仍需授课账号验证,不得将讲师备注中的演示计划表述为已验证。 +- 网页页面源码为 `src/slides/*.html`,每个文件对应一页,含正文、`data-title`、`data-chapter`、`data-note` 和页内样式。 +- 修改前读取 `src/deck.json`,按稳定页面 ID 定位文件,文件名不使用页码。增删及排序由该清单控制。 +- 页内样式选择器以 `:scope` 开头,公共媒体查询与动画放在 `src/styles/theme.css`。公共容器和播放器分别为 `src/shell.html`、`src/runtime/player.js`。 +- 页内脚本由构建器包裹,可使用 `root` 查询本页。不得查询其他页面或在全局保存页内状态。计时器由公共播放器统一维护。 +- 图片放在 `assets/`,页面通过 `src="asset:文件名.png"` 引用;不在页面源码粘贴 Base64。`materials/` 为练习资料,构建时自动内嵌。 +- 不手写页码、总页数、section ID、active 或可见状态。页脚用 `{{PAGE_NUMBER}}` 与 `{{PAGE_COUNT}}`。 +- `python3 scripts/build_web.py --check` 检查;`python3 scripts/build_web.py` 生成 `outputs/index.html` 与ZIP练习包。`outputs/` 为生成结果,禁止直接编辑。 +- `work/` 仅放验证记录或临时工作,不运行历史修补脚本。 + +## PPT与内容一致性 + +- 已提交PPT首版保存在 `outputs/知识库与腾讯ima教育应用培训-v1.pptx`,其旧制作源码为 `scripts/build.mjs`,标题索引为 `src/pptx-deck.json`。不得用PPT构建覆盖网页清单 `src/deck.json`。 +- 当前30页网页对应PPT首版,后续修改课程内容时同步维护网页、大纲及后续发布的PPT版本。历史PPT保留原样,未重新生成的旧版不能声称已同步。 +- 模拟教学与校务资料必须保留模拟标识,不得描述为真实课标、学校制度或产品实测结果。 +- 官方截图为未登录首页,登录后的导入、来源点击、笔记及共享操作仍需授课账号验证。 +- 保留不属于当前任务的改动。默认不提交、不推送。 diff --git a/README.md b/README.md index 95aad9e..ee6b42c 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ ## 课程文件 +- [离线网页演示](outputs/index.html) - [PowerPoint 演示文稿](outputs/知识库与腾讯ima教育应用培训-v1.pptx) - [课程大纲](docs/261103_1129-知识库与腾讯ima教育应用培训PPT大纲.md) - [现场练习单](materials/05_现场练习单.md) @@ -14,7 +15,9 @@ ## 维护方式 -课程内容与样式源码位于 `scripts/build.mjs`,页码与标题索引由构建生成至 `src/deck.json`。改动PPT内容时同步更新大纲及版本记录,具体见 [AGENTS.md](AGENTS.md)。生成文件放在 `outputs/`,不要直接修改后遗漏源码和大纲。 +网页页面源码位于 `src/slides/*.html`,每页一个文件;`src/deck.json` 维护稳定页面ID及播放顺序。修改后运行 `python3 scripts/build_web.py --check`,再运行 `python3 scripts/build_web.py`。双击 `outputs/index.html` 即可离线播放,发送这个单文件即可分享。 + +PPT首版源码位于 `scripts/build.mjs`,其标题索引生成至 `src/pptx-deck.json`。改动PPT内容时同步更新大纲及版本记录,具体见 [AGENTS.md](AGENTS.md)。生成文件放在 `outputs/`,不要直接修改后遗漏源码和大纲。 本机重建使用已安装的官方演示文稿运行库。使用该运行库 Node 执行 `scripts/build.mjs`,参数1为仓库绝对路径,参数2为新的输出文件名,例如 `知识库与腾讯ima教育应用培训-v2.pptx`。构建会生成逐页预览、检查文件并导出新版本,保留旧版本。运行库位置见源码开头,换电脑需要调整。 @@ -27,3 +30,15 @@ - 产品参考:https://ima.tencent.com/download 。参考课程:http://gitea.werich.top/speech/Ai_education 。 默认不提交、不推送。 + +## 网页播放 + +- 方向键或空格翻页,Home / End 到首尾页。 +- M:搜索目录,N:当前页讲师备注,F:全屏,R:阅读模式。 +- 手机可左右滑动,阅读模式适合在小屏上查看全文。截图可点击放大。 +- 第14、27页可复制提问模板;第26页有12分钟计时器,切页后继续;第28页可勾选完成清单。 +- “资料”提供五份Markdown材料及ZIP整包下载,全部内嵌在网页内。 +- 备注会显示在当前投影上,按需打开。刷新页面后练习计时和清单重置。 +- 生成文件无网络依赖,访问官网或使用ima需要联网。网页不调用AI、不上传资料。 + +此版仅生成本地网页,尚未发布到公网,也未自动提交或推送。 diff --git a/docs/261103_1129-知识库与腾讯ima教育应用培训PPT大纲.md b/docs/261103_1129-知识库与腾讯ima教育应用培训PPT大纲.md index f1af933..af1e4d2 100644 --- a/docs/261103_1129-知识库与腾讯ima教育应用培训PPT大纲.md +++ b/docs/261103_1129-知识库与腾讯ima教育应用培训PPT大纲.md @@ -1,7 +1,7 @@ --- created_at: 2026-11-03 11:29:02 +0800 type: 面向教育行业的知识库与腾讯 ima 应用培训 PPT 大纲 -revision: 第2次 +revision: 第3次 revisions: - time: 2026-11-03 11:29:02 +0800 version: 第1次 @@ -11,6 +11,10 @@ revisions: version: 第2次 author: GPT-6 content: 完成30页可编辑PPT及逐页讲师备注,同步实际页标题与演示内容,加入五份模拟练习材料,明确未登录官方截图与待验证操作。 + - time: 2026-11-03 11:52:52 +0800 + version: 第3次 + author: GPT-6 + content: 新增与PPT首版对应的30页离线网页,加入目录搜索、讲师备注、阅读模式、截图放大、提问复制、练习计时与资料下载,更新源码维护路径。 summary: 面向中小学教师、教研组长及学校管理人员,设计120分钟知识库与腾讯ima应用培训,通过知识库基础、产品操作、单元备课、练习设计、教研共享、校务查询和现场练习,掌握资料入库、提问、查证与成果沉淀。 --- @@ -303,7 +307,18 @@ summary: 面向中小学教师、教研组长及学校管理人员,设计120 ### 五、演示文稿与材料 - 演示文稿:`outputs/知识库与腾讯ima教育应用培训-v1.pptx`,30 页,16:9,可编辑文字、图形和六张原生表格,含逐页讲师备注。 -- 制作源文件:`scripts/build.mjs`;实际页码与标题索引:`src/deck.json`。 +- PPT 首版制作源文件:`scripts/build.mjs`;其标题索引独立保存在 `src/pptx-deck.json`。网页版页面源码位于 `src/slides/*.html`,稳定页面 ID 与顺序位于 `src/deck.json`。 - 练习资料:`materials/01_主题说明.md`、`02_活动建议.md`、`03_课堂反思.md`、`04_校务办理模拟指南.md`、`05_现场练习单.md`。 - 所有教学与校务案例均为模拟示例,不是 ima 实测输出,也不替代课程标准、教材或学校正式制度。 - 练习与交流 30 分钟细分为:12 分钟动手、8 分钟分享、10 分钟使用习惯与答疑。 + +### 六、网页演示版 + +- 输出文件:`outputs/index.html`。共30页,与已提交PPT首版的课程内容一致,采用原深绿、薄荷绿和浅色版式。 +- 单页源码:`src/slides/*.html`,包含正文、标题、章节、讲师备注及页内样式;顺序由 `src/deck.json` 管理。 +- 公共文件:`src/shell.html`、`src/styles/theme.css`、`src/runtime/player.js`,使用 `python3 scripts/build_web.py` 构建;`--check` 仅检查。 +- 网页功能:键盘与触屏翻页、目录搜索与跳转、讲师备注、全屏、长文阅读模式、截图放大、第14/27页提示词复制、第26页12分钟计时器、第28页完成清单、五份材料单独下载与ZIP整包下载。 +- 网页将图片和练习材料内嵌,可离线播放。官方链接与ima内操作需要联网,网页不会调用AI或上传资料。 +- 练习计时按实际经过时间计算,切页后继续,支持暂停和重置;刷新网页后重置。完成清单仅保存在当前页面会话中。 +- 已在浏览器检查30页文本与表格溢出,核对正文与PPT一致,验证目录搜索、备注、阅读模式、放大、复制、资料下载、跨页计时及清单更新。 +- PPT 首版文件保留不变;以后修改课程内容时,需同步维护网页、大纲及后续发布的PPT版本。 diff --git a/outputs/index.html b/outputs/index.html new file mode 100644 index 0000000..9e0006a --- /dev/null +++ b/outputs/index.html @@ -0,0 +1,557 @@ + +让教学资料用起来 + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

课程目录

30 页,120 分钟

+

讲师备注 · 打开时会显示在投影上

+

现场练习

资料与播放说明

先用材料 01—03 建立“光与影”专题库。材料 04 用于独立的校务查询案例,材料 05 用于记录练习成果。

官方来源与使用说明

官方截图:腾讯 ima 未登录 Web 首页,2026-11-03。产品介绍:腾讯 ima 官网。

本网页可离线播放,不调用 AI,也不上传资料。产品操作需要联网。教学与校务案例均为模拟示例,不能替代课程标准、教材或学校正式制度。登录后的导入、来源点击、笔记和共享操作需在授课前验证。

← → / 空格翻页,Home / End 到首尾页,M 目录,N 备注,F 全屏,R 阅读模式,Esc 关闭弹窗。手机可左右滑动翻页,或切换阅读模式。截图可点击放大。

+

腾讯 ima 官方首页

腾讯 ima 官方未登录首页放大图
+

手动复制提问模板

当前浏览器未允许自动复制,可选择上面的文字手动复制。

+

完成后逐项勾选

你的成果准备好了吗?

已完成 0 / 5 项

+
+ diff --git a/outputs/培训练习材料.zip b/outputs/培训练习材料.zip new file mode 100644 index 0000000..a177f79 Binary files /dev/null and b/outputs/培训练习材料.zip differ diff --git a/outputs/网页预览.png b/outputs/网页预览.png new file mode 100644 index 0000000..5079174 Binary files /dev/null and b/outputs/网页预览.png differ diff --git a/scripts/build.mjs b/scripts/build.mjs index 7e8f64b..ed99679 100644 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -108,7 +108,7 @@ text(s,'从一个单元开始,建立自己的教学资料库',68,518,670,78,25 await fs.mkdir(path.join(BASE,'build','previews'),{recursive:true}); await fs.mkdir(path.join(BASE,'outputs'),{recursive:true}); -await fs.writeFile(path.join(BASE,'src','deck.json'),JSON.stringify(manifest,null,2)); +await fs.writeFile(path.join(BASE,'src','pptx-deck.json'),JSON.stringify(manifest,null,2)); const draft=path.join(BASE,'build','candidate.pptx'); await (await PresentationFile.exportPptx(p)).save(draft); console.log('Draft exported: '+draft); diff --git a/scripts/build_web.py b/scripts/build_web.py new file mode 100644 index 0000000..bf3ae33 --- /dev/null +++ b/scripts/build_web.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python3 +"""Build the offline deck using only Python's standard library.""" +import argparse +import io +import zipfile +import base64 +import html +import json +import mimetypes +import re +from collections import Counter +from html.parser import HTMLParser +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +OUTPUT = ROOT / "outputs/index.html" + + +class Markup(HTMLParser): + def __init__(self, text): + super().__init__(convert_charrefs=True) + self.ids = [] + self.sections = [] + self.resources = [] + self.feed(text) + + def handle_starttag(self, tag, attrs): + attrs = dict(attrs) + if "id" in attrs: + self.ids.append(attrs["id"]) + if tag == "section": + self.sections.append(attrs) + if tag in ("img", "script", "iframe", "video", "audio", "source"): + if attrs.get("src"): + self.resources.append(attrs["src"]) + + +def require(condition, message): + if not condition: + raise ValueError(message) + + +def extract(text, tag): + blocks = re.findall(rf"<{tag}>(.*?)", text, re.S) + return re.sub(rf"<{tag}>.*?", "", text, flags=re.S), blocks + + +def scoped_css(css, page_id): + """Require every local selector to begin at this page's root.""" + # Page styles deliberately use ordinary rules; global animations belong in theme.css. + clean = re.sub(r"/\*.*?\*/", "", css, flags=re.S) + rules = re.findall(r"([^{}]+)\{([^{}]*)\}", clean) + require(not re.sub(r"[^{}]+\{[^{}]*\}", "", clean).strip(), + f"{page_id}: 页内样式不支持嵌套规则,请将公共媒体规则放入 theme.css") + for selectors, _ in rules: + require(all(re.match(r"^:scope(?=[\s.#:[>+~]|$)", selector.strip()) + for selector in selectors.split(",")), + f"{page_id}: 每个专用样式选择器必须以 :scope 开头") + require(not re.search(r":scope\s*[+~]", selectors), + f"{page_id}: 页内样式不能选择其他页面") + return css.replace(":scope", f':where([data-page="{page_id}"])') + + +def build(): + ids = json.loads((ROOT / "src/deck.json").read_text()) + require(isinstance(ids, list) and ids, "deck.json 必须是非空页面 ID 列表") + require(all(isinstance(x, str) and re.fullmatch(r"[a-z][a-z0-9-]*", x) for x in ids), + "页面 ID 只能包含小写字母、数字和连字符") + require(len(ids) == len(set(ids)), "deck.json 中存在重复页面 ID") + pages, styles, scripts = [], [], [] + styles.append((ROOT / "src/styles/theme.css").read_text()) + materials = [{"name": p.name, "content": p.read_text()} for p in sorted((ROOT / "materials").glob("*.md"))] + require(len(materials) == 5, "练习资料必须包含5份Markdown文件") + buffer = io.BytesIO() + with zipfile.ZipFile(buffer, "w", zipfile.ZIP_DEFLATED) as archive: + for item in materials: + info = zipfile.ZipInfo(item["name"], date_time=(1980, 1, 1, 0, 0, 0)) + info.compress_type = zipfile.ZIP_DEFLATED + archive.writestr(info, item["content"]) + material = buffer.getvalue() + scripts.append("const MATERIALS=" + json.dumps(materials, ensure_ascii=False) + ";") + scripts.append("const MATERIAL_ZIP=" + json.dumps(base64.b64encode(material).decode()) + ";") + scripts.append((ROOT / "src/runtime/player.js").read_text()) + image_types = {} + + def inline_asset(match): + name = match[1] + path = (ROOT / "assets" / name).resolve() + require((ROOT / "assets").resolve() in path.parents, f"图片路径越界: {name}") + require(path.is_file(), f"图片不存在: {name}") + mime = ("image/" + image_types[name] if name in image_types + else mimetypes.guess_type(name)[0]) + require(mime and mime.startswith("image/"), f"无法识别图片类型: {name}") + return "data:" + mime + ";base64," + base64.b64encode(path.read_bytes()).decode() + + for number, page_id in enumerate(ids, 1): + path = ROOT / "src/slides" / (page_id + ".html") + text = path.read_text() + text, page_styles = extract(text, "style") + text, page_scripts = extract(text, "script") + parsed = Markup(text) + require(len(parsed.sections) == 1, f"{path.name}: 必须恰好有一个 section") + attrs = parsed.sections[0] + require("slide" in attrs.get("class", "").split(), f"{path.name}: 缺少 slide 类") + require(all(attrs.get(key) for key in ("data-title", "data-chapter", "data-note")), + f"{path.name}: 缺少标题、章节或讲稿备注") + require(not any(key in attrs for key in ("id", "data-page", "aria-label", "aria-hidden")), + f"{path.name}: 页码、标识和可见状态由构建生成,请移除手写属性") + require("active" not in attrs["class"].split(), f"{path.name}: 不要手写 active") + title = html.escape(attrs["data-title"], quote=True) + text = text.replace("