AI2 代码块预览

把伪代码字面翻译成代码块图,给文档与教程用。只读、不检查语法,仅供参考。

接入:页面只要这一行

<script src="ai2-block-preview-loader.min.js" data-base="./"></script>

页面上没有预览时它什么都不加载。默认写法就是 Markdown 的 ```ai2-blocks 围栏——文档照常写,渲染出来的 <pre><code class="language-ai2-blocks"> 会被原地换成块图; 本页除了特意演示的那一张,全是这么写的。手写页面也可以用 <div data-ai2-blocks> 容器(能带 data-* 选项)。

Markdown 围栏(默认写法)

语言名 ai2-blocks / ai-blocks / ai2-block 都认,其它语言的代码块一律不碰。下面这张特意用的是 kramdown/rouge 渲染出来的形态 (语言 class 挂在最外层 div 上、里面的 code 一个 class 都没有), 和 markdown-it / hugo 的 <pre><code class="language-…"> 一样能认。

when 按钮_保存.Click() {
  微数据库1.StoreValue("note", 文本输入框_笔记.Text)
  对话框1.ShowAlert("已保存")
}

和普通代码块共存(Prism 高亮对照)

页面上引的是 fun123.cn 生产环境同款 /static/js/prism.js + /static/css/prism.css (注意 css 不在 js 目录下,写成 /static/js/prism.css 会 404—— 而这份 prism.js 带 toolbar/copy 插件,没样式时那些按钮会变成裸文字,看着就是"排版乱了")。 只有 ai2-blocks 类的围栏会被换成块图, 其它语言一概不碰——下面这段 PHP(App Inventor 连 MySQL 常用的那个中转脚本)就是普通代码块, 照常由 Prism 高亮,两者同页互不干扰。

<?php
/*
 * Written By: ShivalWolf
 * Date: 2011/06/03
 * Contact: Shivalwolf@domwolf.net
 *
 * UPDATE 2014/07/11
 * mysql API (deprecated) replaced by mysqli by Taifun
 *
 * UPDATE 2016/02/21
 * Bugfix Undefined variable: csv
 */

/************************************CONFIG****************************************/
//DATABSE DETAILS//
$DB_ADDRESS="";
$DB_USER="";
$DB_PASS="";
$DB_NAME="";

//SETTINGS//
//This code is something you set in the APP so random people cant use it.
$SQLKEY="secret";
/************************************CONFIG****************************************/

//these are just in case setting headers forcing it to always expire
header('Cache-Control: no-cache, must-revalidate');

if( isset($_POST['query']) && isset($_POST['key']) ){          //checks if the tag post is there
  header('Content-type: text/csv');

  if($_POST['key']==$SQLKEY){                                  //validates the SQL key
    $query=urldecode($_POST['query']);
    $conn = new mysqli($DB_ADDRESS,$DB_USER,$DB_PASS,$DB_NAME);

    if($conn->connect_error){
      header("HTTP/1.0 400 Bad Request");
      echo "ERROR Database Connection Failed: " . $conn->connect_error, E_USER_ERROR;
    } else {
      $result=$conn->query($query);                            //runs the posted query
      if($result === false){
        header("HTTP/1.0 400 Bad Request");
        echo "Wrong SQL: " . $query . " Error: " . $conn->error, E_USER_ERROR;
      } else {
        if (strlen(stristr($query,"SELECT"))>0) {              //tests if it's a SELECT statement
          $csv = '';
          while ($fieldinfo = $result->fetch_field()) {
            $csv .= $fieldinfo->name.",";
          }
          $csv = rtrim($csv, ",")."\n";
          echo $csv;                                           //prints header row
          $csv = '';

          $result->data_seek(0);
          while($row = $result->fetch_assoc()){
            foreach ($row as $key => $value) {
              $csv .= $value.",";
            }
            $csv = rtrim($csv, ",")."\n";
          }
          echo $csv;                                           //prints all data rows
        } else {
          header("HTTP/1.0 201 Rows");
          echo "AFFECTED ROWS: " . $conn->affected_rows;
        }
      }
      $conn->close();                                          //closes the DB
    }
  } else {
     header("HTTP/1.0 400 Bad Request");
     echo "Bad Request";                                       //reports if the secret key was bad
  }
} else {
        header("HTTP/1.0 400 Bad Request");
        echo "Bad Request";
}
?>

基础:事件 + 变量 + 判断(这张用 div 容器写法)

全页只有这一张用 <div data-ai2-blocks> 容器写法, 用来对照——需要 data-code / data-src / data-png 这类选项时只能用容器;纯写文档用围栏就够了。

global score = 0 when 按钮1.Click() { global score = score + 1 if score > 10 { 标签1.Text = "赢了!" } else { 标签1.Text = join("得分: ", score) } }

组件实例名写中文(按钮1、标签1,与中文版设计器的默认命名一致), 成员名写英文内部名(.Text / .Click),块上自动显示成「文本」「被点击时」。

循环与列表

when Screen1.Initialize() {
  global items = []
  for i = 1 to 5 {
    listAdd(items, random(1, 100))
  }
  列表显示框1.Elements = items
}

过程与函数

function Add(a, b) {
  return a + b
}
procedure ShowSum(x, y) {
  标签1.Text = join("和 = ", Add(x, y))
}
when 按钮1.Click() {
  ShowSum(3, 4)
}

多行文本

字符串必须一行写完,换行用 \n 转义——含换行的字符串自动生成 「多行文本」块(text_multiline_text),单行字符串仍是普通文本块;读取现有工程时, 多行内容也以 \n 转义显示。

global 日志 = "开机自检完成\n等待指令"

when 按钮1.Click() {
  日志 = join(日志, "\n点击了按钮")
  标签1.Text = 日志
}

块图 / 原文 / 等价源码(默认就有)

每张预览默认带 代码块 / 原始 两个 tab, 在同一块视图里切换,不用写任何属性。「原始」就是你写的那段伪代码原文; 三种源码按 tab 懒生成——默认停在「代码块」上,没点开的一分开销都不花。
右上角三个图标:复制(块图 tab 复制可粘贴的代码块,文本 tab 复制当前那段文本)、 下载图片(只在块图 tab)、全屏(再点一次或按 Esc 退出)。
需要 YAIL、Python 或 JavaScript 时显式写入 data-code,例如 data-code="raw,python";完全不要文本 tab 写 data-code="off";单独关图标 data-png="off" / data-fullscreen="off"。

global 总分 = 0

procedure 记一次(得分) {
  总分 = 总分 + 得分
  if 总分 >= 100 {
    标签1.Text = "过关!"
  } else {
    标签1.Text = join("还差 ", 100 - 总分, " 分")
  }
}

when 按钮1.Click() {
  记一次(10)
}

整段「界面 + 逻辑」JSON

把「一键解释项目 / 当前屏幕」导出的 {"$components":[…],"$blocks":[…]} 整段贴进来就行(参考项目库 ai_reference_projects.json 存的也是这个格式):$blocks 多段会合并进 同一个工作区,$components 另开一个「界面」tab 画成界面示意图 (容器递归、尺寸 -1/-2/百分比、&HAARRGGBB 配色,非可视组件排在下面)。
写在 ```ai2-blocks 围栏里即可(JSON 里的 <xml> 标签由 Markdown 自动转义,手写 HTML 时要自己写成 &lt;)。这类输入是从编辑器原样拷出来的, 不打水印,右上角「复制」也是原样复制并提示到设计视图粘贴。

