AimdRecorder
当你希望把 AIMD 输入控件直接渲染在协议正文流里时,使用 AimdRecorder。
示例
<script setup lang="ts">
import { ref } from "vue"
import {
AimdRecorder,
createEmptyProtocolRecordData,
type AimdProtocolRecordData,
} from "@airalogy/aimd-recorder"
import "@airalogy/aimd-recorder/styles"
const content = ref(`# Protocol
样本名:{{var|sample_name: str, title="样本名", description="样本的人类可读标签", examples=["S-001"]}}
记录者:{{var|operator: UserName}}
记录时间:{{var|current_time: CurrentTime}}
温度设置:{{var|temperature: float = 25.0, title="温度 (C)", description="环境温度,单位为摄氏度", examples=[25.0, 37.0]}}
实验摘要:{{var|summary: AiralogyMarkdown}}
质粒:{{var|plasmid: DNASequence}}`)
const record = ref<AimdProtocolRecordData>(createEmptyProtocolRecordData())
</script>
<template>
<AimdRecorder
v-model="record"
:content="content"
locale="zh-CN"
current-user-name="张三"
/>
</template>record 数据结构:
{
"var": {},
"step": {},
"check": {},
"quiz": {}
}内建 Recorder 行为
CurrentTime和UserName可以从运行时上下文自动填值。AiralogyMarkdown会渲染为横铺的 AIMD/Markdown 字段,支持预览与源码切换;预览会通过 AIMD renderer 输出 Markdown 并渲染 Mermaid 代码块,源码编辑仍保留切换到所见即所得的能力。DNASequence会渲染专用序列控件,支持交互式模式、原始结构模式、文件导入导出、拓扑切换、feature 编辑,以及基于SeqViz的可视化。EntityRef和list[EntityRef]会在宿主传入entityResolvers后渲染为实体引用控件;resolver key 可以匹配 AIMDsourceconnector id,也可以匹配entitynamespace。宿主可以使用@airalogy/aimd-core/utils的createAimdEntityResolversFromConnectors()从解析出的connectorsmetadata 生成这些 resolver。- Collector 绑定的
Observation[T]和list[Observation[T]]字段会在宿主传入collectorProviders后渲染为数据采集控件,支持单次读取、手动轮询控制、授权、取消、来源记录和显式手工回退。 ref_var如果已有记录值,会优先以内联只读内容显示当前值。var和var_table标签会显示 AIMD 里的title,设置标题时仍显示规范 id,并且只在 hover 或键盘 focus 时展示description与example/examples详情。没有显式 placeholder 覆盖时,第一个标量示例会作为默认占位文案。bool | None、Literal[...] | None、BloodType | None这类带None的下拉型字段会显示本地化的未填写选项,并把该选项保存为null。必填下拉字段不会显示空值选项,用户仍然需要选择真实枚举值。list[str]、list[int]、list[float]以及等价的可选标量列表变量会渲染为整行字段,支持可重复添加、可拖拽排序的逐项输入,并额外支持 JSON 数组模式,最终保存为标量数组,而不是只能使用通用结构化 textarea。AimdRecorder内建默认折叠的 protocol-aware 当前 Record 搜索控件,可以搜索全部字段或某个指定字段;展开后会 sticky 保持在 recorder 顶部,高亮匹配字段控件,在匹配项之间跳转,并在原生文本输入控件中尽量选中命中的文本片段。choice、true_false、blank、open、scale五类 quiz 都有内建 recorder 输入。- 数值
var输入会识别gt、ge、lt、le、multiple_of这类 Pydantic 风格约束;这些约束只对int、integer、float、number类型生效。 - client assigner 会用同一组数值约束判断依赖是否就绪;依赖字段违反声明边界时会跳过执行。
Record 校验
使用 Protocol 解析返回的 Pydantic JSON Schema 作为前后端共享契约,宿主需要在提交前拦截时,再通过组件 ref 调用:
<AimdRecorder
ref="recorderRef"
v-model="record"
:content="content"
:validation-schema="parseResult.data?.json_schema"
/>const result = await recorderRef.value?.validate()
if (!result?.valid) returnSchema 契约同时接受 engine 键(vars、steps、checks)和兼容用的 platform 别名(research_variable、research_step、research_check),覆盖必填、类型、格式、pattern、枚举、数值边界、数组、对象及内建类型约束,也会先归一化上传文件等结构化值。Schema 标记为 required 且不可为空的 Recorder 输入即使已存在于内存 Record,空字符串和空数组仍会判为未填;可为空的属性接受 null,也不显示必填标记。没有 Schema 时,Recorder 回退到 AIMD 声明:自动把没有默认值的字段视为必填,但类型包含 None 或使用 Optional[...] 的字段除外;AIMD 或 fieldMeta 中显式的 required 设置仍会生效。
Recorder 默认只在当前字段 change 或 blur 时重新校验,不会清除其他字段的错误;可通过 validationTriggers 调整。表格错误使用 var_table:samples:0:concentration 这样的精确行列 key 记录、展示和聚焦。
AimdRecorder 和 AimdRecorderEditor 都暴露 validate()、validateField(fieldKey)、clearValidation(fieldKey?) 和 focusFirstInvalidField()。校验结果包含 issues 和 fieldState。前端提交拦截通过后,仍应执行服务端 Pydantic 权威校验;服务端错误可通过已有 fieldState 属性回填。
Collector Provider
Collector 声明来自 Protocol,真实设备和网络访问由宿主通过 connector id 注入 provider map 来控制:
const collectorProviders = {
lab_sensor_gateway: {
async read({ collector, signal }) {
const response = await fetch(`/api/sensors/${collector.channel}`, { signal })
return response.json()
},
},
}<AimdRecorder
v-model="record"
:content="content"
:collector-providers="collectorProviders"
:request-collector-permission="requestCollectorPermission"
:collector-record-key="recordId"
collector-actor-id="user-123"
/>Provider 可以返回裸值或 observation envelope。Recorder 在写入 Record 前会补全 received_at 和可信 source metadata。授权回调可以返回 false、true/"once" 或 "record";当 Record key 或 Protocol 内容改变时,Record 范围授权会被清除。规范详见 Collector 语法与运行时。
当前浏览器运行时支持 snapshot 和手动启停的 polling。stream、自动 record/step 生命周期与文件化 ObservationSeriesRef[T] 采集可以解析,但当前不执行。
Client Assigner
前端受限的 client assigner 会在 recorder 中本地执行。
Water: {{var|water_volume_ml: float}}
Lemon: {{var|lemon_juice_ml: float}}
Total: {{var|total_liquid_ml: float}}
```assigner runtime=client
assigner(
{
mode: "auto",
dependent_fields: ["water_volume_ml", "lemon_juice_ml"],
assigned_fields: ["total_liquid_ml"],
},
function calculate_total_liquid_ml({ water_volume_ml, lemon_juice_ml }) {
return {
total_liquid_ml: Math.round((water_volume_ml + lemon_juice_ml) * 100) / 100,
};
}
);
```如果使用 mode: "manual",AimdRecorder 会通过组件 ref 暴露显式触发方法:
recorderRef.value?.runClientAssigner("calculate_total_liquid_ml")
recorderRef.value?.runManualClientAssigners()语言与单独 Quiz 组件
AimdRecorder 和 AimdQuizRecorder 都支持通过 locale 切换内建标签:
<AimdRecorder locale="zh-CN" />
<AimdQuizRecorder :quiz="quiz" locale="zh-CN" />单独题目控件用法:
<script setup lang="ts">
import { ref } from "vue"
import { AimdQuizRecorder } from "@airalogy/aimd-recorder"
import "@airalogy/aimd-recorder/styles"
const answer = ref("")
const quiz = {
id: "quiz_single_1",
type: "choice",
mode: "single",
stem: "请选择一个选项",
options: [
{ key: "A", text: "选项 A" },
{ key: "B", text: "选项 B" },
],
}
</script>
<template>
<AimdQuizRecorder v-model="answer" :quiz="quiz" />
</template>如果某个 choice 或 true_false 选项定义了 followups,recorder 只会在该选项被选中后显示这些补充输入。这类题目的答案值结构为 { selected, followups };普通 choice 题继续使用原来的字符串或字符串数组格式,普通 true/false 题继续使用布尔值。
显示评分结果
如果宿主已经在别处完成了评分,可以把结果回传给 AimdRecorder 或 AimdQuizRecorder 直接展示。对于可确定性自动评分的 scale 量表,recorder 也可以直接在前端计算总分和分组。
整份 recorder:
<AimdRecorder
v-model="record"
:content="content"
:quiz-grades="quizGrades"
choice-option-explanation-mode="selected"
scale-grade-display-mode="submitted"
/>单题组件:
<AimdQuizRecorder
v-model="answer"
:quiz="quiz"
choice-option-explanation-mode="graded"
:grade="{
quiz_id: 'quiz_single_1',
earned_score: 4,
max_score: 5,
status: 'partial',
method: 'keyword_rubric',
feedback: '答案方向正确,但还缺少一个要点。',
review_required: true,
}"
/>推荐做法:
choice与常规blank可以直接本地自动评分open题或高开放性blank建议由后端 provider 评分- 练习题可以实时传入
quizGrades,让学生立即看到状态、得分和反馈 - 可确定性
scale量表可以本地自动评分,再用scaleGradeDisplayMode控制是填完即显示,还是提交后才显示 - 如果
choice选项里定义了explanation,可以用choiceOptionExplanationMode="selected"在选中后立即显示讲解 - 如果希望学生提交后再显示选项讲解,可以配合
:submitted="isSubmitted"和choiceOptionExplanationMode="submitted" - 如果希望
scale的总分和分组只在提交后出现,可以配合:submitted="isSubmitted"和scaleGradeDisplayMode="submitted" - 如果希望等评分完成后再显示选项讲解,可以使用
choiceOptionExplanationMode="graded" - 考试题可以先不传
quizGrades,等统一评分后再展示结果 - 尚未作答且状态为
ungraded的题目,默认不会显示评分面板 - 正式考试不要把真实模型密钥传给前端
作业或提交后讲解示例:
<AimdRecorder
v-model="record"
:content="content"
:submitted="isSubmitted"
choice-option-explanation-mode="submitted"
/>其中 submitted 由宿主应用控制。AimdRecorder 本身不会自动判断“是否已提交”,也不内建交卷按钮。