10 小时以前 35722562e9e13f0504acc15b740d042ecb810199
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
/**
 * 质量组件注册表 → AI 积木清单
 * <p>
 * 清单是注册表的**派生视图**:剥掉 icon / buildContent / validate 这些不可序列化的成员,
 * 只留下模型需要知道的「有哪些组件、每个组件能填哪些字段」。
 * <p>
 * 这份清单随请求传给后端拼进提示词,但它<b>不是</b>合法性的判据:真正把关的是装配器
 * 对着活注册表查 getQualityComponent。模型即便编出清单外的 type,也只会被跳过,
 * 永远变不成任意 HTML(清单里不含任何 HTML 能力的描述)。
 */
import type { QualityFieldSchema } from './types';
 
import { listQualityComponents } from './registry';
 
/** 清单里的一个可填字段,与后端 QcReportComponentSpecVO.Field 一一对应 */
export interface QualityCatalogField {
  key: string;
  label: string;
  type: QualityFieldSchema['type'];
  required?: boolean;
  bindable?: boolean;
  /**
   * 枚举候选项。
   * <p>
   * 用 {label, value} 成对给出:只给 label 模型会照着输出中文标签,
   * 只给 value 又不知道含义。成对给出,它才知道该输出哪个字面量。
   */
  enumOptions?: { label: string; value: number | string }[];
}
 
/** 一个组件积木 */
export interface QualityComponentCatalog {
  type: string;
  label: string;
  /** 组件分类的中文显示名,帮模型理解组件用途 */
  category: string;
  /**
   * 组件的选型说明,来自注册表的 aiHint。
   * <p>
   * 只有 label 时,模型面对「报告抬头」这类内容会在 Heading / Text / ReportHeader 之间瞎猜,
   * 且总会挑字段最简单的基础组件。这段文字就是给它划边界的,不是给人看的。
   */
  hint?: string;
  fields: QualityCatalogField[];
}
 
/** 从活注册表生成积木清单,顺序与设计器左侧面板一致 */
export function buildQualityComponentCatalog(): QualityComponentCatalog[] {
  const definitions = listQualityComponents();
  // 注册表要显式调用 registerAllQualityComponents() 才有内容(设计器在 init 时做)。
  // 空清单会被后端判为入参非法,报出来的错和真实原因隔了好几层,所以在这里就说清楚。
  if (definitions.length === 0) {
    throw new Error('质量组件注册表为空,请先调用 registerAllQualityComponents()');
  }
  return definitions.map((definition) => {
    const item: QualityComponentCatalog = {
      type: definition.type,
      label: definition.label,
      category: definition.category,
      fields: definition.propertySchema.map(toCatalogField),
    };
    // 缺席即省略,与 toCatalogField 同一风格:省掉的字段能明显缩小提示词
    if (definition.aiHint) {
      item.hint = definition.aiHint;
    }
    return item;
  });
}
 
function toCatalogField(field: QualityFieldSchema): QualityCatalogField {
  const item: QualityCatalogField = {
    key: field.key,
    label: field.label,
    type: field.type,
  };
  // 缺席即「否」,省掉的字段能明显缩小提示词
  if (field.required) {
    item.required = true;
  }
  if (field.bindable) {
    item.bindable = true;
  }
  if (field.options?.length) {
    item.enumOptions = field.options.map((option) => ({
      label: option.label,
      value: option.value,
    }));
  }
  return item;
}