{
  "$components": [{"$Name": "Screen1", "$Type": "Form", "$Version": "31", "Title": "日志小工具",
    "$Components": [
      {"$Name": "StatusLabel", "$Type": "Label", "$Version": "5", "Text": "就绪", "FontSize": "14", "TextColor": "&HFF3C5CFF", "Uuid": "1"},
      {"$Name": "LogInput", "$Type": "TextBox", "$Version": "6", "Hint": "输入点什么…", "Width": "-2", "Uuid": "2"},
      {"$Name": "Row1", "$Type": "HorizontalArrangement", "$Version": "4", "Width": "-2", "AlignHorizontal": "3", "$Components": [{"$Name": "BtnSave", "$Type": "Button", "$Version": "7", "Text": "保存", "FontSize": "16", "Uuid": "3"}, {"$Name": "BtnClear", "$Type": "Button", "$Version": "7", "FontSize": "16", "Width": "-2", "Text": "清空", "Uuid": "-51472725"}]},
      {"$Name": "AutoSave", "$Type": "CheckBox", "$Version": "2", "Text": "自动保存", "Checked": "True", "Uuid": "4"},
      {"$Name": "Logo", "$Type": "Image", "$Version": "4", "Picture": "logo.png", "Height": "60", "Uuid": "5"},
      {"$Name": "TinyDB1", "$Type": "TinyDB", "$Version": "3", "Uuid": "6"},
      {"$Name": "Clock1", "$Type": "Clock", "$Version": "4", "Uuid": "7"}
    ]}],
  "$blocks": [
    "<xml><block xmlns=\"http://www.w3.org/1999/xhtml\" type=\"component_event\" id=\"e1\" x=\"10\" y=\"10\"><mutation component_type=\"Button\" is_generic=\"false\" instance_name=\"BtnClear\" event_name=\"Click\"></mutation><field name=\"COMPONENT_SELECTOR\">BtnClear</field><statement name=\"DO\"><block type=\"component_set_get\" id=\"s1\"><mutation component_type=\"TextBox\" set_or_get=\"set\" property_name=\"Text\" is_generic=\"false\" instance_name=\"LogInput\"></mutation><field name=\"COMPONENT_SELECTOR\">LogInput</field><field name=\"PROP\">Text</field><value name=\"VALUE\"><block type=\"text\" id=\"t1\"><field name=\"TEXT\"></field></block></value><next><block type=\"component_set_get\" id=\"s2\"><mutation component_type=\"Label\" set_or_get=\"set\" property_name=\"Text\" is_generic=\"false\" instance_name=\"StatusLabel\"></mutation><field name=\"COMPONENT_SELECTOR\">StatusLabel</field><field name=\"PROP\">Text</field><value name=\"VALUE\"><block type=\"text\" id=\"t2\"><field name=\"TEXT\">已清空输入</field></block></value></block></next></block></statement></block></xml>",
    "<xml><block xmlns=\"http://www.w3.org/1999/xhtml\" type=\"component_event\" id=\"e2\" x=\"10\" y=\"300\"><mutation component_type=\"Form\" is_generic=\"false\" instance_name=\"Screen1\" event_name=\"Initialize\"></mutation><field name=\"COMPONENT_SELECTOR\">Screen1</field><statement name=\"DO\"><block type=\"component_set_get\" id=\"s3\"><mutation component_type=\"Label\" set_or_get=\"set\" property_name=\"Text\" is_generic=\"false\" instance_name=\"StatusLabel\"></mutation><field name=\"COMPONENT_SELECTOR\">StatusLabel</field><field name=\"PROP\">Text</field><value name=\"VALUE\"><block type=\"text\" id=\"t3\"><field name=\"TEXT\">就绪</field></block></value></block></statement></block></xml>"
  ]
}

