@skopon-cool/form-sdk 0.1.4 → 0.1.6

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 (48) hide show
  1. package/dist/adapter/a2uiAdapter.d.ts.map +1 -1
  2. package/dist/adapter/formSchema.d.ts.map +1 -1
  3. package/dist/blocks/{case_singleselect → case_multiselect}/adapter.d.ts +2 -2
  4. package/dist/blocks/case_multiselect/adapter.d.ts.map +1 -0
  5. package/dist/blocks/case_multiselect/index.d.ts +3 -0
  6. package/dist/blocks/case_multiselect/index.d.ts.map +1 -0
  7. package/dist/catalog/resumeSearchContext.d.ts +9 -0
  8. package/dist/catalog/resumeSearchContext.d.ts.map +1 -1
  9. package/dist/catalog/skoponCaseSelect.d.ts.map +1 -1
  10. package/dist/catalog/skoponResumeSelect.d.ts.map +1 -1
  11. package/dist/components/AskUserFormCard.d.ts.map +1 -1
  12. package/dist/components/CurlSubmitBlock.d.ts +4 -1
  13. package/dist/components/CurlSubmitBlock.d.ts.map +1 -1
  14. package/dist/components/SkoponA2uiStreamRenderer.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 +901 -852
  19. package/dist/submit/buildCurlStatement.d.ts +3 -1
  20. package/dist/submit/buildCurlStatement.d.ts.map +1 -1
  21. package/dist/submit/intersectPayloadBlocksWithForm.d.ts.map +1 -1
  22. package/dist/types/index.d.ts +11 -4
  23. package/dist/types/index.d.ts.map +1 -1
  24. package/package.json +1 -1
  25. package/src/adapter/a2uiAdapter.test.ts +37 -14
  26. package/src/adapter/a2uiAdapter.ts +1 -4
  27. package/src/adapter/formSchema.ts +49 -29
  28. package/src/blocks/case_multiselect/adapter.ts +90 -0
  29. package/src/blocks/case_multiselect/index.ts +14 -0
  30. package/src/blocks/registry.ts +2 -2
  31. package/src/catalog/resumeSearchContext.tsx +10 -0
  32. package/src/catalog/skoponCaseSelect.tsx +38 -13
  33. package/src/catalog/skoponResumeSelect.tsx +76 -10
  34. package/src/components/AskUserFormCard.tsx +3 -1
  35. package/src/components/CurlSubmitBlock.tsx +28 -7
  36. package/src/components/SkoponA2uiStreamRenderer.tsx +11 -4
  37. package/src/index.ts +6 -1
  38. package/src/styles/a2ui-preview.css +4 -4
  39. package/src/styles/index.css +148 -17
  40. package/src/submit/buildCurlStatement.ts +18 -23
  41. package/src/submit/intersectPayloadBlocksWithForm.ts +14 -2
  42. package/src/submit/submit.test.ts +66 -3
  43. package/src/types/index.ts +10 -3
  44. package/dist/blocks/case_singleselect/adapter.d.ts.map +0 -1
  45. package/dist/blocks/case_singleselect/index.d.ts +0 -3
  46. package/dist/blocks/case_singleselect/index.d.ts.map +0 -1
  47. package/src/blocks/case_singleselect/adapter.ts +0 -74
  48. package/src/blocks/case_singleselect/index.ts +0 -14
@@ -1,5 +1,5 @@
1
1
  export declare function buildCurlStatement(payload: unknown, callbackUrl?: string | null): string;
2
- /** 构建含行注释的 JSON bodyextra 段标注「额外字段(未在卡片展示)」 */
2
+ /** 构建合法 JSON body(card + extra 合并,便于 curl 直接执行) */
3
3
  export declare function buildAskUserCurlBodyJson(cardValues: Record<string, unknown>, extraValues: Record<string, unknown>): string;
