@skopon-cool/form-sdk 0.4.5 → 0.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -25,8 +25,10 @@ export declare function intersectPayloadBlocksWithForm(payloadDef: PayloadBlocks
25
25
  export declare function extractExtraBlockValues(extraBlocks: FormBlock[]): Record<string, unknown>;
26
26
  /** payload 是否含至少一个具 name 的 input 块(可用于卡片 fallback) */
27
27
  export declare function payloadHasInputBlocks(payloadDef: PayloadBlocksDefinition): boolean;
28
- /** 交集为空时 fallback:payload 中可上屏的 blocks(layout + input) */
28
+ /** 交集为空时 fallback:payload 中可上屏的 blocks(layout + 非 hidden input) */
29
29
  export declare function getPayloadRenderableBlocks(payloadDef: PayloadBlocksDefinition): FormBlock[];
30
- /** payload 中所有 input 块 field name(fallback 提交用) */
30
+ /** payload 中所有 input 块 field name(fallback 提交用,含 hidden) */
31
31
  export declare function getPayloadInputFieldNames(payloadDef: PayloadBlocksDefinition): string[];
32
+ /** payload 中 hidden text 的默认提交值(fallback 不上屏时仍需写入 answer) */
33
+ export declare function getPayloadHiddenBlockValues(payloadDef: PayloadBlocksDefinition): Record<string, unknown>;
32
34
  //# sourceMappingURL=intersectPayloadBlocksWithForm.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"intersectPayloadBlocksWithForm.d.ts","sourceRoot":"","sources":["../../src/submit/intersectPayloadBlocksWithForm.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAG3D,MAAM,MAAM,uBAAuB,GAAG,IAAI,CAAC,UAAU,EAAE,OAAO,GAAG,aAAa,GAAG,QAAQ,CAAC,GAAG;IAC3F;;;OAGG;IACH,SAAS,CAAC,EAAE,SAAS,EAAE,CAAA;CACxB,CAAA;AAED,MAAM,WAAW,yBAAyB;IACxC,aAAa,EAAE,SAAS,EAAE,CAAA;IAC1B,WAAW,EAAE,SAAS,EAAE,CAAA;IACxB,0DAA0D;IAC1D,YAAY,EAAE,SAAS,EAAE,CAAA;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;CACpB;AAuED,uEAAuE;AACvE,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,uBAAuB,GAAG,IAAI,CAmBvF;AAED;;;GAGG;AACH,wBAAgB,8BAA8B,CAC5C,UAAU,EAAE,uBAAuB,EACnC,cAAc,EAAE,UAAU,GAAG,SAAS,GACrC,yBAAyB,CA+B3B;AA+BD,2CAA2C;AAC3C,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CASzF;AAED,sDAAsD;AACtD,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,uBAAuB,GAAG,OAAO,CAIlF;AAED,0DAA0D;AAC1D,wBAAgB,0BAA0B,CAAC,UAAU,EAAE,uBAAuB,GAAG,SAAS,EAAE,CAK3F;AAED,mDAAmD;AACnD,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,uBAAuB,GAAG,MAAM,EAAE,CAIvF"}
1
+ {"version":3,"file":"intersectPayloadBlocksWithForm.d.ts","sourceRoot":"","sources":["../../src/submit/intersectPayloadBlocksWithForm.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAG3D,MAAM,MAAM,uBAAuB,GAAG,IAAI,CAAC,UAAU,EAAE,OAAO,GAAG,aAAa,GAAG,QAAQ,CAAC,GAAG;IAC3F;;;OAGG;IACH,SAAS,CAAC,EAAE,SAAS,EAAE,CAAA;CACxB,CAAA;AAED,MAAM,WAAW,yBAAyB;IACxC,aAAa,EAAE,SAAS,EAAE,CAAA;IAC1B,WAAW,EAAE,SAAS,EAAE,CAAA;IACxB,0DAA0D;IAC1D,YAAY,EAAE,SAAS,EAAE,CAAA;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;CACpB;AA8ED,uEAAuE;AACvE,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,uBAAuB,GAAG,IAAI,CAmBvF;AAED;;;GAGG;AACH,wBAAgB,8BAA8B,CAC5C,UAAU,EAAE,uBAAuB,EACnC,cAAc,EAAE,UAAU,GAAG,SAAS,GACrC,yBAAyB,CA+B3B;AA+BD,2CAA2C;AAC3C,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CASzF;AAED,sDAAsD;AACtD,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,uBAAuB,GAAG,OAAO,CAIlF;AAED,mEAAmE;AACnE,wBAAgB,0BAA0B,CAAC,UAAU,EAAE,uBAAuB,GAAG,SAAS,EAAE,CAO3F;AAED,4DAA4D;AAC5D,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,uBAAuB,GAAG,MAAM,EAAE,CAIvF;AAED,6DAA6D;AAC7D,wBAAgB,2BAA2B,CACzC,UAAU,EAAE,uBAAuB,GAClC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CASzB"}
@@ -31,9 +31,17 @@ export interface FormBlockOption {
31
31
  value: string;
32
32
  label: string;
33
33
  }
34
+ export type FormConditionOperator = 'eq' | 'ne' | 'empty' | 'notEmpty';
35
+ export interface FormBlockCondition {
36
+ fieldName: string;
37
+ operator: FormConditionOperator;
38
+ value?: string;
39
+ }
34
40
  /** 简历多选组件:渲染时用于 /univ/resume/search 的筛选条件 */
35
41
  export interface FormResumeFilter {
36
42
  names?: string[];
43
+ /** Agent 类型(编辑器辅助,运行时不用) */
44
+ agentKind?: string;
37
45
  agentUniqueIds?: string[];
38
46
  resumeUniqueIds?: string[];
39
47
  pageSize?: number;
@@ -70,6 +78,12 @@ export interface FormBlock {
70
78
  fileMinCount?: number;
71
79
  fileMaxCount?: number;
72
80
  defaultValue?: string | string[] | boolean;
81
+ /** 派生 JSON Schema 时写入 property.$comment */
82
+ jsonSchemaDescription?: string;
83
+ /** 是否隐藏(仅 text 块;隐藏时仍提交) */
84
+ hidden?: boolean;
85
+ /** 是否需要 LLM 处理(持久化进 block json) */
86
+ needLLM?: boolean;
73
87
  /** 简历多选:是否显示「换一批」 */
74
88
  resumeEnableRefresh?: boolean;
75
89
  /** 简历多选:最少选中数量,配置值 ≥ 1 */
@@ -86,6 +100,7 @@ export interface FormBlock {
86
100
  caseMaxCount?: number;
87
101
  /** 案例单选:列表筛选条件(持久化进 block json) */
88
102
  caseFilter?: FormCaseFilter;
103
+ condition?: FormBlockCondition;
89
104
  }
90
105
  export interface FormSchema {
91
106
  title?: string;
@@ -96,6 +111,9 @@ export interface FormSchema {
96
111
  export declare function isMediaBlockType(type: FormBlockType): boolean;
97
112
  export declare function isLayoutBlockType(type: FormBlockType): boolean;
98
113
  export declare function isInputBlockType(type: FormBlockType): boolean;
114
+ export declare function isDisplayBlockType(type: FormBlockType): boolean;
115
+ export declare function isOptionsBlockType(type: FormBlockType): boolean;
116
+ export declare function supportsDefaultValue(type: FormBlockType): boolean;
99
117
  export interface FormDefinitionPayload {
100
118
  title?: string;
101
119
  description?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAEjD,MAAM,MAAM,iBAAiB,GACzB,QAAQ,GACR,KAAK,GACL,MAAM,GACN,OAAO,GACP,OAAO,GACP,aAAa,GACb,WAAW,GACX,UAAU,GACV,cAAc,GACd,eAAe,GACf,YAAY,GACZ,aAAa,GACb,cAAc,GACd,oBAAoB,GACpB,kBAAkB,CAAA;AAEtB,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,iBAAiB,GAAG,MAAM,CAAA;IACrC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IACnB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;CACxB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,EAAE,iBAAiB,EAAE,CAAA;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACnC,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;QAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAA;CAC1E;AAED,eAAO,MAAM,qBAAqB,EAAG,MAAe,CAAA;AAEpD,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAEpD,MAAM,MAAM,aAAa,GACrB,SAAS,GACT,WAAW,GACX,MAAM,GACN,UAAU,GACV,OAAO,GACP,QAAQ,GACR,QAAQ,GACR,aAAa,GACb,OAAO,GACP,UAAU,GACV,QAAQ,GACR,KAAK,GACL,KAAK,GACL,UAAU,GACV,MAAM,GACN,MAAM,GACN,oBAAoB,GACpB,kBAAkB;AACpB,4CAA4C;GAC1C,mBAAmB,GACnB,OAAO,GACP,OAAO,GACP,OAAO,CAAA;AAEX,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAA;AAE1E,MAAM,MAAM,uBAAuB,GAC/B,OAAO,GACP,OAAO,GACP,OAAO,GACP,MAAM,GACN,aAAa,GACb,UAAU,CAAA;AAEd,eAAO,MAAM,gBAAgB,EAAE,aAAa,EAM3C,CAAA;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd;AAED,6CAA6C;AAC7C,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;IACzB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,yCAAyC;AACzC,MAAM,WAAW,cAAc;IAC7B,4BAA4B;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,uCAAuC;IACvC,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,oBAAoB;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,oBAAoB;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,6BAA6B;IAC7B,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,aAAa,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,OAAO,CAAC,EAAE,eAAe,EAAE,CAAA;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;IACpB,SAAS,CAAC,EAAE,aAAa,CAAA;IACzB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;IAC1B,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC/B,mBAAmB,CAAC,EAAE,uBAAuB,CAAA;IAC7C,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAA;IAC1C,qBAAqB;IACrB,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B,0BAA0B;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,0BAA0B;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,mCAAmC;IACnC,YAAY,CAAC,EAAE,gBAAgB,CAAA;IAC/B,qBAAqB;IACrB,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,0BAA0B;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,0BAA0B;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,mCAAmC;IACnC,UAAU,CAAC,EAAE,cAAc,CAAA;CAC5B;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,SAAS,EAAE,CAAA;IACnB,UAAU,EAAE,cAAc,CAAA;CAC3B;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAE7D;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAE9D;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAE7D;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,CAAC,EAAE,SAAS,EAAE,CAAA;IACpB,IAAI,CAAC,EAAE,cAAc,CAAA;IACrB,WAAW,CAAC,EAAE,cAAc,CAAA;IAC5B,UAAU,CAAC,EAAE,cAAc,CAAA;CAC5B;AAED,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,MAAM,CAAA;IACpB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,OAAO,CAAA;IACjB,cAAc,CAAC,EAAE,UAAU,CAAA;IAC3B,IAAI,CAAC,EAAE,cAAc,CAAA;CACtB;AAED,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.4.5",
6
+ "version": "0.4.7",
7
7
  "description": "Skopon form rendering SDK (A2UI + form_definition) with submit helpers",
8
8
  "type": "module",
9
9
  "sideEffects": [
@@ -88,6 +88,31 @@ describe('a2uiAdapter blocks <-> surface', () => {
88
88
  expect(imgNode?.mediaSize).toBe('large')
89
89
  })
90
90
 
91
+ it('blocksToA2ui omits hidden text controls but keeps dataModel defaults', () => {
92
+ const doc = blocksToA2ui(
93
+ {
94
+ title: '',
95
+ description: '',
96
+ blocks: [
97
+ { id: 'b-title', type: 'text', name: 'title', label: '标题', defaultValue: 'shown' },
98
+ {
99
+ id: 'b-hint',
100
+ type: 'text',
101
+ name: 'agent_hint',
102
+ label: '提示',
103
+ defaultValue: 'secret',
104
+ hidden: true,
105
+ },
106
+ ],
107
+ },
108
+ { includeHeader: false },
109
+ )
110
+ expect(doc.components.some((c) => c.id === 'b-hint')).toBe(false)
111
+ expect(doc.components.some((c) => c.id === 'b-title')).toBe(true)
112
+ expect(doc.dataModel?.agent_hint).toBe('secret')
113
+ expect(doc.dataModel?.title).toBe('shown')
114
+ })
115
+
91
116
  it('blocksToA2ui preserves select vs multiselect picker types', () => {
92
117
  const doc = blocksToA2ui(
93
118
  {
@@ -147,6 +147,20 @@ function blockToComponent(block: FormBlock): {
147
147
  return { node: { id, component: 'Text', text: label, variant: 'body' } }
148
148
 
149
149
  case 'text':
150
+ // 隐藏 text:不生成可见控件,仍写入 dataModel 默认值供提交
151
+ if (block.hidden) {
152
+ return name
153
+ ? { node: null, dataKey: name, dataValue: defaultValueForBlock(block) }
154
+ : { node: null }
155
+ }
156
+ return withData({
157
+ id,
158
+ component: 'TextField',
159
+ label,
160
+ variant: 'shortText',
161
+ ...(block.placeholder ? { placeholder: block.placeholder } : {}),
162
+ value: { path },
163
+ })
150
164
  case 'tel':
151
165
  case 'url':
152
166
  return withData({
@@ -312,10 +326,11 @@ export function blocksToA2ui(
312
326
 
313
327
  for (const block of definition.blocks ?? []) {
314
328
  const { node, dataKey, dataValue } = blockToComponent(block)
329
+ // hidden 等无节点块仍可写入 dataModel,保证「不展示仍提交」
330
+ if (dataKey) dataModel[dataKey] = dataValue
315
331
  if (!node) continue
316
332
  components.push(node)
317
333
  childIds.push(node.id)
318
- if (dataKey) dataModel[dataKey] = dataValue
319
334
  }
320
335
 
321
336
  components.unshift({ id: ROOT_ID, component: 'Column', children: childIds })
@@ -2,7 +2,11 @@
2
2
  * @vitest-environment node
3
3
  */
4
4
  import { describe, it, expect } from 'vitest'
5
- import { coerceToggleValue, normalizeFormDefinition } from './formSchema'
5
+ import {
6
+ coerceToggleValue,
7
+ normalizeFormDefinition,
8
+ blocksToJsonSchema,
9
+ } from './formSchema'
6
10
  import { mapFormDefinitionFromDto } from './resolveSurface'
7
11
 
8
12
  describe('coerceToggleValue', () => {
@@ -38,27 +42,104 @@ describe('normalizeFormDefinition toggle default', () => {
38
42
  expect(def.blocks[0]?.defaultValue).toBe(true)
39
43
  })
40
44
 
41
- it('preserves jsonSchema from raw definition', () => {
42
- const schema = { type: 'object', properties: { title: { type: 'string' } } }
45
+ it('regenerates jsonSchema from blocks', () => {
46
+ const def = normalizeFormDefinition({
47
+ title: 'Title',
48
+ description: 'Desc',
49
+ blocks: [
50
+ {
51
+ id: 'b1',
52
+ type: 'text',
53
+ name: 'title',
54
+ label: '标题',
55
+ required: true,
56
+ },
57
+ ],
58
+ jsonSchema: { type: 'object', properties: { stale: { type: 'number' } } },
59
+ })
60
+ expect(def.jsonSchema).toEqual(
61
+ blocksToJsonSchema(def.blocks, 'Title', 'Desc'),
62
+ )
63
+ expect(def.jsonSchema).toMatchObject({
64
+ type: 'object',
65
+ properties: { title: { type: 'string' } },
66
+ required: ['title'],
67
+ })
68
+ })
69
+ })
70
+
71
+ describe('normalizeFormDefinition preserves needLLM and editor fields', () => {
72
+ it('keeps needLLM, hidden, condition, jsonSchemaDescription after normalize', () => {
73
+ const def = normalizeFormDefinition({
74
+ title: '',
75
+ description: '',
76
+ blocks: [
77
+ {
78
+ id: 'b1',
79
+ type: 'text',
80
+ name: 'prompt',
81
+ label: 'Prompt',
82
+ needLLM: true,
83
+ hidden: true,
84
+ jsonSchemaDescription: 'for agent',
85
+ condition: { fieldName: 'other', operator: 'eq', value: '1' },
86
+ },
87
+ {
88
+ id: 'b0',
89
+ type: 'text',
90
+ name: 'other',
91
+ label: 'Other',
92
+ },
93
+ ],
94
+ jsonSchema: {},
95
+ })
96
+ const block = def.blocks.find((item) => item.name === 'prompt')
97
+ expect(block?.needLLM).toBe(true)
98
+ expect(block?.hidden).toBe(true)
99
+ expect(block?.jsonSchemaDescription).toBe('for agent')
100
+ expect(block?.condition).toEqual({
101
+ fieldName: 'other',
102
+ operator: 'eq',
103
+ value: '1',
104
+ })
105
+ })
106
+
107
+ it('keeps resumeFilter.agentKind', () => {
43
108
  const def = normalizeFormDefinition({
44
109
  title: '',
45
110
  description: '',
46
- blocks: [],
47
- jsonSchema: schema,
111
+ blocks: [
112
+ {
113
+ id: 'b1',
114
+ type: 'resume_multiselect',
115
+ name: 'partners',
116
+ resumeFilter: {
117
+ agentKind: 'management',
118
+ names: [],
119
+ agentUniqueIds: [],
120
+ resumeUniqueIds: [],
121
+ pageSize: 20,
122
+ },
123
+ },
124
+ ],
125
+ jsonSchema: {},
48
126
  })
49
- expect(def.jsonSchema).toEqual(schema)
127
+ expect(def.blocks[0]?.resumeFilter?.agentKind).toBe('management')
50
128
  })
51
129
  })
52
130
 
53
131
  describe('mapFormDefinitionFromDto jsonSchema', () => {
54
- it('reads json_schema snake_case from DTO', () => {
55
- const schema = { type: 'object' }
132
+ it('regenerates jsonSchema from DTO blocks', () => {
56
133
  const def = mapFormDefinitionFromDto({
57
134
  title: 'T',
58
135
  blocks: [{ id: 'b1', type: 'text', name: 'x', label: 'X' }],
59
- json_schema: schema,
136
+ json_schema: { type: 'object', properties: { stale: { type: 'number' } } },
137
+ })
138
+ expect(def?.jsonSchema).toMatchObject({
139
+ type: 'object',
140
+ title: 'T',
141
+ properties: { x: { type: 'string' } },
60
142
  })
61
- expect(def?.jsonSchema).toEqual(schema)
62
143
  })
63
144
  })
64
145
 
@@ -1,5 +1,12 @@
1
- import type { FormBlock, FormBlockType, FormCaseFilter, FormResumeFilter, FormJsonSchema, FormSchema } from '../types/index'
2
- import { isInputBlockType, isMediaBlockType } from '../types/index'
1
+ import type {
2
+ FormBlock,
3
+ FormBlockType,
4
+ FormCaseFilter,
5
+ FormResumeFilter,
6
+ FormJsonSchema,
7
+ FormSchema,
8
+ } from '../types/index'
9
+ import { isInputBlockType, isMediaBlockType, isOptionsBlockType } from '../types/index'
3
10
  import { generateId } from './id'
4
11
  import {
5
12
  filterFileAcceptExtensionsForTypes,
@@ -32,7 +39,7 @@ function normalizeFileUploadCount(raw: unknown, fallback = 1): number {
32
39
  return Math.max(0, Math.floor(num))
33
40
  }
34
41
 
35
- function normalizeMultiselectCount(raw: unknown, fallback = 1): number {
42
+ export function normalizeMultiselectCount(raw: unknown, fallback = 1): number {
36
43
  const num = typeof raw === 'number' ? raw : Number(raw)
37
44
  if (!Number.isFinite(num)) return fallback
38
45
  return Math.max(1, Math.floor(num))
@@ -42,16 +49,27 @@ function normalizeDefaultValue(
42
49
  type: FormBlockType,
43
50
  raw: FormBlock['defaultValue'],
44
51
  ): FormBlock['defaultValue'] {
45
- if (raw === undefined) return undefined
46
- if (type === 'toggle') return coerceToggleValue(raw)
47
- if (type === 'multiselect' || type === 'checkbox' || type === 'resume_multiselect' || type === 'case_multiselect') {
48
- if (Array.isArray(raw)) return raw.map(String)
49
- if (typeof raw === 'string' && raw) return [raw]
52
+ if (raw === undefined || raw === null) return undefined
53
+ if (type === 'toggle') {
54
+ return coerceToggleValue(raw)
55
+ }
56
+ if (
57
+ type === 'multiselect' ||
58
+ type === 'checkbox' ||
59
+ type === 'resume_multiselect' ||
60
+ type === 'case_multiselect'
61
+ ) {
62
+ if (Array.isArray(raw)) {
63
+ const values = raw.map(String).map((item) => item.trim()).filter(Boolean)
64
+ return values
65
+ }
66
+ if (typeof raw === 'string' && raw.trim()) return [raw.trim()]
50
67
  return []
51
68
  }
52
69
  if (typeof raw === 'boolean') return raw ? 'true' : 'false'
53
70
  if (Array.isArray(raw)) return raw.map(String)
54
- return String(raw)
71
+ const trimmed = String(raw).trim()
72
+ return trimmed || undefined
55
73
  }
56
74
 
57
75
  /** n8n 等可能把 flowUniqueIds 发成 JSON 数组字符串;解析失败则 []。 */
@@ -100,8 +118,13 @@ function normalizeResumeFilter(raw: FormResumeFilter | undefined): FormResumeFil
100
118
  Number.isFinite(pageSizeRaw) && pageSizeRaw >= 1 && pageSizeRaw <= 100
101
119
  ? Math.floor(pageSizeRaw)
102
120
  : 20
121
+ const agentKind =
122
+ typeof raw?.agentKind === 'string' && raw.agentKind.trim()
123
+ ? raw.agentKind.trim()
124
+ : undefined
103
125
  return {
104
126
  names: Array.isArray(raw?.names) ? raw!.names.map(String).filter(Boolean) : [],
127
+ agentKind,
105
128
  agentUniqueIds: Array.isArray(raw?.agentUniqueIds)
106
129
  ? raw!.agentUniqueIds.map(String).filter(Boolean)
107
130
  : [],
@@ -148,12 +171,22 @@ function normalizeBlock(raw: FormBlock): FormBlock {
148
171
  fileMinCount: type === 'file' ? normalizeFileUploadCount(migrated.fileMinCount, 1) : undefined,
149
172
  fileMaxCount: type === 'file' ? normalizeFileUploadCount(migrated.fileMaxCount, 1) : undefined,
150
173
  defaultValue: normalizeDefaultValue(type, migrated.defaultValue),
174
+ jsonSchemaDescription: migrated.jsonSchemaDescription?.trim() ?? '',
175
+ hidden: type === 'text' ? migrated.hidden === true : undefined,
176
+ needLLM: migrated.needLLM === true ? true : undefined,
151
177
  options: Array.isArray(migrated.options)
152
178
  ? migrated.options.map((opt, index) => ({
153
179
  value: opt.value?.trim() || `option_${index + 1}`,
154
180
  label: opt.label?.trim() || `选项 ${index + 1}`,
155
181
  }))
156
182
  : undefined,
183
+ condition: migrated.condition?.fieldName
184
+ ? {
185
+ fieldName: migrated.condition.fieldName.trim(),
186
+ operator: migrated.condition.operator ?? 'eq',
187
+ value: migrated.condition.value,
188
+ }
189
+ : undefined,
157
190
  resumeEnableRefresh:
158
191
  type === 'resume_multiselect' ? migrated.resumeEnableRefresh !== false : undefined,
159
192
  resumeMinCount:
@@ -181,12 +214,175 @@ function normalizeBlock(raw: FormBlock): FormBlock {
181
214
  }
182
215
  }
183
216
 
217
+ function getOptionValueSet(block: FormBlock): Set<string> {
218
+ return new Set((block.options ?? []).map((opt) => opt.value))
219
+ }
220
+
221
+ function buildMultiselectCountJsonSchema(
222
+ minCount: number | undefined,
223
+ maxCount: number | undefined,
224
+ ): Record<string, unknown> {
225
+ const schema: Record<string, unknown> = {
226
+ type: 'array',
227
+ items: { type: 'string' },
228
+ }
229
+ if (minCount !== undefined) {
230
+ schema.minItems = normalizeMultiselectCount(minCount, 1)
231
+ }
232
+ if (maxCount !== undefined) {
233
+ schema.maxItems = normalizeMultiselectCount(maxCount, 1)
234
+ }
235
+ return schema
236
+ }
237
+
238
+ export function resolveSchemaDefaultValue(block: FormBlock): unknown | undefined {
239
+ const normalized = normalizeDefaultValue(block.type, block.defaultValue)
240
+ if (normalized === undefined) return undefined
241
+
242
+ const optionValues = getOptionValueSet(block)
243
+
244
+ switch (block.type) {
245
+ case 'toggle':
246
+ return normalized as boolean
247
+ case 'number': {
248
+ const num = Number(normalized as string)
249
+ return Number.isNaN(num) ? undefined : num
250
+ }
251
+ case 'multiselect':
252
+ case 'checkbox': {
253
+ const values = (normalized as string[]).filter((item) => optionValues.has(item))
254
+ return values.length > 0 ? values : undefined
255
+ }
256
+ case 'select':
257
+ case 'radio': {
258
+ const value = normalized as string
259
+ return optionValues.has(value) ? value : undefined
260
+ }
261
+ case 'resume_multiselect':
262
+ case 'case_multiselect': {
263
+ const values = Array.isArray(normalized)
264
+ ? (normalized as string[]).map(String).filter(Boolean)
265
+ : []
266
+ return values.length > 0 ? values : undefined
267
+ }
268
+ default:
269
+ return normalized
270
+ }
271
+ }
272
+
273
+ function blockToJsonSchemaProperty(block: FormBlock): Record<string, unknown> | null {
274
+ const name = block.name?.trim()
275
+ if (!name || !isInputBlockType(block.type)) return null
276
+
277
+ let property: Record<string, unknown>
278
+
279
+ switch (block.type) {
280
+ case 'number':
281
+ property = { type: 'number' }
282
+ break
283
+ case 'toggle':
284
+ property = { type: 'boolean' }
285
+ break
286
+ case 'select':
287
+ case 'radio': {
288
+ property = { type: 'string' }
289
+ const enumValues = (block.options ?? []).map((opt) => opt.value).filter(Boolean)
290
+ if (enumValues.length > 0) property.enum = enumValues
291
+ break
292
+ }
293
+ case 'multiselect':
294
+ case 'checkbox': {
295
+ const enumValues = (block.options ?? []).map((opt) => opt.value).filter(Boolean)
296
+ property = {
297
+ type: 'array',
298
+ items:
299
+ enumValues.length > 0
300
+ ? { type: 'string', enum: enumValues }
301
+ : { type: 'string' },
302
+ }
303
+ break
304
+ }
305
+ case 'file': {
306
+ const minCount = normalizeFileUploadCount(block.fileMinCount, 1)
307
+ const maxCount = normalizeFileUploadCount(block.fileMaxCount, 1)
308
+ if (maxCount <= 1) {
309
+ property = { type: 'string' }
310
+ } else {
311
+ property = {
312
+ type: 'array',
313
+ items: { type: 'string' },
314
+ minItems: minCount,
315
+ maxItems: maxCount,
316
+ }
317
+ }
318
+ break
319
+ }
320
+ case 'resume_multiselect':
321
+ property = buildMultiselectCountJsonSchema(block.resumeMinCount, block.resumeMaxCount)
322
+ break
323
+ case 'case_multiselect':
324
+ property = buildMultiselectCountJsonSchema(block.caseMinCount, block.caseMaxCount)
325
+ break
326
+ case 'text':
327
+ property = { type: 'string' }
328
+ if (block.hidden) property['x-hidden'] = true
329
+ break
330
+ default:
331
+ property = { type: 'string' }
332
+ break
333
+ }
334
+
335
+ const comment = block.jsonSchemaDescription?.trim()
336
+ if (comment) property.$comment = comment
337
+
338
+ const defaultValue = resolveSchemaDefaultValue(block)
339
+ if (defaultValue !== undefined) property.default = defaultValue
340
+
341
+ return property
342
+ }
343
+
344
+ export function blocksToJsonSchema(
345
+ blocks: FormBlock[],
346
+ title?: string,
347
+ description?: string,
348
+ ): FormJsonSchema {
349
+ const properties: Record<string, Record<string, unknown>> = {}
350
+ const required: string[] = []
351
+
352
+ for (const block of blocks) {
353
+ const name = block.name?.trim()
354
+ if (!name || !isInputBlockType(block.type)) continue
355
+
356
+ const property = blockToJsonSchemaProperty(block)
357
+ if (!property) continue
358
+
359
+ properties[name] = property
360
+ if (block.required) required.push(name)
361
+ }
362
+
363
+ const schema: FormJsonSchema = {
364
+ type: 'object',
365
+ properties,
366
+ required,
367
+ additionalProperties: false,
368
+ }
369
+
370
+ const trimmedTitle = title?.trim()
371
+ const trimmedDescription = description?.trim()
372
+ if (trimmedTitle) schema.title = trimmedTitle
373
+ if (trimmedDescription) schema.description = trimmedDescription
374
+
375
+ return schema
376
+ }
377
+
184
378
  export function syncFormDefinition(definition: FormSchema): FormSchema {
379
+ const title = definition.title ?? ''
380
+ const description = definition.description ?? ''
185
381
  return {
186
- title: definition.title ?? '',
187
- description: definition.description ?? '',
382
+ title,
383
+ description,
188
384
  blocks: definition.blocks,
189
- jsonSchema: definition.jsonSchema ?? {},
385
+ jsonSchema: blocksToJsonSchema(definition.blocks, title, description),
190
386
  }
191
387
  }
192
388
 
@@ -199,7 +395,7 @@ export function normalizeFormDefinition(raw: FormSchema | undefined): FormSchema
199
395
  title: typeof raw.title === 'string' ? raw.title : '',
200
396
  description: typeof raw.description === 'string' ? raw.description : '',
201
397
  blocks,
202
- jsonSchema: normalizeJsonSchema(raw.jsonSchema),
398
+ jsonSchema: {},
203
399
  })
204
400
  }
205
401
 
@@ -267,7 +463,3 @@ export function createEmptyBlock(type: FormBlockType): FormBlock {
267
463
 
268
464
  return block
269
465
  }
270
-
271
- function isOptionsBlockType(type: FormBlockType): boolean {
272
- return type === 'select' || type === 'multiselect' || type === 'radio' || type === 'checkbox'
273
- }
@@ -13,7 +13,7 @@ import {
13
13
  } from '../submit/buildAnswerAgentBody'
14
14
  import {
15
15
  extractExtraBlockValues,
16
- getPayloadInputFieldNames,
16
+ getPayloadHiddenBlockValues,
17
17
  getPayloadRenderableBlocks,
18
18
  intersectPayloadBlocksWithForm,
19
19
  parsePayloadBlocksJson,
@@ -195,18 +195,29 @@ function AskUserFormCardBody({
195
195
  return []
196
196
  }, [intersection, usePayloadFallback, payloadDef])
197
197
 
198
- const extraValues = useMemo(
199
- () => (usePayloadFallback ? {} : extractExtraBlockValues(intersection?.extraBlocks ?? [])),
200
- [usePayloadFallback, intersection],
198
+ const extraValues = useMemo(() => {
199
+ if (usePayloadFallback && payloadDef) {
200
+ // fallback:hidden 不上屏,提交仍带默认值
201
+ return getPayloadHiddenBlockValues(payloadDef)
202
+ }
203
+ const fromExtra = extractExtraBlockValues(intersection?.extraBlocks ?? [])
204
+ // 交集命中但 surface 可能为空(仅 hidden)时,兜底写入默认值
205
+ const fromHiddenMatched = extractExtraBlockValues(
206
+ (intersection?.matchedBlocks ?? []).filter(
207
+ (block) => block.type === 'text' && block.hidden === true,
208
+ ),
209
+ )
210
+ return { ...fromHiddenMatched, ...fromExtra }
211
+ }, [usePayloadFallback, payloadDef, intersection])
212
+
213
+ const fieldNames = useMemo(
214
+ () =>
215
+ renderBlocks
216
+ .filter((block) => block.name?.trim())
217
+ .map((block) => block.name!.trim()),
218
+ [renderBlocks],
201
219
  )
202
220
 
203
- const fieldNames = useMemo(() => {
204
- if (usePayloadFallback && payloadDef) return getPayloadInputFieldNames(payloadDef)
205
- return renderBlocks
206
- .filter((block) => block.name?.trim())
207
- .map((block) => block.name!.trim())
208
- }, [usePayloadFallback, payloadDef, renderBlocks])
209
-
210
221
  const surfaceTitle = intersection?.title ?? payloadDef?.title ?? ''
211
222
  const surfaceDescription = intersection?.description ?? payloadDef?.description ?? ''
212
223
 
@@ -313,12 +324,13 @@ function AskUserFormCardBody({
313
324
  setSubmitting(true)
314
325
  try {
315
326
  const cardValues = rendererRef.current?.getValues(fieldNames) ?? {}
316
- const validationError = validateFormSubmitValues(renderBlocks, cardValues)
327
+ // extra/hidden 后合并:getValues 对未上屏字段可能为 undefined,不能覆盖默认值
328
+ const answer = { ...cardValues, ...extraValues }
329
+ const validationError = validateFormSubmitValues(renderBlocks, answer)
317
330
  if (validationError) {
318
331
  onNotifyRef.current?.('error', validationError.message)
319
332
  return
320
333
  }
321
- const answer = { ...cardValues, ...extraValues }
322
334
 
323
335
  if (submitMode === 'post') {
324
336
  const url = (callbackUrl ?? '').trim()