拓展组件(ML Kit 系)

拓展没有中文调色板名,实例名保持英文(MLKitTextRecognizer1)。 能渲染的前提是组件类型库里有拓展描述符——component_types.json 里已经合并进来了。

文字识别(OCR) — 选一张图 → 识别出文字。Script 决定用哪套模型:latin 认英文,chinese 认中文(也能认中文里夹的英文)。

global picked = ""

when 图像选择器1.AfterPicking() {
  picked = 图像选择器1.Selection
  图像1.Picture = picked
  MLKitTextRecognizer1.Script = "chinese"
  MLKitTextRecognizer1.RecognizeFromFile(picked)
}

when MLKitTextRecognizer1.RecognitionSuccess(text, resultJson) {
  标签1.Text = text
}

when MLKitTextRecognizer1.RecognitionFailed(operation, errorMessage) {
  标签1.Text = join("识别失败:", errorMessage)
}

条码 / 二维码 — 摄像头实时扫描。预览容器要在设计器里选一个布局;ContinuousScan 为真时扫完一个还能接着扫。

when 按钮1.Click() {
  MLKitCodeScanner1.ContinuousScan = true
  MLKitCodeScanner1.StartScan()
}

when MLKitCodeScanner1.GotBarcode(rawValue, format, valueType, displayValue, detailJson) {
  标签1.Text = join(format, ":", rawValue)
}

when MLKitCodeScanner1.GotWifi(ssid, password, encryption) {
  标签1.Text = join("WiFi ", ssid, " 密码 ", password)
}

when MLKitCodeScanner1.ScanFailed(operation, errorMessage) {
  标签1.Text = errorMessage
  MLKitCodeScanner1.StopScan()
}

文档扫描 — 调起系统扫描界面,返回的是能直接用的文件路径。用户按返回键走 ScanCancelled,不是失败。

when 按钮1.Click() {
  MLKitDocScanner1.Scan()
}

when MLKitDocScanner1.GotScanResult(pageCount, imagePaths, pdfPath) {
  图像1.Picture = imagePaths[1]
  标签1.Text = join("共 ", pageCount, " 页,PDF:", pdfPath)
}

when MLKitDocScanner1.ScanCancelled() {
  标签1.Text = "已取消"
}

