- 教育
- 后端
- 前端
【免费下载链接】oppia
A free, online learning platform to make quality education accessible for all.
Oppia 是一个免费、开放的在线学习平台,其课程内容(Explorations)的交互体验高度依赖一套插件化的扩展体系。本文以仓库 extensions/README.md 为主线,系统讲解 Oppiaextensions目录中 actions、answer_summarizers、ckeditor_plugins、interactions、objects、rich_text_components、value_generators、visualizations 八大扩展模块的职责、源码结构与扩展开发方法。读完本文,你将掌握每个扩展模块的目录约定、Python/TypeScript 双端实现模式,以及如何为 Oppia 新增一个交互或富文本组件。
扩展系统总览:一份目录,八类插件
extensions/README.md 明确说明:该目录集中存放 Oppia 代码库中各类扩展与插件的前端与后端代码。它并非单一功能模块,而是围绕"如何让课程内容可交互、可编辑、可统计"这一目标组织的八类插件集合:
| 目录 | 职责 | 技术形态 |
|---|---|---|
| actions | 用户在 Oppia 网站上的各类行为(action)的领域类定义 | Python 后端配置 |
| answer_summarizers | 答案汇总器(answer summarizer)的模型定义 | Python 后端计算模型 |
| ckeditor_plugins | CKEditor RTE 中使用的插件 | JavaScript 前端插件 |
| interactions | Oppia 网站支持的交互(互动组件)的前后端代码 | Python + TypeScript |
| objects | 各类类型化编辑器(布尔编辑器、文件路径编辑器等)的前后端代码 | Python + TypeScript |
| rich_text_components | Oppia RTE 支持的富文本组件的前后端代码 | Python + TypeScript |
| value_generators | Oppia 中使用的值生成器代码 | Python + TypeScript |
| visualizations | Oppia 中使用的答案可视化代码 | Python + TypeScript |
除上述八类之外,目录中还包含三个基础设施文件:extensions/domain.py 定义了跨扩展共享的CustomizationArgSpec等值对象;extensions/extensions.module.ts 是扩展组件的前端 Angular 模块入口;extensions/domain_test.py 提供对应的后端测试。
双端并行的模块组织惯例
从目录结构看,除ckeditor_plugins外,每个扩展模块都遵循"后端配置 + 前端实现"的对称布局:后端用 Python 类声明元数据(名称、描述、自定义参数规格、规则描述等),前端用 TypeScript + Angular 组件渲染交互界面。以交互模块为例,BaseInteraction类(extensions/interactions/base.py)是所有交互的后端基类,注释中明确要求每个交互附带一张 178 × 146 像素的缩略图,供交互选择器(interaction selector)展示——这正是双端协作的一个缩影。
actions:学习者行为(Learner Action)的领域建模
actions模块负责定义"用户在 Oppia 上发生的行为"的领域类。当前仓库包含三个具体行为与一个基类:
- extensions/actions/AnswerSubmit:记录学习者提交答案的行为;
- extensions/actions/ExplorationQuit:记录学习者中途退出探索的行为;
- extensions/actions/ExplorationStart:记录学习者开始探索的行为。
BaseLearnerActionSpec 基类
所有行为类都继承自 extensions/actions/base.py 中的BaseLearnerActionSpec。该基类被明确标注为"不可由用户编辑,只应提供 getter 方法",其核心是一个_customization_arg_specs类属性列表,每个规格包含 name、description、schema、default_value 四个字段,并通过customization_arg_specs属性将其转换为domain.CustomizationArgSpec值对象。to_dict()方法负责输出仅含默认值的字典表示。
AnswerSubmit 的规格定义示例
以 AnswerSubmit/AnswerSubmit.py 为例,它声明了六个自定义参数规格:
| 参数名 | 说明 | 类型 | 默认值 |
|---|---|---|---|
state_name | 当前状态名 | unicode | '' |
dest_state_name | 目标状态名 | unicode | '' |
interaction_id | 交互 ID | unicode | '' |
submitted_answer | 提交的答案 | unicode | '' |
feedback | 对提交答案的反馈 | unicode | '' |
time_spent_state_in_msecs | 在该状态停留的毫秒数 | int | 0 |
这些规格模式与交互、富文本组件中的自定义参数规格完全一致,体现了 Oppia 扩展体系中"自定义参数规格(customization arg specs)"这一贯穿始终的统一抽象。
answer_summarizers:答案汇总计算模型
answer_summarizers模块(extensions/answer_summarizers/models.py)提供对已记录的状态答案(state answers)执行统计计算的模型。其模块文档给出了开发者接入方式的关键说明:要为某个交互<INTERACTION_NAME>指定计算,需编辑extensions.interactions.<INTERACTION_NAME>.answer_visualizations列表,每个可视化条目是一个包含id、options、calculation_id三个键的字典,例如:
answer_visualizations = [{ 'id': 'SortedTiles', 'options': { 'use_percentages': True, 'header': 'Pretty Tiles!', }, 'calculation_id': 'AnswerFrequencies', }]计算模型的分类体系
该模块定义了两类重要常量:
CLASSIFICATION_CATEGORIES:包含显式分类(EXPLICIT)、训练数据分类(TRAINING_DATA)、统计分类(STATISTICAL)与默认结果分类(DEFAULT_OUTCOME)四类;UNRESOLVED_ANSWER_CLASSIFICATION_CATEGORIES:仅包含统计分类与默认结果分类,用于界定"未解决答案"。
所有计算类继承自BaseCalculation,其id属性直接取类名,calculate_from_state_answers_dict()抽象方法需在子类中实现,且均在 MapReduce 批处理任务上下文中运行。仓库内置了六种计算实现:
AnswerFrequencies:统计每个答案的提交次数,返回{'answer': ..., 'frequency': ...}列表;Top5AnswerFrequencies/Top10AnswerFrequencies:分别只保留频次最高的前 5 / 前 10 个答案;FrequencyCommonlySubmittedElements:面向集合型答案(如SetOfUnicodeString),统计各独立元素的频次,要求答案必须可迭代,否则抛出异常;TopAnswersByCategorization:按分类类别分组统计答案频次;TopNUnresolvedAnswersByFrequency:过滤出未解决答案并按其频次降序排列,上限由 core/feconf.py 中的TOP_UNRESOLVED_ANSWERS_LIMIT = 20控制。
这些计算统一以 core/domain/calculation_registry.py 中的注册表按类名查取,并被可视化模块引用。
ckeditor_plugins:CKEditor 富文本编辑器的扩展插件
ckeditor_plugins模块存放 CKEditor RTE 使用的插件,目前仓库只含一个pre插件(extensions/ckeditor_plugins/pre/plugin.js),其功能是让编辑者在富文本中插入<pre>预格式化文本块。该插件通过标准的CKEDITOR.plugins.add('pre', {...})API 注册,包含一个styleCommand命令、工具栏按钮Pre(置于 insert 工具栏)以及按钮高亮状态同步逻辑,配套图标位于pre/icons/目录。
由此可以推断新增 RTE 插件的模式:在 extensions/ckeditor_plugins 下新建目录、按 CKEditor 插件规范编写plugin.js,并在编辑器配置中引用。
interactions:交互(互动组件)的前后端实现
interactions是扩展体系中最庞大的模块,当前包含 20 个交互:AlgebraicExpressionInput、CodeRepl、Continue、DragAndDropSortInput、EndExploration、FractionInput、GraphInput、ImageClickInput、InteractiveMap、ItemSelectionInput、MathEquationInput、MultipleChoiceInput、MusicNotesInput、NumberWithUnits、NumericExpressionInput、NumericInput、PencilCodeEditor、RatioExpressionInput、SetInput、TextInput。此外,模块根目录还有 interaction_specs.json、rule_templates.json、answer-defs.ts、rule-input-defs.ts、customization-args-defs.ts 等全局定义文件。
BaseInteraction:交互的"身份档案"
每个交互的后端 Python 类继承 extensions/interactions/base.py 中的BaseInteraction,并通过类属性声明一份完整的"身份档案",其中核心字段包括:
| 字段 | 含义 | 典型值 |
|---|---|---|
name/description | 人类可读的名称与描述 | 'Text Input' |
display_mode | 展示方式:inline(内嵌对话流)或supplemental(独立对象) | base.DISPLAY_MODE_INLINE |
is_terminal | 是否为终止型交互(结束探索) | EndExploration为True |
is_linear | 是否只有唯一答案 | Continue为True |
is_trainable | 是否支持机器学习分类 | TextInput为True |
answer_type | 接受的答案类型字符串 | 'NormalizedString' |
_customization_arg_specs | 自定义参数规格列表 | 见下文 |
_answer_visualization_specs | 答案可视化规格列表 | 见下文 |
can_have_solution | 是否支持解题提示(solution)功能 | TextInput为True |
show_generic_submit_button | 是否在导航区显示通用提交按钮 | TextInput为True |
display_mode的两种取值在 base.py 中定义为常量DISPLAY_MODE_INLINE与DISPLAY_MODE_SUPPLEMENTAL;后者模式下,若相邻两个状态使用相同交互 ID,交互实例会被复用。
自定义参数规格(customization args):交互的"可配置项"
交互的编辑器侧配置能力来自_customization_arg_specs。以 TextInput/TextInput.py 为例,它声明了三个自定义参数:
_customization_arg_specs = [ { 'name': 'placeholder', 'description': 'Placeholder text (optional)', 'schema': {'type': 'custom', 'obj_type': 'SubtitledUnicode'}, 'default_value': {'content_id': None, 'unicode_str': ''}, }, { 'name': 'rows', 'description': 'Height (in rows)', 'schema': { 'type': 'int', 'validators': [ {'id': 'is_at_least', 'min_value': 1}, {'id': 'is_at_most', 'max_value': 10}, ], }, 'default_value': 1, }, { 'name': 'catchMisspellings', 'description': 'Catch Misspellings (Detect if answer is misspelled ...)', 'schema': {'type': 'bool'}, 'default_value': False, }, ]注意其中rows参数通过 schema 的validators约束取值范围为 1~10,这是 Oppia 表单渲染引擎(core/schema_utils.py)在编辑器界面自动校验数据合法性的关键机制。文本注释还提示:旧版本中曾有一个columns参数,为兼容历史探索数据,不要再复用该参数名。
答案可视化规格与规则描述
交互的_answer_visualization_specs把"答案统计"与"展示方式"绑定在一起。TextInput 配了两个FrequencyTable可视化:第一个用Top10AnswerFrequencies计算展示 Top 10 答案,第二个用TopNUnresolvedAnswersByFrequency展示未解决答案。前端通过 interactions/base-interaction-validation.service.ts 校验这些规格,后端则在 base.py 的answer_visualizations属性中,借助 core/domain/visualization_registry.py 按 ID 实例化可视化类。
规则描述统一以 JSON 形式维护在 interaction_specs.json 中。以ImageClickInput为例,其规则为"IsInRegion": "is in the region {{x|UnicodeString}}"——{{参数名|对象类型}}占位符语法由get_rule_param_list()(base.py)解析,从中提取规则参数及其extensions.objects.models中的对象类型,从而支持通用的规则参数类型推断。
一个交互的前端组成
每个交互目录下同时包含 Angular 组件与配套测试。以 TextInput 为例,TextInput.ts 汇总 require 了以下前端依赖:
text-input-rules.service.ts:规则判定服务;text-input-validation.service.ts:自定义参数合法性校验服务;oppia-interactive-text-input.component.ts:学习者侧的交互组件;oppia-response-text-input.component.ts:学习者作答后的完整回答展示组件;oppia-short-response-text-input.component.ts:回答的短摘要展示组件。
对应的 HTML 模板与 spec 测试位于 extensions/interactions/TextInput/directives 目录。这五件套(交互组件、回答组件、短回答组件、规则服务、校验服务)是 Oppia 交互的前端标准结构。
objects:类型化编辑器与领域对象
objects模块承载 Oppia 中各类类型化对象的定义与对应编辑器组件。后端核心是 extensions/objects/models/objects.py,其中定义了UnicodeString、Html、Filepath、SanitizedUrl、MathExpressionContent、ListOfTabs、SvgFilename、Int、Boolean、SkillSelector等对象类,每个类都提供normalize()方法做数据规范化。这些对象类是交互答案类型、富文本组件参数、规则参数校验的共同基石——例如 rich_text_components/components.py 中维护了一张从对象类型名到对象类的映射表,正是复用这套模型。
objects模块还包括:
- extensions/objects/object_defaults.json:各对象的默认值定义;
- extensions/objects/object-components.module.ts:对象编辑器组件的 Angular 模块;
- extensions/objects/templates:各类编辑器(布尔编辑器、文件路径编辑器、坐标编辑器等)的组件模板。
rich_text_components:RTE 富文本组件
rich_text_components模块定义 Oppia 富文本编辑器(RTE)中可插入的组件,目前包含八个组件:Collapsible(折叠块)、Image(图片)、Link(链接)、Math(数学公式)、Skillreview(技能回顾)、Tabs(选项卡)、Video(视频)、Workedexample(例题讲解)。
BaseRteComponent 与参数校验
后端基类 extensions/rich_text_components/components.py 的BaseRteComponent通过类方法validate()校验组件参数:它从 assets/rich_text_components_definitions.ts(由feconf.RTE_EXTENSIONS_DEFINITIONS_PATH指定,见 core/feconf.py)读取各组件的customization_arg_specs,将每个参数的 schema 类型映射到 objects 对象类后调用normalize()校验,并严格核对参数集合,发现缺失或多余属性即抛出utils.ValidationError。
新增富文本组件的标准步骤
extensions/rich_text_components/README.md 给出了新增组件的三步流程:
- 在
extensions/rich_text_components/下新建目录并放置全部文件(后端 Python 类、前端组件与模板); - 在
assets/rich_text_components_definitions.ts中为该组件新增一条 JSON 记录(含名称、描述、customization_arg_specs 等); - 在
core/templates/components/ck-editor-helpers/rich-text-components.template.html中加入对应的脚本导入,使组件出现在 CKEditor 工具栏中。
同时,core/templates/components/ck-editor-helpers 目录下的其他文件(如复制工具栏组件)共同构成了 RTE 的编辑辅助设施。前端侧,extensions/rich_text_components/rte-output-display.component.ts 负责在探索播放时渲染这些组件,extensions/rich_text_components/richTextComponentsRequires.ts 汇总各组件的前端依赖。
value_generators:参数值生成器
value_generators模块提供"值生成器"——在探索运行时根据上下文参数动态生成自定义参数值的工具。后端模型位于 extensions/value_generators/models/generators.py,包含两个生成器,均继承 core/domain/value_generators_domain.py 的BaseValueGenerator:
Copier:通过copy.deepcopy(value)原样复制输入值;RandomSelector:通过utils.get_random_choice(list_of_values)从输入列表中随机选取一个值。
前端模板位于 extensions/value_generators/templates,包括Copier.component.html、RandomSelector.component.html及对应的 Angular 组件(copier.component.ts、random-selector.component.ts),供探索编辑器在配置参数时选择"复制"或"随机选取"两种取值策略;valueGeneratorsRequires.ts负责汇总前端依赖。
visualizations:学习者答案可视化
visualizations模块实现"对汇总后的学习者答案进行可视化展示",后端定义在 extensions/visualizations/models.py,前端为对应的 Angular 指令/组件。基类BaseVisualization接收三个关键参数:
calculation_id:关联的答案汇总计算(来自 answer_summarizers 模块);options_dict:可视化的选项配置;addressed_info_is_supported:是否支持"已回答"信息标记。
其validate()方法会依次校验计算 ID 在 core/domain/calculation_registry.py 中真实存在、选项名与_OPTIONS_SPECS声明完全一致(借助 core/schema_utils.py 做 schema 归一化)、以及addressed_info_is_supported为布尔值。
仓库内置四种可视化:
| 可视化 | 前端文件 | 说明 |
|---|---|---|
ClickHexbins | oppia-visualization-click-hexbins.directive.ts | 在图片上以六边形分组叠加展示点击分布,面向ImageClickInput这类点击型交互 |
FrequencyTable | oppia-visualization-frequency-table.directive.ts | 两列答案频次表格 |
EnumeratedFrequencyTable | oppia-visualization-enumerated-frequency-table.directive.ts | 带序号枚举的频次表格 |
SortedTiles | oppia-visualization-sorted-tiles.component.ts | 按频次排序的答案磁贴 |
ClickHexbins的注释还解释了为何选择六边形分箱:相比正方形网格,六边形在平面密铺中拥有对称的最近邻关系,覆盖效率高约 13%,且视觉上对密度分布更少偏见。这类可视化正是通过BaseInteraction.answer_visualizations与各交互的_answer_visualization_specs绑定到具体交互上的。
贯穿全局的统一抽象:CustomizationArgSpec
无论 actions、interactions 还是 rich_text_components,都用同一套"自定义参数规格"抽象描述可配置项,其值对象定义在 extensions/domain.py:
class CustomizationArgSpec: def __init__(self, name, description, schema, default_value) -> None: self.name = name self.description = description self.schema = schema self.default_value = default_value def to_dict(self) -> CustomizationArgSpecsDict: return { 'name': self.name, 'description': self.description, 'schema': self.schema, 'default_value': self.default_value, }domain.py同时定义了参数默认值允许的类型集合AllowedDefaultValueTypes,涵盖字符串、浮点数、图结构(GraphDict)、图片区域(ImageAndRegionDict)、带字幕的 Unicode/HTML 以及它们的列表。CustomizationArgSpec的具体实例化方式有两种:actions 与 interactions 的后端基类各自通过[domain.CustomizationArgSpec(**cas) for cas in self._customization_arg_specs]批量构造(见 extensions/actions/base.py 与 extensions/interactions/base.py);编辑器前端则以 JSON 形式消费同一份规格,实现"一处声明、双端可用"。
扩展开发实战:以新增一个交互为例
综合以上源码结构,可以为 Oppia 新增一个交互梳理出完整开发路线(该流程同样适用于新增富文本组件,仅需将目标目录换成rich_text_components):
- 创建目录骨架:在 extensions/interactions 下新建
<InteractionName>/目录,参考 TextInput 的标准结构; - 编写后端 Python 类:继承
BaseInteraction(extensions/interactions/base.py),声明name、description、display_mode、answer_type、is_trainable、is_terminal、is_linear、can_have_solution、show_generic_submit_button等属性,并通过_customization_arg_specs声明可配置参数(注意利用 schemavalidators约束参数范围); - 声明答案可视化:在
_answer_visualization_specs中组合id、options、calculation_id(引用 extensions/answer_summarizers/models.py 的计算类); - 定义规则描述:在 interaction_specs.json 中登记规则描述与规则模板,使用
{{参数名|对象类型}}占位符语法; - 编写前端组件:实现交互组件、回答组件、短回答组件三个 Angular 组件(及 HTML 模板),并在
<InteractionName>.ts汇总 require 依赖; - 实现规则与校验服务:提供
<interaction-name>-rules.service.ts与<interaction-name>-validation.service.ts; - 补充测试:参照各模块的
*_test.py与.spec.ts文件补齐后端单测与前端单元测试(如 base_test.py、base-interaction-validation.service.spec.ts); - 注册与构建:确保交互被 extensions/interactions/interactions.module.ts 等模块聚合,并满足
BaseInteraction对缩略图(178 × 146 像素)等约定要求。
结语
Oppia 的扩展体系以extensions目录为枢纽,用"自定义参数规格"统一描述配置、用"答案汇总计算 + 可视化"统一呈现统计、用"Python 声明 + TypeScript 渲染"统一实现前后端,形成了高度模块化、可测试、可组合的插件架构。开发者只要吃透 extensions/interactions/base.py 与 extensions/rich_text_components/components.py 两个基类,即可按固定范式快速扩展新的交互与富文本组件;而 extensions/domain.py、extensions/answer_summarizers/models.py、extensions/visualizations/models.py 则分别提供了跨扩展共享的值对象、统计计算与可视化展示能力。对于想要深入源码的读者,建议从BaseInteraction、BaseRteComponent、BaseVisualization三个基类及其测试文件入手,逐一对照各交互/组件的具体子类实现,即可完整掌握这套扩展体系的设计全貌。
- 教育
- 后端
- 前端
【免费下载链接】oppia
A free, online learning platform to make quality education accessible for all.
相关推荐
Linera插件系统:可扩展架构与自定义组件
Linera插件系统:可扩展架构与自定义组件 引言 在区块链应用开发中,高效的数据索引和查询是构建复杂DApp(去中心化应用)的关键挑战。Linera协议通过其
区块链Web3革命性x86模拟器FEX:在ARM64 Linux上无缝运行Windows游戏和应用的终极指南
革命性x86模拟器FEX:在ARM64 Linux上无缝运行Windows游戏和应用的终极指南 FEX是一款革命性的x86和x86 64用户模式模拟器,专门为A
JIT编译虚拟化从零到物联网:如何用Arduino ESP32避开新手最易踩的5个坑?
从零到物联网:如何用Arduino ESP32避开新手最易踩的5个坑? 你是否曾满怀热情地开始一个ESP32物联网项目,却在环境配置阶段就碰壁?明明按照教程一步
嵌入式物联网驱动开发
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考