@skopon-cool/form-sdk 0.6.3 → 0.6.5

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.
Files changed (43) hide show
  1. package/dist/adapter/a2uiAdapter.d.ts +1 -1
  2. package/dist/adapter/a2uiAdapter.d.ts.map +1 -1
  3. package/dist/adapter/formAssetOptions.d.ts +3 -0
  4. package/dist/adapter/formAssetOptions.d.ts.map +1 -0
  5. package/dist/adapter/formSchema.d.ts.map +1 -1
  6. package/dist/adapter/resolveSurface.d.ts +1 -1
  7. package/dist/adapter/resolveSurface.d.ts.map +1 -1
  8. package/dist/catalog/a2uiCustomCatalog.d.ts.map +1 -1
  9. package/dist/catalog/materialUploadContext.d.ts +24 -0
  10. package/dist/catalog/materialUploadContext.d.ts.map +1 -0
  11. package/dist/components/AskUserFormCard.d.ts +5 -0
  12. package/dist/components/AskUserFormCard.d.ts.map +1 -1
  13. package/dist/components/SkoponFormRenderer.d.ts +3 -0
  14. package/dist/components/SkoponFormRenderer.d.ts.map +1 -1
  15. package/dist/form-sdk.css +1 -1
  16. package/dist/index.d.ts +1 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +2170 -1928
  19. package/dist/submit/buildAnswerAgentBody.d.ts.map +1 -1
  20. package/dist/submit/intersectPayloadBlocksWithForm.d.ts +11 -2
  21. package/dist/submit/intersectPayloadBlocksWithForm.d.ts.map +1 -1
  22. package/dist/types/index.d.ts +35 -1
  23. package/dist/types/index.d.ts.map +1 -1
  24. package/package.json +1 -1
  25. package/src/adapter/a2uiAdapter.test.ts +4 -0
  26. package/src/adapter/a2uiAdapter.ts +58 -2
  27. package/src/adapter/formAssetOptions.ts +19 -0
  28. package/src/adapter/formSchema.ts +54 -0
  29. package/src/adapter/resolveSurface.ts +4 -1
  30. package/src/catalog/a2uiCustomCatalog.tsx +296 -46
  31. package/src/catalog/fileUploadPreview.test.tsx +77 -11
  32. package/src/catalog/materialUploadContext.tsx +43 -0
  33. package/src/catalog/moduleTabs.test.tsx +59 -0
  34. package/src/components/AskUserFormCard.tsx +33 -9
  35. package/src/components/AskUserFormCard.upload.test.tsx +66 -0
  36. package/src/components/SkoponFormRenderer.tsx +6 -0
  37. package/src/icons/FilePlaceholderIcon.tsx +2 -2
  38. package/src/index.ts +2 -0
  39. package/src/styles/index.css +295 -0
  40. package/src/submit/buildAnswerAgentBody.ts +2 -0
  41. package/src/submit/intersectPayloadBlocksWithForm.ts +52 -3
  42. package/src/submit/submit.test.ts +54 -0
  43. package/src/types/index.ts +37 -0
@@ -230,6 +230,210 @@
230
230
  justify-content: flex-end;
231
231
  }
232
232
 