when MLKitDocScanner1.ScanFailed(errorMessage) {
  标签1.Text = errorMessage
}

图像标注 — 认出图里有什么。标签同时给英文原名和中文名,置信度低于阈值的不返回。

when 图像选择器1.AfterPicking() {
  MLKitImageLabeler1.ConfidenceThreshold = 0.6
  MLKitImageLabeler1.LabelFromFile(图像选择器1.Selection)
}

when MLKitImageLabeler1.GotLabels(topLabel, topName, labels, names, resultJson) {
  标签1.Text = join("最像:", topName, "(", topLabel, ")")
}

when MLKitImageLabeler1.NoLabelFound(operation) {
  标签1.Text = "没认出来"
}

目标检测 — GotObjects 先报总数,随后每个物体触发一次 GotObject,带位置矩形框。

global found = 0

when 图像选择器1.AfterPicking() {
  found = 0
  MLKitObjectDetector1.MultipleObjects = true
  MLKitObjectDetector1.DetectFromFile(图像选择器1.Selection)
}

when MLKitObjectDetector1.GotObjects(count, imageWidth, imageHeight, resultJson) {
  标签1.Text = join("找到 ", count, " 个物体")
}

when MLKitObjectDetector1.GotObject(index, x, y, width, height, label, confidence, trackingId) {
  found = found + 1
  画框(x, y, x + width, y + height)
}

// Canvas 没有画矩形的方法,用四条线拼
procedure 画框(左, 上, 右, 下) {
  画布1.DrawLine(左, 上, 右, 上)
  画布1.DrawLine(右, 上, 右, 下)
  画布1.DrawLine(右, 下, 左, 下)
  画布1.DrawLine(左, 下, 左, 上)
}

人脸检测 — 返回人脸数与详细 JSON(位置框、关键点、微笑/睁眼概率)。开轮廓和分类会更慢。

when 图像选择器1.AfterPicking() {
  MLKitFaceDetector1.Classifications = true
  MLKitFaceDetector1.DetectFromFile(图像选择器1.Selection)
}

when MLKitFaceDetector1.DetectionSuccess(resultJson, faceCount) {
  标签1.Text = join("检测到 ", faceCount, " 张脸")
  标签2.Text = resultJson
}

when MLKitFaceDetector1.DetectionFailed(operation, errorMessage) {
  标签1.Text = errorMessage
}

面部网格 — 468 个带 xyz 的关键点。可按区域取点(脸廓/左右眼/眉毛/嘴唇/鼻梁),用于贴纸定位。

when 图像选择器1.AfterPicking() {
  MLKitFaceMesh1.DetectFromFile(图像选择器1.Selection)
}

when MLKitFaceMesh1.GotFaceMesh(faceCount, pointCount, boundsJson) {
  标签1.Text = join("脸 ", faceCount, " 张,关键点 ", pointCount, " 个")
  标签2.Text = MLKitFaceMesh1.RegionPoints("leftEye", 1)
}

when MLKitFaceMesh1.NoFaceFound(operation) {
  标签1.Text = "没找到脸"
}

姿势检测 — 33 个人体关键点及置信度,可做体感游戏、健身计数。AccurateMode 更准但更慢。

when 图像选择器1.AfterPicking() {
  MLKitPoseDetector1.AccurateMode = false
  MLKitPoseDetector1.DetectFromFile(图像选择器1.Selection)
}

when MLKitPoseDetector1.DetectionSuccess(resultJson, landmarkCount) {
  标签1.Text = join("关键点 ", landmarkCount, " 个")
  标签2.Text = resultJson
}

when MLKitPoseDetector1.DetectionFailed(operation, errorMessage) {
  标签1.Text = errorMessage
}

人像分割 — 抠人像 / 换背景 / 虚化,结果存成 PNG 并返回路径,可直接给图像组件显示。

global src = ""

when 图像选择器1.AfterPicking() {
  src = 图像选择器1.Selection
  MLKitSegmenter1.Threshold = 0.8
  MLKitSegmenter1.ExtractPerson(src)
}

when 按钮1.Click() {
  MLKitSegmenter1.ReplaceBackgroundWithColor(src, "&amp;HFF4CAF50")
}

when MLKitSegmenter1.GotResult(imagePath, width, height) {
  图像1.Picture = imagePath
}

when MLKitSegmenter1.SegmentFailed(operation, errorMessage) {
  标签1.Text = errorMessage
}

手写识别 — 设计器里给「书写画布」选一块画布,用户写完直接调识别。语言包没下会自动下载。

