ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Oppia 扩展系统(Extensions)完全解析:interactions、objects、RTE 组件与可视化插件的架构与实践

Oppia 扩展系统(Extensions)完全解析:interactions、objects、RTE 组件与可视化插件的架构与实践
  • 教育
  • 后端
  • 前端

【免费下载链接】oppia

A free, online learning platform to make quality education accessible for all.

项目地址:https://gitcode.com/gh_mirrors/op/oppia
点击查看免费下载

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_pluginsCKEditor RTE 中使用的插件JavaScript 前端插件
interactionsOppia 网站支持的交互(互动组件)的前后端代码Python + TypeScript
objects各类类型化编辑器(布尔编辑器、文件路径编辑器等)的前后端代码Python + TypeScript
rich_text_componentsOppia RTE 支持的富文本组件的前后端代码Python + TypeScript
value_generatorsOppia 中使用的值生成器代码Python + TypeScript
visualizationsOppia 中使用的答案可视化代码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交互 IDunicode''
submitted_answer提交的答案unicode''
feedback对提交答案的反馈unicode''
time_spent_state_in_msecs在该状态停留的毫秒数int0

这些规格模式与交互、富文本组件中的自定义参数规格完全一致,体现了 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 给出了新增组件的三步流程:

  1. 在extensions/rich_text_components/下新建目录并放置全部文件(后端 Python 类、前端组件与模板);
  2. 在assets/rich_text_components_definitions.ts中为该组件新增一条 JSON 记录(含名称、描述、customization_arg_specs 等);
  3. 在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为布尔值。

仓库内置四种可视化:

可视化前端文件说明
ClickHexbinsoppia-visualization-click-hexbins.directive.ts在图片上以六边形分组叠加展示点击分布,面向ImageClickInput这类点击型交互
FrequencyTableoppia-visualization-frequency-table.directive.ts两列答案频次表格
EnumeratedFrequencyTableoppia-visualization-enumerated-frequency-table.directive.ts带序号枚举的频次表格
SortedTilesoppia-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):

  1. 创建目录骨架:在 extensions/interactions 下新建<InteractionName>/目录,参考 TextInput 的标准结构;
  2. 编写后端 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约束参数范围);
  3. 声明答案可视化:在_answer_visualization_specs中组合id、options、calculation_id(引用 extensions/answer_summarizers/models.py 的计算类);
  4. 定义规则描述:在 interaction_specs.json 中登记规则描述与规则模板,使用{{参数名|对象类型}}占位符语法;
  5. 编写前端组件:实现交互组件、回答组件、短回答组件三个 Angular 组件(及 HTML 模板),并在<InteractionName>.ts汇总 require 依赖;
  6. 实现规则与校验服务:提供<interaction-name>-rules.service.ts与<interaction-name>-validation.service.ts;
  7. 补充测试:参照各模块的*_test.py与.spec.ts文件补齐后端单测与前端单元测试(如 base_test.py、base-interaction-validation.service.spec.ts);
  8. 注册与构建:确保交互被 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.

项目地址:https://gitcode.com/gh_mirrors/op/oppia
点击查看免费下载

相关推荐

上一篇:SilentPatch:GTA III、罪恶都市、圣安地列斯在 Win10/11 上的兼容补丁,不崩不卡
下一篇:如何 2 小时落地:GB28181 国标视频监控平台的从 0 到 1 部署实录

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进