233
+ .skopon-module-panel {
234
+ min-width: 0;
235
+ padding: var(--space-3) var(--space-2) var(--space-2);
236
+ }
237
+
238
+ .skopon-module-panel__description {
239
+ margin: 0 0 var(--space-3) !important;
240
+ }
241
+
242
+ .skopon-module-panel__grid {
243
+ display: grid;
244
+ grid-template-columns: repeat(2, minmax(0, 1fr));
245
+ gap: var(--space-3);
246
+ align-items: start;
247
+ }
248
+
249
+ .skopon-module-panel__field {
250
+ min-width: 0;
251
+ }
252
+
253
+ .skopon-module-panel__field .form-block-preview {
254
+ padding: 0;
255
+ }
256
+
257
+ .skopon-compact-upload {
258
+ display: grid;
259
+ grid-template-columns: 86px minmax(0, 1fr);
260
+ gap: 13px;
261
+ align-items: center;
262
+ min-width: 0;
263
+ padding: 12px;
264
+ border: 1px solid var(--color-border-subtle);
265
+ border-radius: 12px;
266
+ background: var(--color-surface);
267
+ }
268
+
269
+ .skopon-compact-upload--failed {
270
+ border-color: color-mix(in srgb, var(--color-error, #cf3030) 45%, var(--color-border-subtle));
271
+ background: color-mix(in srgb, var(--color-error, #cf3030) 7%, var(--color-surface));
272
+ }
273
+
274
+ .skopon-compact-upload__thumb {
275
+ position: relative;
276
+ display: grid;
277
+ place-items: center;
278
+ width: 86px;
279
+ height: 92px;
280
+ overflow: hidden;
281
+ border-radius: 9px;
282
+ color: var(--color-text-secondary);
283
+ background: linear-gradient(145deg, var(--color-bg-subtle), var(--color-border-subtle));
284
+ }
285
+
286
+ .skopon-compact-upload__thumb img {
287
+ width: 100%;
288
+ height: 100%;
289
+ object-fit: cover;
290
+ }
291
+
292
+ .skopon-compact-upload__state-mark {
293
+ position: absolute;
294
+ right: 6px;
295
+ bottom: 6px;
296
+ display: grid;
297
+ place-items: center;
298
+ width: 22px;
299
+ height: 22px;
300
+ border-radius: 999px;
301
+ color: var(--color-text-secondary);
302
+ background: color-mix(in srgb, var(--color-surface) 92%, transparent);
303
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.14);
304
+ font-size: 13px;
305
+ font-weight: 700;
306
+ }
307
+
308
+ .skopon-compact-upload--passed .skopon-compact-upload__state-mark,
309
+ .skopon-compact-upload--passed .skopon-compact-upload__status {
310
+ color: var(--color-success, #087a48);
311
+ }
312
+
313
+ .skopon-compact-upload--failed .skopon-compact-upload__state-mark,
314
+ .skopon-compact-upload--failed .skopon-compact-upload__status,
315
+ .skopon-compact-upload__error {
316
+ color: var(--color-error, #cf3030);
317
+ }
318
+
319
+ .skopon-compact-upload__meta {
320
+ display: flex;
321
+ flex-direction: column;
322
+ align-items: flex-start;
323
+ gap: 5px;
324
+ min-width: 0;
325
+ }
326
+
327
+ .skopon-compact-upload__meta > strong {
328
+ font-size: var(--font-size-sm);
329
+ font-weight: var(--font-weight-medium);
330
+ line-height: 1.4;
331
+ }
332
+
333
+ .skopon-compact-upload__status,
334
+ .skopon-compact-upload__error {
335
+ max-width: 100%;
336
+ overflow: hidden;
337
+ color: var(--color-text-muted);
338
+ font-size: var(--font-size-xs);
339
+ line-height: 1.4;
340
+ text-overflow: ellipsis;
341
+ }
342
+
343
+ .skopon-compact-upload__source {
344
+ display: inline-flex;
345
+ align-items: center;
346
+ min-height: 20px;
347
+ padding: 1px 7px;
348
+ border: 1px solid var(--color-border-subtle);
349
+ border-radius: 999px;
350
+ color: var(--color-text-secondary);
351
+ background: var(--color-bg-subtle);
352
+ font-size: 11px;
353
+ line-height: 1.4;
354
+ }
355
+
356
+ .skopon-compact-upload__source--asset_only {
357
+ color: var(--color-primary);
358
+ border-color: color-mix(in srgb, var(--color-primary) 24%, var(--color-border-subtle));
359
+ }
360
+
361
+ .skopon-compact-upload__source--upload_or_asset {
362
+ color: var(--color-text);
363
+ }
364
+
365
+ .skopon-compact-upload__meta .ant-btn {
366
+ min-height: 30px;
367
+ padding-inline: 11px;
368
+ border-radius: 8px;
369
+ }
370
+
371
+ .skopon-module-panel__guide {
372
+ display: flex;
373
+ align-items: center;
374
+ justify-content: space-between;
375
+ gap: var(--space-4);
376
+ margin-top: 14px;
377
+ padding: 13px 0 0;
378
+ border-top: 1px solid var(--color-border-subtle);
379
+ }
380
+
381
+ .skopon-module-panel__requirements .ant-typography {
382
+ margin-bottom: 0;
383
+ white-space: pre-line;
384
+ }
385
+
386
+ .skopon-module-panel__example-list {
387
+ display: flex;
388
+ flex-wrap: wrap;
389
+ gap: 6px;
390
+ margin-top: 4px;
391
+ }
392
+
393
+ .skopon-module-panel__example-list .ant-image,
394
+ .skopon-module-panel__example-list .ant-image-img {
395
+ width: 42px !important;
396
+ height: 42px !important;
397
+ border-radius: 7px;
398
+ }
399
+
400
+ .a2ui-surface.a2ui-container .a2ui-tabs-header.a2ui-tab-button {
401
+ min-height: 44px;
402
+ padding: 0 14px !important;
403
+ border-radius: 0 !important;
404
+ border-bottom: 2px solid transparent !important;
405
+ background: transparent !important;
406
+ color: var(--color-text-muted) !important;
407
+ font-size: var(--font-size-sm);
408
+ font-weight: 400;
409
+ }
410
+
411
+ .a2ui-surface.a2ui-container .a2ui-tabs-header.a2ui-tab-button.active {
412
+ border-bottom-color: var(--color-text) !important;
413
+ color: var(--color-text) !important;
414
+ font-weight: var(--font-weight-medium);
415
+ }
416
+
417
+ @media (max-width: 640px) {
418
+ .skopon-module-panel__grid {
419
+ grid-template-columns: repeat(2, minmax(0, 1fr));
420
+ }
421
+
422
+ .skopon-module-panel__guide {
423
+ align-items: flex-start;
424
+ flex-direction: column;
425
+ }
426
+
427
+ .skopon-compact-upload {
428
+ grid-template-columns: 72px minmax(0, 1fr);
429
+ }
430
+
431
+ .skopon-compact-upload__thumb {
432
+ width: 72px;
433
+ height: 82px;
434
+ }
435
+ }
436
+
233
437
  .ask-user-curl-card {
234
438
  display: flex;
235
439
  flex-direction: column;
@@ -617,3 +821,94 @@
617
821
  padding: 12px 0;
618
822
  color: var(--color-text-secondary, rgba(0, 0, 0, 0.45));
619
823
  }
824
+
825
+ .skopon-file-source-actions {
826
+ display: flex;
827
+ flex-wrap: wrap;
828
+ gap: 8px;
829
+ margin: 8px 0;
830
+ }
831
+
832
+ .skopon-asset-picker {
833
+ display: grid;
834
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
835
+ gap: 12px;
836
+ max-height: min(68vh, 620px);
837
+ overflow: auto;
838
+ padding: 2px;
839
+ }
840
+
841
+ .skopon-asset-picker__card {
842
+ display: flex;
843
+ flex-direction: column;
844
+ gap: 9px;
845
+ min-width: 0;
846
+ padding: 10px;
847
+ border: 1px solid #e5e7eb;
848
+ border-radius: 10px;
849
+ background: #fff;
850
+ transition: border-color 160ms ease, box-shadow 160ms ease;
851
+ }
852
+
853
+ .skopon-asset-picker__card:hover {
854
+ border-color: #91caff;
855
+ box-shadow: 0 2px 10px rgba(22, 119, 255, 0.1);
856
+ }
857
+
858
+ .skopon-asset-picker__media {
859
+ display: flex;
860
+ align-items: center;
861
+ justify-content: center;
862
+ width: 100%;
863
+ height: 150px;
864
+ overflow: hidden;
865
+ border-radius: 6px;
866
+ background: #f5f7fa;
867
+ }
868
+
869
+ .skopon-asset-picker__media .ant-image,
870
+ .skopon-asset-picker__media .ant-image-img {
871
+ display: block;
872
+ width: 100%;
873
+ height: 100%;
874
+ }
875
+
876
+ .skopon-asset-picker__media .ant-image-img {
877
+ object-fit: contain;
878
+ }
879
+
880
+ .skopon-asset-picker__placeholder {
881
+ display: flex;
882
+ justify-content: center;
883
+ align-items: center;
884
+ width: 100%;
885
+ height: 100%;
886
+ color: #8c8c8c;
887
+ }
888
+
889
+ .skopon-asset-picker__name {
890
+ overflow-wrap: anywhere;
891
+ min-height: 22px;
892
+ }
893
+
894
+ @media (max-width: 600px) {
895
+ .skopon-asset-picker {
896
+ grid-template-columns: repeat(2, minmax(0, 1fr));
897
+ gap: 8px;
898
+ }
899
+
900
+ .skopon-asset-picker__card {
901
+ padding: 8px;
902
+ }
903
+
904
+ .skopon-asset-picker__media {
905
+ height: 120px;
906
+ }
907
+ }
908
+
909
+ .skopon-selected-assets__row {
910
+ display: flex;
911
+ align-items: center;
912
+ justify-content: space-between;
913
+ gap: 8px;
914
+ }
@@ -24,8 +24,10 @@ function toBlocksJsonShape(
24
24
  return source
25
25
  }
26
26
  return {
27
+ ...(obj.schema === 'skopon.material-form/v2' ? { schema: obj.schema } : {}),
27
28
  title: typeof obj.title === 'string' ? obj.title : '',
28
29
  description: typeof obj.description === 'string' ? obj.description : '',
30
+ modules: Array.isArray(obj.modules) ? obj.modules : undefined,
29
31
  blocks: obj.blocks,
30
32
  }
31
33
  }
@@ -3,7 +3,8 @@ import { hasRenderableFormOptions } from '../adapter/formOptions'
3
3
  import type { FormBlock, FormSchema } from '../types/index'
4
4
  import { isDisplayBlockType, isInputBlockType } from '../types/index'
5
5
 
6
- export type PayloadBlocksDefinition = Pick<FormSchema, 'title' | 'description' | 'blocks'> & {
6
+ export type PayloadBlocksDefinition = Pick<FormSchema, 'title' | 'description' | 'modules' | 'blocks'> & {
7
+ schema?: 'skopon.material-form/v2'
7
8
  /**
8
9
  * ask 原始 blocks(normalize 前)。交集合并按此键集覆盖 form,
9
10
  * 避免 normalize 填入的空默认值误盖表单样式。
@@ -18,9 +19,24 @@ export interface PayloadBlocksIntersection {
18
19
  renderBlocks: FormBlock[]
19
20
  title: string
20
21
  description: string
22
+ modules?: FormSchema['modules']
21
23
  }
22
24
 
23
25
  const FORM_ANCHORED_KEYS = new Set(['type', 'name', 'id'])
26
+ const RUNTIME_SNAPSHOT_KEYS = new Set([
27
+ 'defaultValue',
28
+ 'fileReviewStatus',
29
+ 'fileReviewMessage',
30
+ 'fileRejectedUrls',
31
+ ])
32
+
33
+ export interface IntersectPayloadBlocksOptions {
34
+ /**
35
+ * snapshot:消息快照中的显式配置优先(兼容旧行为);
36
+ * latest:最新表单负责布局/文案/校验,只保留快照里的填写与审核运行态。
37
+ */
38
+ definitionPolicy?: 'snapshot' | 'latest'
39
+ }
24
40
 
25
41
  function isHiddenTextBlock(block: FormBlock): boolean {
26
42
  return block.type === 'text' && block.hidden === true
@@ -94,7 +110,27 @@ function mergeBlock(formBlock: FormBlock, rawPayloadBlock: FormBlock): FormBlock
94
110
  merged.type = formBlock.type
95
111
  merged.name = formBlock.name?.trim() || rawPayloadBlock.name
96
112
  merged.id = formBlock.id
97
- return normalizePayloadBlock(merged)
113
+ const normalized = normalizePayloadBlock(merged)
114
+ // 单 block normalize 没有 modules 上下文,会把合法 moduleId 当成悬空引用清掉。
115
+ normalized.moduleId = merged.moduleId
116
+ return normalized
117
+ }
118
+
119
+ function mergeBlockWithLatestDefinition(
120
+ formBlock: FormBlock,
121
+ rawPayloadBlock: FormBlock,
122
+ ): FormBlock {
123
+ const merged: FormBlock = { ...formBlock }
124
+ for (const key of Object.keys(rawPayloadBlock) as (keyof FormBlock)[]) {
125
+ if (!RUNTIME_SNAPSHOT_KEYS.has(key)) continue
126
+ if (!Object.prototype.hasOwnProperty.call(rawPayloadBlock, key)) continue
127
+ const value = rawPayloadBlock[key]
128
+ if (value === undefined) continue
129
+ Object.assign(merged, { [key]: value })
130
+ }
131
+ const normalized = normalizePayloadBlock(merged)
132
+ normalized.moduleId = merged.moduleId
133
+ return normalized
98
134
  }
99
135
 
100
136
  /** 解析 ask_user payload 为 blocksJson 形(title / description / blocks) */
@@ -108,12 +144,15 @@ export function parsePayloadBlocksJson(payload: unknown): PayloadBlocksDefinitio
108
144
  const normalized = normalizeFormDefinition({
109
145
  title: typeof obj.title === 'string' ? obj.title : '',
110
146
  description: typeof obj.description === 'string' ? obj.description : '',
147
+ modules: Array.isArray(obj.modules) ? obj.modules as FormSchema['modules'] : undefined,
111
148
  blocks: rawBlocks,
112
149
  jsonSchema: {},
113
150
  })
114
151
  return {
152
+ schema: obj.schema === 'skopon.material-form/v2' ? obj.schema : undefined,
115
153
  title: normalized.title,
116
154
  description: normalized.description,
155
+ modules: normalized.modules,
117
156
  blocks: normalized.blocks,
118
157
  rawBlocks,
119
158
  }
@@ -126,7 +165,9 @@ export function parsePayloadBlocksJson(payload: unknown): PayloadBlocksDefinitio
126
165
  export function intersectPayloadBlocksWithForm(
127
166
  payloadDef: PayloadBlocksDefinition,
128
167
  formDefinition: FormSchema | undefined,
168
+ options?: IntersectPayloadBlocksOptions,
129
169
  ): PayloadBlocksIntersection {
170
+ const definitionPolicy = options?.definitionPolicy ?? 'snapshot'
130
171
  const formByName = new Map<string, FormBlock>()
131
172
  for (const block of formDefinition?.blocks ?? []) {
132
173
  if (isInputBlockType(block.type) && block.name?.trim()) {
@@ -144,7 +185,11 @@ export function intersectPayloadBlocksWithForm(
144
185
  const formBlock = formByName.get(name)
145
186
  if (formBlock) {
146
187
  const rawPayloadBlock = resolveRawInputBlock(payloadDef, name, payloadBlock)
147
- matchedBlocks.push(mergeBlock(formBlock, rawPayloadBlock))
188
+ matchedBlocks.push(
189
+ definitionPolicy === 'latest'
190
+ ? mergeBlockWithLatestDefinition(formBlock, rawPayloadBlock)
191
+ : mergeBlock(formBlock, rawPayloadBlock),
192
+ )
148
193
  } else {
149
194
  extraBlocks.push(normalizePayloadBlock(payloadBlock))
150
195
  }
@@ -156,6 +201,10 @@ export function intersectPayloadBlocksWithForm(
156
201
  renderBlocks: buildRenderBlocks(payloadDef, matchedBlocks),
157
202
  title: payloadDef.title?.trim() || formDefinition?.title?.trim() || '',
158
203
  description: payloadDef.description?.trim() || formDefinition?.description?.trim() || '',
204
+ modules:
205
+ definitionPolicy === 'latest'
206
+ ? (formDefinition?.modules?.length ? formDefinition.modules : payloadDef.modules)
207
+ : (payloadDef.modules?.length ? payloadDef.modules : formDefinition?.modules),
159
208
  }
160
209
  }
161
210
 
@@ -262,6 +262,60 @@ describe('intersectPayloadBlocksWithForm', () => {
262
262
  expect(result.matchedBlocks[0]?.defaultValue).toBe('hello')
263
263
  })
264
264
 
265
+ it('uses latest form presentation while preserving snapshot runtime state', () => {
266
+ const form: FormSchema = {
267
+ title: 'Latest Form',
268
+ description: '',
269
+ modules: [{ id: 'latest-person', label: '人物(新版)' }],
270
+ blocks: [{
271
+ id: 'latest-file',
272
+ type: 'file',
273
+ name: 'image01',
274
+ label: '新版人物图',
275
+ moduleId: 'latest-person',
276
+ help: '新版说明',
277
+ fileReviewCriteria: '新版审核标准',
278
+ fileExampleUrls: ['https://example.com/latest.png'],
279
+ }],
280
+ jsonSchema: {},
281
+ }
282
+ const snapshot = parsePayloadBlocksJson({
283
+ title: '当前任务素材',
284
+ modules: [{ id: 'old-person', label: '人物(旧版)' }],
285
+ blocks: [{
286
+ id: 'old-file',
287
+ type: 'file',
288
+ name: 'image01',
289
+ label: '旧版人物图',
290
+ moduleId: 'old-person',
291
+ help: '旧版说明',
292
+ fileReviewCriteria: '旧版审核标准',
293
+ defaultValue: ['https://example.com/uploaded.png'],
294
+ fileReviewStatus: 'failed',
295
+ fileReviewMessage: '人物模糊',
296
+ fileRejectedUrls: ['https://example.com/rejected.png'],
297
+ }],
298
+ })!
299
+
300
+ const result = intersectPayloadBlocksWithForm(snapshot, form, {
301
+ definitionPolicy: 'latest',
302
+ })
303
+
304
+ expect(result.modules).toEqual(form.modules)
305
+ expect(result.matchedBlocks[0]).toMatchObject({
306
+ id: 'latest-file',
307
+ label: '新版人物图',
308
+ moduleId: 'latest-person',
309
+ help: '新版说明',
310
+ fileReviewCriteria: '新版审核标准',
311
+ fileExampleUrls: ['https://example.com/latest.png'],
312
+ defaultValue: ['https://example.com/uploaded.png'],
313
+ fileReviewStatus: 'failed',
314
+ fileReviewMessage: '人物模糊',
315
+ fileRejectedUrls: ['https://example.com/rejected.png'],
316
+ })
317
+ })
318
+
265
319
  it('keeps form caseFilter when ask omits caseFilter key', () => {
266
320
  const form: FormSchema = {
267
321
  title: 'Form',
@@ -20,6 +20,16 @@ export type A2uiComponentName =
20
20
  | 'SkoponSelect'
21
21
  | 'SkoponResumeSelect'
22
22
  | 'SkoponCaseSelect'
23
+ | 'SkoponModulePanel'
24
+
25
+ /** 表单分组模块。运行时以 Tab 展示,要求与示例统一放在模块底部。 */
26
+ export interface FormModule {
27
+ id: string
28
+ label: string
29
+ description?: string
30
+ reviewCriteria?: string
31
+ exampleUrls?: string[]
32
+ }
23
33
 
24
34
  export interface A2uiComponentNode {
25
35
  id: string
@@ -88,6 +98,14 @@ export interface FormBlockOption {
88
98
  label: string
89
99
  }
90
100
 
101
+ export interface FormAssetOption {
102
+ assetId: string
103
+ url: string
104
+ previewUrl?: string
105
+ label: string
106
+ assetType?: string
107
+ }
108
+
91
109
  export type FormConditionOperator = 'eq' | 'ne' | 'empty' | 'notEmpty'
92
110
 
93
111
  export interface FormBlockCondition {
@@ -165,6 +183,21 @@ export interface FormBlock {
165
183
  fileExampleUrls?: string[]
166
184
  /** 制作模板的逐字段审核标准;由工作流读取,不能使用提交值覆盖。 */
167
185
  fileReviewCriteria?: string
186
+ /** 是否需要由工作流审核上传内容;素材库已审核素材可以关闭。 */
187
+ fileReviewRequired?: boolean
188
+ /** 物料采集来源模式,由 skopon-admin 环节配置。 */
189
+ fileSourceMode?: 'upload_required' | 'upload_or_asset' | 'asset_only'
190
+ /** 通用收集流从 Footage 为当前素材位查询的候选;提交选中的素材 ID。 */
191
+ fileAssetOptions?: FormAssetOption[]
192
+ /** 对应 Footage 同步素材位的限定 key。 */
193
+ assetSlotKey?: string
194
+ assetType?: 'image' | 'video' | 'audio' | 'text'
195
+ /** 运行时素材审核状态;用于返工表单回显,不作为用户提交值。 */
196
+ fileReviewStatus?: 'passed' | 'failed'
197
+ /** 运行时素材审核说明;failed 时通常为不合格原因。 */
198
+ fileReviewMessage?: string
199
+ /** 审核未通过的历史文件,仅作错误态回显,不计入有效提交。 */
200
+ fileRejectedUrls?: string[]
168
201
  defaultValue?: string | string[] | boolean
169
202
  /** 派生 JSON Schema 时写入 property.$comment */
170
203
  jsonSchemaDescription?: string
@@ -195,11 +228,14 @@ export interface FormBlock {
195
228
  /** 案例单选:列表筛选条件(持久化进 block json) */
196
229
  caseFilter?: FormCaseFilter
197
230
  condition?: FormBlockCondition
231
+ /** 所属模块;命中 FormSchema.modules.id 时在对应 Tab 中展示。 */
232
+ moduleId?: string
198
233
  }
199
234
 
200
235
  export interface FormSchema {
201
236
  title?: string
202
237
  description?: string
238
+ modules?: FormModule[]
203
239
  blocks: FormBlock[]
204
240
  jsonSchema: FormJsonSchema
205
241
  }
@@ -240,6 +276,7 @@ export function supportsDefaultValue(type: FormBlockType): boolean {
240
276
  export interface FormDefinitionPayload {
241
277
  title?: string
242
278
  description?: string
279
+ modules?: FormModule[]
243
280
  blocks?: FormBlock[]
244
281
  a2ui?: A2uiSurfaceDoc
245
282
  json_schema?: FormJsonSchema