when 按钮1.Click() {
  MLKitHandwriting1.LanguageTag = "zh-Hani"
  MLKitHandwriting1.Recognize()
}

when 按钮2.Click() {
  MLKitHandwriting1.Clear()
  标签1.Text = ""
}

when MLKitHandwriting1.GotText(text, candidatesJson) {
  标签1.Text = text
}

when MLKitHandwriting1.ModelDownloadFailed(errorMessage) {
  标签1.Text = join("语言包下载失败:", errorMessage)
}

语言识别 — 判断一段文本是哪种语言,100+ 种,完全离线。常用来自动选翻译的源语言。

when 按钮1.Click() {
  MLKitLanguageId1.IdentifyLanguage(文本输入框1.Text)
}

when MLKitLanguageId1.GotLanguage(languageTag, name) {
  标签1.Text = join(name, "(", languageTag, ")")
}

when MLKitLanguageId1.NoLanguageFound(text) {
  标签1.Text = "认不出是什么语言"
}

离线翻译 — 语言包首次用要联网从 Google 下载(每种约 30MB),下完永久离线可用。

when 按钮1.Click() {
  MLKitTranslator1.SourceLanguage = "en"
  MLKitTranslator1.TargetLanguage = "zh"
  MLKitTranslator1.Translate(文本输入框1.Text)
}

when MLKitTranslator1.GotTranslation(originalText, translatedText) {
  标签1.Text = translatedText
}

when MLKitTranslator1.ModelDownloaded() {
  标签1.Text = "语言包已就绪"
}

when MLKitTranslator1.TranslateFailed(operation, errorMessage) {
  标签1.Text = errorMessage
}

实体抽取 — 从文本里挑出地址、日期、电话、快递单号、航班号、银行卡号等,并给出在原文中的位置。

when 按钮1.Click() {
  MLKitEntityExtractor1.Extract(文本输入框1.Text)
}

when MLKitEntityExtractor1.GotEntities(count, resultJson) {
  标签1.Text = join("抽出 ", count, " 条")
  标签2.Text = resultJson
}

when MLKitEntityExtractor1.NoEntityFound(text) {
  标签1.Text = "没抽到有用信息"
}

智能回复 — 按对话上下文给 1~3 条候选回复。只支持英语对话,中文会触发「语言不支持」。

when 按钮1.Click() {
  MLKitSmartReply1.AddRemoteMessage(文本输入框1.Text, "friend")
  MLKitSmartReply1.SuggestReplies()
}

when MLKitSmartReply1.GotSuggestions(count, suggestions) {
  列表显示框1.Elements = suggestions
}

when MLKitSmartReply1.LanguageNotSupported() {
  标签1.Text = "只支持英语对话"
}

自定义分类 — 用你自己训练的 TFLite 模型分类,突破基础模型 400 个通用类别的限制。模型填素材文件名即可。

when 图像选择器1.AfterPicking() {
  MLKitCustomLabeler1.ModelFile = "flowers.tflite"
  MLKitCustomLabeler1.ConfidenceThreshold = 0.7
  MLKitCustomLabeler1.LabelFromFile(图像选择器1.Selection)
}

when MLKitCustomLabeler1.GotLabels(topLabel, topConfidence, labels, resultJson) {
  标签1.Text = join(topLabel, " ", round(topConfidence * 100), "%")
}

when MLKitCustomLabeler1.LabelFailed(operation, errorMessage) {
  标签1.Text = errorMessage
}

自定义检测 — 自训模型做检测 + 精细分类,既给位置框也给具体品类。

when 图像选择器1.AfterPicking() {
  MLKitCustomObjectDetector1.ModelFile = "parts.tflite"
  MLKitCustomObjectDetector1.Classification = true
  MLKitCustomObjectDetector1.DetectFromFile(图像选择器1.Selection)
}

when MLKitCustomObjectDetector1.GotObject(index, x, y, width, height, label, confidence, trackingId) {
  画布1.DrawShape([[x, y], [x + width, y], [x + width, y + height], [x, y + height]], false)
  标签1.Text = label
}

when MLKitCustomObjectDetector1.NoObjectFound(operation) {
  标签1.Text = "没检测到物体"
}

直接给 Blockly XML

从代码块编辑器里复制出来的 XML 也能直接渲染。这类输入没经过任何转换, 所以不打水印,右上角「复制」是原样复制并提示到代码块视图粘贴。

<block type="controls_if"><value name="IF0"><block type="logic_boolean"><field name="BOOL">TRUE</field></block></value></block>