4
4
  export interface BuildAskUserCurlStatementOptions {
5
5
  cardValues: Record<string, unknown>;
@@ -7,4 +7,6 @@ export interface BuildAskUserCurlStatementOptions {
7
7
  callbackUrl?: string | null;
8
8
  }
9
9
  export declare function buildAskUserCurlStatement({ cardValues, extraValues, callbackUrl, }: BuildAskUserCurlStatementOptions): string;
10
+ /** 从 ask_user payload(blocksJson)生成向 callback 提交返回字段的 curl */
11
+ export declare function buildAskUserSubmitCurlFromPayload(payload: unknown, callbackUrl?: string | null): string;
10
12
  //# sourceMappingURL=buildCurlStatement.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"buildCurlStatement.d.ts","sourceRoot":"","sources":["../../src/submit/buildCurlStatement.ts"],"names":[],"mappings":"AAIA,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAQxF;AAMD,gDAAgD;AAChD,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC,MAAM,CAmBR;AAED,MAAM,WAAW,gCAAgC;IAC/C,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACrC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC5B;AAED,wBAAgB,yBAAyB,CAAC,EACxC,UAAU,EACV,WAAgB,EAChB,WAAW,GACZ,EAAE,gCAAgC,GAAG,MAAM,CAQ3C"}
1
+ {"version":3,"file":"buildCurlStatement.d.ts","sourceRoot":"","sources":["../../src/submit/buildCurlStatement.ts"],"names":[],"mappings":"AASA,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAQxF;AAED,mDAAmD;AACnD,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC,MAAM,CAER;AAED,MAAM,WAAW,gCAAgC;IAC/C,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACrC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC5B;AAED,wBAAgB,yBAAyB,CAAC,EACxC,UAAU,EACV,WAAgB,EAChB,WAAW,GACZ,EAAE,gCAAgC,GAAG,MAAM,CAQ3C;AAED,8DAA8D;AAC9D,wBAAgB,iCAAiC,CAC/C,OAAO,EAAE,OAAO,EAChB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,GAC1B,MAAM,CAKR"}
@@ -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,CAAA;AAE1F,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;AAqDD,uEAAuE;AACvE,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,uBAAuB,GAAG,IAAI,CAUvF;AAED;;;GAGG;AACH,wBAAgB,8BAA8B,CAC5C,UAAU,EAAE,uBAAuB,EACnC,cAAc,EAAE,UAAU,GAAG,SAAS,GACrC,yBAAyB,CAqC3B;AAmBD,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,CAAA;AAE1F,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;AAqDD,uEAAuE;AACvE,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,uBAAuB,GAAG,IAAI,CAUvF;AAED;;;GAGG;AACH,wBAAgB,8BAA8B,CAC5C,UAAU,EAAE,uBAAuB,EACnC,cAAc,EAAE,UAAU,GAAG,SAAS,GACrC,yBAAyB,CAqC3B;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"}
@@ -21,7 +21,9 @@ export interface A2uiSurfaceDoc {
21
21
  }
22
22
  export declare const A2UI_PROTOCOL_VERSION: "v0.9";
23
23
  export type FormJsonSchema = Record<string, unknown>;
24
- export type FormBlockType = 'heading' | 'paragraph' | 'text' | 'textarea' | 'email' | 'number' | 'select' | 'multiselect' | 'radio' | 'checkbox' | 'toggle' | 'tel' | 'url' | 'datetime' | 'time' | 'file' | 'resume_multiselect' | 'case_singleselect' | 'image' | 'video' | 'audio';
24
+ export type FormBlockType = 'heading' | 'paragraph' | 'text' | 'textarea' | 'email' | 'number' | 'select' | 'multiselect' | 'radio' | 'checkbox' | 'toggle' | 'tel' | 'url' | 'datetime' | 'time' | 'file' | 'resume_multiselect' | 'case_multiselect'
25
+ /** @deprecated 加载时自动迁移为 case_multiselect */
26
+ | 'case_singleselect' | 'image' | 'video' | 'audio';
25
27
  export type FormMediaSize = 'huge' | 'large' | 'medium' | 'small' | 'icon';
26
28
  export type FormFilePlaceholderIcon = 'video' | 'audio' | 'image' | 'file' | 'spreadsheet' | 'document';
27
29
  export declare const FORM_MEDIA_SIZES: FormMediaSize[];
@@ -36,13 +38,18 @@ export interface FormResumeFilter {
36
38
  resumeUniqueIds?: string[];
37
39
  pageSize?: number;
38
40
  }
39
- /** 案例单选组件:渲染时用于 /univ/case/list 的筛选条件 */
41
+ /** 案例多选组件:渲染时用于 /univ/case/list 的筛选条件 */
40
42
  export interface FormCaseFilter {
41
43
  /** Agent 类型(编辑器辅助,运行时不用) */
42
44
  agentKind?: string;
43
- /** Agent unique_id(编辑器辅助,运行时不用) */
45
+ /** Agent unique_id(运行时用于限定所属 Agent) */
44
46
  agentUniqueId?: string;
45
- flowId?: number;
47
+ /** 流类型 unique_id */
48
+ flowType?: string;
49
+ /** 流类别 unique_id */
50
+ category?: string;
51
+ /** 流程编号(flow_unique_id)多选 */
52
+ flowUniqueIds?: string[];
46
53
  pageSize?: number;
47
54
  }
48
55
  export interface FormBlock {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
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,mCAAmC;IACnC,YAAY,CAAC,EAAE,gBAAgB,CAAA;IAC/B,qBAAqB;IACrB,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,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"}
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.1.4",
6
+ "version": "0.1.6",
7
7
  "description": "Skopon form rendering SDK (A2UI + form_definition) with submit helpers",
8
8
  "type": "module",
9
9
  "sideEffects": [
@@ -4,6 +4,7 @@
4
4
  import { describe, it, expect } from 'vitest'
5
5
  import type { FormSchema } from '../types/index'
6
6
  import { a2uiToBlocks, blocksToA2ui, isA2uiSurfaceEmpty, surfaceDocToMessages } from './a2uiAdapter'
7
+ import { normalizeFormDefinition } from './formSchema'
7
8
 
8
9
  function makeDefinition(): Pick<FormSchema, 'title' | 'description' | 'blocks'> {
9
10
  return {
@@ -239,7 +240,7 @@ describe('a2uiAdapter blocks <-> surface', () => {
239
240
  expect((withHeader.components.find((c) => c.id === 'root')?.children as string[]).length).toBe(12)
240
241
  })
241
242
 
242
- it('round-trips case_singleselect through blocksToA2ui and a2uiToBlocks', () => {
243
+ it('round-trips case_multiselect through blocksToA2ui and a2uiToBlocks', () => {
243
244
  const doc = blocksToA2ui(
244
245
  {
245
246
  title: '',
@@ -247,19 +248,21 @@ describe('a2uiAdapter blocks <-> surface', () => {
247
248
  blocks: [
248
249
  {
249
250
  id: 'b-case',
250
- type: 'case_singleselect',
251
- name: 'selected_case',
251
+ type: 'case_multiselect',
252
+ name: 'selected_cases',
252
253
  label: '选择案例',
253
254
  placeholder: '暂无案例',
254
- help: '请选择一个案例',
255
+ help: '请选择一个或多个案例',
255
256
  caseEnableRefresh: true,
256
257
  caseFilter: {
257
258
  agentKind: 'public-user',
258
259
  agentUniqueId: 'agent-1',
259
- flowId: 42,
260
+ flowType: 'content',
261
+ category: 'short-video',
262
+ flowUniqueIds: ['flow-rec-a', 'flow-rec-b'],
260
263
  pageSize: 10,
261
264
  },
262
- defaultValue: 'case-abc',
265
+ defaultValue: ['case-abc', 'case-def'],
263
266
  },
264
267
  ],
265
268
  },
@@ -271,19 +274,39 @@ describe('a2uiAdapter blocks <-> surface', () => {
271
274
  expect(node?.caseFilter).toEqual({
272
275
  agentKind: 'public-user',
273
276
  agentUniqueId: 'agent-1',
274
- flowId: 42,
277
+ flowType: 'content',
278
+ category: 'short-video',
279
+ flowUniqueIds: ['flow-rec-a', 'flow-rec-b'],
275
280
  pageSize: 10,
276
281
  })
277
- expect(doc.dataModel?.selected_case).toBe('case-abc')
282
+ expect(doc.dataModel?.selected_cases).toEqual(['case-abc', 'case-def'])
278
283
 
279
284
  const back = a2uiToBlocks(doc)
280
- const block = back.blocks.find((b) => b.name === 'selected_case')
281
- expect(block?.type).toBe('case_singleselect')
285
+ const block = back.blocks.find((b) => b.name === 'selected_cases')
286
+ expect(block?.type).toBe('case_multiselect')
282
287
  expect(block?.caseEnableRefresh).toBe(true)
283
- expect(block?.caseFilter?.flowId).toBe(42)
288
+ expect(block?.caseFilter?.flowType).toBe('content')
289
+ expect(block?.caseFilter?.category).toBe('short-video')
290
+ expect(block?.caseFilter?.flowUniqueIds).toEqual(['flow-rec-a', 'flow-rec-b'])
284
291
  expect(block?.caseFilter?.pageSize).toBe(10)
285
- expect(block?.caseFilter?.agentKind).toBe('public-user')
286
- expect(block?.caseFilter?.agentUniqueId).toBe('agent-1')
287
- expect(block?.defaultValue).toBe('case-abc')
292
+ expect(block?.defaultValue).toEqual(['case-abc', 'case-def'])
293
+ })
294
+
295
+ it('migrates legacy case_singleselect to case_multiselect on normalize', () => {
296
+ const normalized = normalizeFormDefinition({
297
+ title: '',
298
+ description: '',
299
+ blocks: [
300
+ {
301
+ id: 'legacy',
302
+ type: 'case_singleselect',
303
+ name: 'old_case',
304
+ defaultValue: 'case-old',
305
+ },
306
+ ],
307
+ jsonSchema: {},
308
+ })
309
+ expect(normalized.blocks[0]?.type).toBe('case_multiselect')
310
+ expect(normalized.blocks[0]?.defaultValue).toEqual(['case-old'])
288
311
  })
289
312
  })
@@ -101,15 +101,12 @@ function defaultValueForBlock(block: FormBlock): unknown {
101
101
  case 'multiselect':
102
102
  case 'checkbox':
103
103
  case 'resume_multiselect':
104
+ case 'case_multiselect':
104
105
  if (Array.isArray(block.defaultValue)) return block.defaultValue
105
106
  if (typeof block.defaultValue === 'string' && block.defaultValue) return [block.defaultValue]
106
107
  return []
107
108
  case 'select':
108
109
  case 'radio':
109
- case 'case_singleselect':
110
- if (Array.isArray(block.defaultValue)) return block.defaultValue[0] ?? ''
111
- if (typeof block.defaultValue === 'string') return block.defaultValue
112
- return ''
113
110
  case 'number':
114
111
  if (typeof block.defaultValue === 'number') return block.defaultValue
115
112
  if (typeof block.defaultValue === 'string' && block.defaultValue) return block.defaultValue
@@ -29,7 +29,7 @@ function normalizeDefaultValue(
29
29
  ): FormBlock['defaultValue'] {
30
30
  if (raw === undefined) return undefined
31
31
  if (type === 'toggle') return coerceToggleValue(raw)
32
- if (type === 'multiselect' || type === 'checkbox' || type === 'resume_multiselect') {
32
+ if (type === 'multiselect' || type === 'checkbox' || type === 'resume_multiselect' || type === 'case_multiselect') {
33
33
  if (Array.isArray(raw)) return raw.map(String)
34
34
  if (typeof raw === 'string' && raw) return [raw]
35
35
  return []
@@ -45,9 +45,6 @@ function normalizeCaseFilter(raw: FormCaseFilter | undefined): FormCaseFilter {
45
45
  Number.isFinite(pageSizeRaw) && pageSizeRaw >= 1 && pageSizeRaw <= 100
46
46
  ? Math.floor(pageSizeRaw)
47
47
  : 20
48
- const flowIdRaw = typeof raw?.flowId === 'number' ? raw.flowId : Number(raw?.flowId)
49
- const flowId =
50
- Number.isFinite(flowIdRaw) && flowIdRaw > 0 ? Math.floor(flowIdRaw) : undefined
51
48
  const agentKind =
52
49
  typeof raw?.agentKind === 'string' && raw.agentKind.trim()
53
50
  ? raw.agentKind.trim()
@@ -56,7 +53,18 @@ function normalizeCaseFilter(raw: FormCaseFilter | undefined): FormCaseFilter {
56
53
  typeof raw?.agentUniqueId === 'string' && raw.agentUniqueId.trim()
57
54
  ? raw.agentUniqueId.trim()
58
55
  : undefined
59
- return { agentKind, agentUniqueId, flowId, pageSize }
56
+ const flowType =
57
+ typeof raw?.flowType === 'string' && raw.flowType.trim()
58
+ ? raw.flowType.trim()
59
+ : undefined
60
+ const category =
61
+ typeof raw?.category === 'string' && raw.category.trim()
62
+ ? raw.category.trim()
63
+ : undefined
64
+ const flowUniqueIds = Array.isArray(raw?.flowUniqueIds)
65
+ ? raw.flowUniqueIds.map(String).filter(Boolean)
66
+ : []
67
+ return { agentKind, agentUniqueId, flowType, category, flowUniqueIds, pageSize }
60
68
  }
61
69
 
62
70
  function normalizeResumeFilter(raw: FormResumeFilter | undefined): FormResumeFilter {
@@ -77,44 +85,56 @@ function normalizeResumeFilter(raw: FormResumeFilter | undefined): FormResumeFil
77
85
  }
78
86
  }
79
87
 
88
+ function migrateLegacyBlock(raw: FormBlock): FormBlock {
89
+ if (raw.type !== 'case_singleselect') return raw
90
+ const defaultValue =
91
+ typeof raw.defaultValue === 'string' && raw.defaultValue.trim()
92
+ ? [raw.defaultValue.trim()]
93
+ : Array.isArray(raw.defaultValue)
94
+ ? raw.defaultValue.map(String).filter(Boolean)
95
+ : []
96
+ return { ...raw, type: 'case_multiselect', defaultValue }
97
+ }
98
+
80
99
  function normalizeBlock(raw: FormBlock): FormBlock {
81
- const fileAcceptTypes = normalizeFileAcceptTypes(raw.fileAcceptTypes)
82
- const type = raw.type
100
+ const migrated = migrateLegacyBlock(raw)
101
+ const fileAcceptTypes = normalizeFileAcceptTypes(migrated.fileAcceptTypes)
102
+ const type = migrated.type
83
103
  return {
84
- id: raw.id?.trim() || generateId('block-'),
104
+ id: migrated.id?.trim() || generateId('block-'),
85
105
  type,
86
- name: raw.name?.trim() || undefined,
87
- label: raw.label ?? '',
88
- placeholder: raw.placeholder ?? '',
89
- help: raw.help ?? '',
90
- required: raw.required === true,
106
+ name: migrated.name?.trim() || undefined,
107
+ label: migrated.label ?? '',
108
+ placeholder: migrated.placeholder ?? '',
109
+ help: migrated.help ?? '',
110
+ required: migrated.required === true,
91
111
  mediaUrls: isMediaBlockType(type)
92
- ? normalizeMediaUrls(raw, DEFAULT_MEDIA_URL_MAX)
112
+ ? normalizeMediaUrls(migrated, DEFAULT_MEDIA_URL_MAX)
93
113
  : undefined,
94
- mediaSize: isMediaBlockType(type) ? normalizeMediaSize(raw.mediaSize) : undefined,
114
+ mediaSize: isMediaBlockType(type) ? normalizeMediaSize(migrated.mediaSize) : undefined,
95
115
  fileAcceptTypes,
96
116
  fileAcceptExtensions: filterFileAcceptExtensionsForTypes(
97
117
  fileAcceptTypes,
98
- raw.fileAcceptExtensions,
118
+ migrated.fileAcceptExtensions,
99
119
  ),
100
- filePlaceholderIcon: normalizeFilePlaceholderIcon(raw.filePlaceholderIcon),
101
- fileMinCount: type === 'file' ? normalizeFileUploadCount(raw.fileMinCount, 1) : undefined,
102
- fileMaxCount: type === 'file' ? normalizeFileUploadCount(raw.fileMaxCount, 1) : undefined,
103
- defaultValue: normalizeDefaultValue(type, raw.defaultValue),
104
- options: Array.isArray(raw.options)
105
- ? raw.options.map((opt, index) => ({
120
+ filePlaceholderIcon: normalizeFilePlaceholderIcon(migrated.filePlaceholderIcon),
121
+ fileMinCount: type === 'file' ? normalizeFileUploadCount(migrated.fileMinCount, 1) : undefined,
122
+ fileMaxCount: type === 'file' ? normalizeFileUploadCount(migrated.fileMaxCount, 1) : undefined,
123
+ defaultValue: normalizeDefaultValue(type, migrated.defaultValue),
124
+ options: Array.isArray(migrated.options)
125
+ ? migrated.options.map((opt, index) => ({
106
126
  value: opt.value?.trim() || `option_${index + 1}`,
107
127
  label: opt.label?.trim() || `选项 ${index + 1}`,
108
128
  }))
109
129
  : undefined,
110
130
  resumeEnableRefresh:
111
- type === 'resume_multiselect' ? raw.resumeEnableRefresh !== false : undefined,
131
+ type === 'resume_multiselect' ? migrated.resumeEnableRefresh !== false : undefined,
112
132
  resumeFilter:
113
- type === 'resume_multiselect' ? normalizeResumeFilter(raw.resumeFilter) : undefined,
133
+ type === 'resume_multiselect' ? normalizeResumeFilter(migrated.resumeFilter) : undefined,
114
134
  caseEnableRefresh:
115
- type === 'case_singleselect' ? raw.caseEnableRefresh !== false : undefined,
135
+ type === 'case_multiselect' ? migrated.caseEnableRefresh !== false : undefined,
116
136
  caseFilter:
117
- type === 'case_singleselect' ? normalizeCaseFilter(raw.caseFilter) : undefined,
137
+ type === 'case_multiselect' ? normalizeCaseFilter(migrated.caseFilter) : undefined,
118
138
  }
119
139
  }
120
140
 
@@ -192,10 +212,10 @@ export function createEmptyBlock(type: FormBlockType): FormBlock {
192
212
  block.defaultValue = []
193
213
  }
194
214
 
195
- if (type === 'case_singleselect') {
215
+ if (type === 'case_multiselect') {
196
216
  block.caseEnableRefresh = true
197
- block.caseFilter = { pageSize: 20 }
198
- block.defaultValue = ''
217
+ block.caseFilter = { flowUniqueIds: [], pageSize: 20 }
218
+ block.defaultValue = []
199
219
  }
200
220
 
201
221
  return block
@@ -0,0 +1,90 @@
1
+ import type { A2uiComponentNode, FormBlock } from '../../types/index'
2
+ import type { BlockAdapterContext, BlockToComponentResult } from '../types'
3
+
4
+ function asLiteral(value: unknown): string | undefined {
5
+ return typeof value === 'string' ? value : undefined
6
+ }
7
+
8
+ function readCaseFilterFromNode(cf: Record<string, unknown>) {
9
+ const pageSizeRaw = typeof cf.pageSize === 'number' ? cf.pageSize : Number(cf.pageSize)
10
+ const agentKind =
11
+ typeof cf.agentKind === 'string' && cf.agentKind.trim()
12
+ ? cf.agentKind.trim()
13
+ : undefined
14
+ const agentUniqueId =
15
+ typeof cf.agentUniqueId === 'string' && cf.agentUniqueId.trim()
16
+ ? cf.agentUniqueId.trim()
17
+ : undefined
18
+ const flowType =
19
+ typeof cf.flowType === 'string' && cf.flowType.trim()
20
+ ? cf.flowType.trim()
21
+ : undefined
22
+ const category =
23
+ typeof cf.category === 'string' && cf.category.trim()
24
+ ? cf.category.trim()
25
+ : undefined
26
+ const flowUniqueIds = Array.isArray(cf.flowUniqueIds)
27
+ ? cf.flowUniqueIds.map(String).filter(Boolean)
28
+ : []
29
+ return {
30
+ agentKind,
31
+ agentUniqueId,
32
+ flowType,
33
+ category,
34
+ flowUniqueIds,
35
+ pageSize:
36
+ Number.isFinite(pageSizeRaw) && pageSizeRaw >= 1 && pageSizeRaw <= 100
37
+ ? Math.floor(pageSizeRaw)
38
+ : 20,
39
+ }
40
+ }
41
+
42
+ export function caseMultiselectToComponent(
43
+ block: FormBlock,
44
+ ctx: BlockAdapterContext,
45
+ ): BlockToComponentResult {
46
+ const { id, label, path, withData } = ctx
47
+ return withData({
48
+ id,
49
+ component: 'SkoponCaseSelect',
50
+ label,
51
+ placeholder: block.placeholder ?? '',
52
+ help: block.help ?? '',
53
+ enableRefresh: block.caseEnableRefresh !== false,
54
+ caseFilter: {
55
+ agentKind: block.caseFilter?.agentKind,
56
+ agentUniqueId: block.caseFilter?.agentUniqueId,
57
+ flowType: block.caseFilter?.flowType,
58
+ category: block.caseFilter?.category,
59
+ flowUniqueIds: block.caseFilter?.flowUniqueIds ?? [],
60
+ pageSize: block.caseFilter?.pageSize ?? 20,
61
+ },
62
+ value: { path },
63
+ })
64
+ }
65
+
66
+ export function caseMultiselectFromComponent(
67
+ node: A2uiComponentNode,
68
+ base: (type: 'case_multiselect') => FormBlock,
69
+ helpers: {
70
+ asLiteral: (value: unknown) => string | undefined
71
+ nameFromValue: (value: unknown) => string
72
+ },
73
+ ): FormBlock {
74
+ const name = helpers.nameFromValue(node.value)
75
+ const block: FormBlock = {
76
+ ...base('case_multiselect'),
77
+ name,
78
+ caseEnableRefresh: node.enableRefresh !== false,
79
+ }
80
+ const placeholder = helpers.asLiteral(node.placeholder)
81
+ if (placeholder) block.placeholder = placeholder
82
+ const help = helpers.asLiteral(node.help)
83
+ if (help) block.help = help
84
+ if (node.caseFilter && typeof node.caseFilter === 'object') {
85
+ block.caseFilter = readCaseFilterFromNode(node.caseFilter as Record<string, unknown>)
86
+ }
87
+ return block
88
+ }
89
+
90
+ export { asLiteral }
@@ -0,0 +1,14 @@
1
+ import type { BlockAdapterPlugin } from '../types'
2
+ import {
3
+ caseMultiselectFromComponent,
4
+ caseMultiselectToComponent,
5
+ } from './adapter'
6
+ import { SkoponCaseSelectImpl } from '../../catalog/skoponCaseSelect'
7
+
8
+ export const caseMultiselectAdapter: BlockAdapterPlugin = {
9
+ type: 'case_multiselect',
10
+ componentName: 'SkoponCaseSelect',
11
+ toComponent: caseMultiselectToComponent,
12
+ fromComponent: caseMultiselectFromComponent,
13
+ catalogComponents: [SkoponCaseSelectImpl],
14
+ }
@@ -1,9 +1,9 @@
1
1
  import type { FormBlockType } from '../types/index'
2
2
  import type { BlockAdapterPlugin } from './types'
3
- import { caseSingleselectAdapter } from './case_singleselect'
3
+ import { caseMultiselectAdapter } from './case_multiselect'
4
4
  import { resumeMultiselectAdapter } from './resume_multiselect'
5
5
 
6
- const adapters: BlockAdapterPlugin[] = [resumeMultiselectAdapter, caseSingleselectAdapter]
6
+ const adapters: BlockAdapterPlugin[] = [resumeMultiselectAdapter, caseMultiselectAdapter]
7
7
 
8
8
  const adapterByType = new Map<FormBlockType, BlockAdapterPlugin>(
9
9
  adapters.map((adapter) => [adapter.type, adapter]),
@@ -1,6 +1,12 @@
1
1
  import { createContext, useContext, type ReactNode } from 'react'
2
2
  import type { FormResumeFilter } from '../types/index'
3
3
 
4
+ export interface ResumeSearchWorkItem {
5
+ title: string
6
+ coverUrl?: string | null
7
+ link?: string
8
+ }
9
+
4
10
  export interface ResumeSearchItem {
5
11
  resumeUniqueId: string
6
12
  resumeId?: number
@@ -11,6 +17,10 @@ export interface ResumeSearchItem {
11
17
  satisfaction?: number
12
18
  published?: number
13
19
  worksCount?: number
20
+ personality?: string[]
21
+ specialties?: string[]
22
+ traits?: string[]
23
+ works?: ResumeSearchWorkItem[]
14
24
  }
15
25
 
16
26
  export interface ResumeSearchParams {
@@ -6,7 +6,7 @@ import { createBinderlessComponentImplementation } from '@a2ui/react/v0_9'
6
6
  import { z } from 'zod'
7
7
  import type { FormCaseFilter } from '../types/index'
8
8
  import { useA2uiPreviewMode } from './a2uiPreviewContext'
9
- import { asOptionalString, useSkoponBoundField } from './useSkoponBoundField'
9
+ import { asStringArray, useSkoponBoundField } from './useSkoponBoundField'
10
10
  import { type CaseSearchItem, useCaseSearch } from './caseSearchContext'
11
11
 
12
12
  function readString(value: unknown): string {
@@ -15,18 +15,40 @@ function readString(value: unknown): string {
15
15
 
16
16
  function readCaseFilter(raw: unknown): FormCaseFilter {
17
17
  if (!raw || typeof raw !== 'object') {
18
- return { pageSize: 20 }
18
+ return { flowUniqueIds: [], pageSize: 20 }
19
19
  }
20
20
  const rec = raw as Record<string, unknown>
21
- const flowIdRaw = typeof rec.flowId === 'number' ? rec.flowId : Number(rec.flowId)
22
- const flowId =
23
- Number.isFinite(flowIdRaw) && flowIdRaw > 0 ? Math.floor(flowIdRaw) : undefined
24
21
  const pageSizeRaw = typeof rec.pageSize === 'number' ? rec.pageSize : Number(rec.pageSize)
25
22
  const pageSize =
26
23
  Number.isFinite(pageSizeRaw) && pageSizeRaw >= 1 && pageSizeRaw <= 100
27
24
  ? Math.floor(pageSizeRaw)
28
25
  : 20
29
- return { flowId, pageSize }
26
+ const agentKind =
27
+ typeof rec.agentKind === 'string' && rec.agentKind.trim()
28
+ ? rec.agentKind.trim()
29
+ : undefined
30
+ const agentUniqueId =
31
+ typeof rec.agentUniqueId === 'string' && rec.agentUniqueId.trim()
32
+ ? rec.agentUniqueId.trim()
33
+ : undefined
34
+ const flowType =
35
+ typeof rec.flowType === 'string' && rec.flowType.trim()
36
+ ? rec.flowType.trim()
37
+ : undefined
38
+ const category =
39
+ typeof rec.category === 'string' && rec.category.trim()
40
+ ? rec.category.trim()
41
+ : undefined
42
+ const flowUniqueIds = Array.isArray(rec.flowUniqueIds)
43
+ ? rec.flowUniqueIds.map(String).filter(Boolean)
44
+ : []
45
+ return { agentKind, agentUniqueId, flowType, category, flowUniqueIds, pageSize }
46
+ }
47
+
48
+ function isCaseFilterReady(filter: FormCaseFilter): boolean {
49
+ const hasFlowIds = Boolean(filter.flowUniqueIds?.length)
50
+ const hasCategory = Boolean(filter.agentUniqueId && filter.flowType && filter.category)
51
+ return hasFlowIds || hasCategory
30
52
  }
31
53
 
32
54
  function filterKey(filter: FormCaseFilter): string {
@@ -104,20 +126,20 @@ function SkoponCaseSelectPreview({ context }: { context: ComponentContext }) {
104
126
  const [pageSize, setPageSize] = useState(caseFilter.pageSize ?? 20)
105
127
  const requestSeqRef = useRef(0)
106
128
 
107
- const selectedId = asOptionalString(value) ?? ''
129
+ const selectedIds = asStringArray(value)
108
130
 
109
131
  const emptyHint = useMemo(() => {
110
132
  if (!caseSearch) return '未配置案例搜索服务'
111
- if (!caseFilter.flowId) return '未配置 Flow 筛选条件'
133
+ if (!isCaseFilterReady(caseFilter)) return '未配置案例列表筛选条件'
112
134
  if (fetchError) return fetchError
113
135
  return placeholder || '暂无匹配的案例'
114
- }, [caseSearch, caseFilter.flowId, fetchError, placeholder])
136
+ }, [caseSearch, caseFilter, fetchError, placeholder])
115
137
 
116
138
  const fetchPage = useCallback(
117
139
  async (targetPage: number) => {
118
140
  const filter = readCaseFilter(JSON.parse(filterSig) as FormCaseFilter)
119
141
  const seq = ++requestSeqRef.current
120
- if (!caseSearch || !filter.flowId) {
142
+ if (!caseSearch || !isCaseFilterReady(filter)) {
121
143
  setItems([])
122
144
  setTotal(0)
123
145
  setFetchError('')
@@ -151,7 +173,10 @@ function SkoponCaseSelectPreview({ context }: { context: ComponentContext }) {
151
173
 
152
174
  const toggleSelect = (caseUniqueId: string) => {
153
175
  if (!interactive) return
154
- setValue(selectedId === caseUniqueId ? '' : caseUniqueId)
176
+ const next = selectedIds.includes(caseUniqueId)
177
+ ? selectedIds.filter((id) => id !== caseUniqueId)
178
+ : [...selectedIds, caseUniqueId]
179
+ setValue(next)
155
180
  }
156
181
 
157
182
  const handleRefresh = () => {
@@ -179,14 +204,14 @@ function SkoponCaseSelectPreview({ context }: { context: ComponentContext }) {
179
204
  <CaseCard
180
205
  key={item.caseUniqueId}
181
206
  item={item}
182
- selected={selectedId === item.caseUniqueId}
207
+ selected={selectedIds.includes(item.caseUniqueId)}
183
208
  disabled={!interactive}
184
209
  onToggle={() => toggleSelect(item.caseUniqueId)}
185
210
  />
186
211
  ))}
187
212
  </div>
188
213
  ) : null}
189
- {enableRefresh && caseSearch && caseFilter.flowId ? (
214
+ {enableRefresh && caseSearch && isCaseFilterReady(caseFilter) ? (
190
215
  <div className="skopon-resume-select-actions">
191
216
  <Button
192
217
  type="default"