@skopon-cool/form-sdk 0.1.3 → 0.1.4

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 (66) hide show
  1. package/README.md +2 -0
  2. package/dist/adapter/a2uiAdapter.d.ts +0 -1
  3. package/dist/adapter/a2uiAdapter.d.ts.map +1 -1
  4. package/dist/adapter/formSchema.d.ts.map +1 -1
  5. package/dist/blocks/case_singleselect/adapter.d.ts +10 -0
  6. package/dist/blocks/case_singleselect/adapter.d.ts.map +1 -0
  7. package/dist/blocks/case_singleselect/catalog.d.ts +2 -0
  8. package/dist/blocks/case_singleselect/catalog.d.ts.map +1 -0
  9. package/dist/blocks/case_singleselect/index.d.ts +3 -0
  10. package/dist/blocks/case_singleselect/index.d.ts.map +1 -0
  11. package/dist/blocks/registry.d.ts +8 -0
  12. package/dist/blocks/registry.d.ts.map +1 -0
  13. package/dist/blocks/resume_multiselect/adapter.d.ts +10 -0
  14. package/dist/blocks/resume_multiselect/adapter.d.ts.map +1 -0
  15. package/dist/blocks/resume_multiselect/catalog.d.ts +2 -0
  16. package/dist/blocks/resume_multiselect/catalog.d.ts.map +1 -0
  17. package/dist/blocks/resume_multiselect/index.d.ts +3 -0
  18. package/dist/blocks/resume_multiselect/index.d.ts.map +1 -0
  19. package/dist/blocks/types.d.ts +27 -0
  20. package/dist/blocks/types.d.ts.map +1 -0
  21. package/dist/catalog/a2uiCustomCatalog.d.ts.map +1 -1
  22. package/dist/catalog/caseSearchContext.d.ts +28 -0
  23. package/dist/catalog/caseSearchContext.d.ts.map +1 -0
  24. package/dist/catalog/resumeSearchContext.d.ts +30 -0
  25. package/dist/catalog/resumeSearchContext.d.ts.map +1 -0
  26. package/dist/catalog/skoponCaseSelect.d.ts +2 -0
  27. package/dist/catalog/skoponCaseSelect.d.ts.map +1 -0
  28. package/dist/catalog/skoponResumeSelect.d.ts +2 -0
  29. package/dist/catalog/skoponResumeSelect.d.ts.map +1 -0
  30. package/dist/components/AskUserFormCard.d.ts +7 -1
  31. package/dist/components/AskUserFormCard.d.ts.map +1 -1
  32. package/dist/components/SkoponA2uiStreamRenderer.d.ts +7 -1
  33. package/dist/components/SkoponA2uiStreamRenderer.d.ts.map +1 -1
  34. package/dist/components/SkoponFormRenderer.d.ts +6 -0
  35. package/dist/components/SkoponFormRenderer.d.ts.map +1 -1
  36. package/dist/form-sdk.css +1 -1
  37. package/dist/index.d.ts +5 -0
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +1290 -845
  40. package/dist/types/index.d.ts +26 -2
  41. package/dist/types/index.d.ts.map +1 -1
  42. package/package.json +1 -1
  43. package/src/adapter/a2uiAdapter.test.ts +48 -0
  44. package/src/adapter/a2uiAdapter.ts +41 -1
  45. package/src/adapter/formSchema.ts +65 -2
  46. package/src/blocks/case_singleselect/adapter.ts +74 -0
  47. package/src/blocks/case_singleselect/catalog.ts +1 -0
  48. package/src/blocks/case_singleselect/index.ts +14 -0
  49. package/src/blocks/registry.ts +34 -0
  50. package/src/blocks/resume_multiselect/adapter.ts +57 -0
  51. package/src/blocks/resume_multiselect/catalog.ts +1 -0
  52. package/src/blocks/resume_multiselect/index.ts +14 -0
  53. package/src/blocks/types.ts +34 -0
  54. package/src/catalog/a2uiCustomCatalog.tsx +6 -0
  55. package/src/catalog/caseSearchContext.tsx +46 -0
  56. package/src/catalog/resumeSearchContext.tsx +48 -0
  57. package/src/catalog/skoponCaseSelect.tsx +215 -0
  58. package/src/catalog/skoponResumeSelect.tsx +227 -0
  59. package/src/components/AskUserFormCard.tsx +10 -0
  60. package/src/components/SkoponA2uiStreamRenderer.test.ts +18 -2
  61. package/src/components/SkoponA2uiStreamRenderer.test.tsx +26 -2
  62. package/src/components/SkoponA2uiStreamRenderer.tsx +60 -23
  63. package/src/components/SkoponFormRenderer.tsx +32 -10
  64. package/src/index.ts +23 -0
  65. package/src/styles/index.css +60 -0
  66. package/src/types/index.ts +30 -0
@@ -4,7 +4,7 @@
4
4
  export type A2uiBinding<T> = T | {
5
5
  path: string;
6
6
  };
7
- export type A2uiComponentName = 'Column' | 'Row' | 'Text' | 'Image' | 'Video' | 'AudioPlayer' | 'TextField' | 'CheckBox' | 'ChoicePicker' | 'DateTimeInput' | 'FileUpload' | 'SkoponMedia' | 'SkoponSelect';
7
+ export type A2uiComponentName = 'Column' | 'Row' | 'Text' | 'Image' | 'Video' | 'AudioPlayer' | 'TextField' | 'CheckBox' | 'ChoicePicker' | 'DateTimeInput' | 'FileUpload' | 'SkoponMedia' | 'SkoponSelect' | 'SkoponResumeSelect' | 'SkoponCaseSelect';
8
8
  export interface A2uiComponentNode {
9
9
  id: string;
10
10
  component: A2uiComponentName | string;
@@ -21,7 +21,7 @@ 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' | '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_singleselect' | 'image' | 'video' | 'audio';
25
25
  export type FormMediaSize = 'huge' | 'large' | 'medium' | 'small' | 'icon';
26
26
  export type FormFilePlaceholderIcon = 'video' | 'audio' | 'image' | 'file' | 'spreadsheet' | 'document';
27
27
  export declare const FORM_MEDIA_SIZES: FormMediaSize[];
@@ -29,6 +29,22 @@ export interface FormBlockOption {
29
29
  value: string;
30
30
  label: string;
31
31
  }
32
+ /** 简历多选组件:渲染时用于 /univ/resume/search 的筛选条件 */
33
+ export interface FormResumeFilter {
34
+ names?: string[];
35
+ agentUniqueIds?: string[];
36
+ resumeUniqueIds?: string[];
37
+ pageSize?: number;
38
+ }
39
+ /** 案例单选组件:渲染时用于 /univ/case/list 的筛选条件 */
40
+ export interface FormCaseFilter {
41
+ /** Agent 类型(编辑器辅助,运行时不用) */
42
+ agentKind?: string;
43
+ /** Agent unique_id(编辑器辅助,运行时不用) */
44
+ agentUniqueId?: string;
45
+ flowId?: number;
46
+ pageSize?: number;
47
+ }
32
48
  export interface FormBlock {
33
49
  id: string;
34
50
  type: FormBlockType;
@@ -47,6 +63,14 @@ export interface FormBlock {
47
63
  fileMinCount?: number;
48
64
  fileMaxCount?: number;
49
65
  defaultValue?: string | string[] | boolean;
66
+ /** 简历多选:是否显示「换一批」 */
67
+ resumeEnableRefresh?: boolean;
68
+ /** 简历多选:列表筛选条件(持久化进 block json) */
69
+ resumeFilter?: FormResumeFilter;
70
+ /** 案例单选:是否显示「换一批」 */
71
+ caseEnableRefresh?: boolean;
72
+ /** 案例单选:列表筛选条件(持久化进 block json) */
73
+ caseFilter?: FormCaseFilter;
50
74
  }
51
75
  export interface FormSchema {
52
76
  title?: string;
@@ -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":"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"}
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.1.3",
6
+ "version": "0.1.4",
7
7
  "description": "Skopon form rendering SDK (A2UI + form_definition) with submit helpers",
8
8
  "type": "module",
9
9
  "sideEffects": [
@@ -238,4 +238,52 @@ describe('a2uiAdapter blocks <-> surface', () => {
238
238
  const withHeader = blocksToA2ui(makeDefinition())
239
239
  expect((withHeader.components.find((c) => c.id === 'root')?.children as string[]).length).toBe(12)
240
240
  })
241
+
242
+ it('round-trips case_singleselect through blocksToA2ui and a2uiToBlocks', () => {
243
+ const doc = blocksToA2ui(
244
+ {
245
+ title: '',
246
+ description: '',
247
+ blocks: [
248
+ {
249
+ id: 'b-case',
250
+ type: 'case_singleselect',
251
+ name: 'selected_case',
252
+ label: '选择案例',
253
+ placeholder: '暂无案例',
254
+ help: '请选择一个案例',
255
+ caseEnableRefresh: true,
256
+ caseFilter: {
257
+ agentKind: 'public-user',
258
+ agentUniqueId: 'agent-1',
259
+ flowId: 42,
260
+ pageSize: 10,
261
+ },
262
+ defaultValue: 'case-abc',
263
+ },
264
+ ],
265
+ },
266
+ { includeHeader: false },
267
+ )
268
+ const node = doc.components.find((c) => c.id === 'b-case')
269
+ expect(node?.component).toBe('SkoponCaseSelect')
270
+ expect(node?.enableRefresh).toBe(true)
271
+ expect(node?.caseFilter).toEqual({
272
+ agentKind: 'public-user',
273
+ agentUniqueId: 'agent-1',
274
+ flowId: 42,
275
+ pageSize: 10,
276
+ })
277
+ expect(doc.dataModel?.selected_case).toBe('case-abc')
278
+
279
+ const back = a2uiToBlocks(doc)
280
+ const block = back.blocks.find((b) => b.name === 'selected_case')
281
+ expect(block?.type).toBe('case_singleselect')
282
+ expect(block?.caseEnableRefresh).toBe(true)
283
+ expect(block?.caseFilter?.flowId).toBe(42)
284
+ 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')
288
+ })
241
289
  })
@@ -11,6 +11,7 @@ import { generateId } from './id'
11
11
  import { coerceToggleValue } from './formSchema'
12
12
  import { getMediaUrls, normalizeMediaSize } from './formMedia'
13
13
  import { syncFormDefinition } from './formSchema'
14
+ import { getBlockTypeAdapter, getComponentAdapter } from '../blocks/registry'
14
15
 
15
16
  /**
16
17
  * A2UI surface <-> FormBlock 双向适配器。
@@ -99,14 +100,20 @@ function defaultValueForBlock(block: FormBlock): unknown {
99
100
  return coerceToggleValue(block.defaultValue)
100
101
  case 'multiselect':
101
102
  case 'checkbox':
103
+ case 'resume_multiselect':
102
104
  if (Array.isArray(block.defaultValue)) return block.defaultValue
103
105
  if (typeof block.defaultValue === 'string' && block.defaultValue) return [block.defaultValue]
104
106
  return []
105
107
  case 'select':
106
108
  case 'radio':
109
+ case 'case_singleselect':
107
110
  if (Array.isArray(block.defaultValue)) return block.defaultValue[0] ?? ''
108
111
  if (typeof block.defaultValue === 'string') return block.defaultValue
109
112
  return ''
113
+ case 'number':
114
+ if (typeof block.defaultValue === 'number') return block.defaultValue
115
+ if (typeof block.defaultValue === 'string' && block.defaultValue) return block.defaultValue
116
+ return null
110
117
  default:
111
118
  if (typeof block.defaultValue === 'string') return block.defaultValue
112
119
  return ''
@@ -131,6 +138,12 @@ function blockToComponent(block: FormBlock): {
131
138
  dataValue?: unknown
132
139
  } => (name ? { node, dataKey: name, dataValue: defaultValueForBlock(block) } : { node })
133
140
 
141
+ const adapterCtx = { id, label, name, path, withData }
142
+ const typeAdapter = getBlockTypeAdapter(block.type)
143
+ if (typeAdapter) {
144
+ return typeAdapter.toComponent(block, adapterCtx)
145
+ }
146
+
134
147
  switch (block.type) {
135
148
  case 'heading':
136
149
  return { node: { id, component: 'Text', text: label, variant: 'h3' } }
@@ -351,6 +364,11 @@ function componentToBlock(node: A2uiComponentNode): FormBlock | null {
351
364
 
352
365
  const base = (type: FormBlockType): FormBlock => ({ id, type, label })
353
366
 
367
+ const componentAdapter = getComponentAdapter(component)
368
+ if (componentAdapter) {
369
+ return componentAdapter.fromComponent(node, base, { asLiteral, nameFromValue })
370
+ }
371
+
354
372
  switch (component) {
355
373
  case 'Text': {
356
374
  const variant = String(node.variant ?? 'body')
@@ -465,6 +483,23 @@ function collectOrder(
465
483
  }
466
484
 
467
485
  /** A2UI surface 文档 -> FormSchema(已 sync 出 jsonSchema) */
486
+ function applyDataModelDefaults(
487
+ blocks: FormBlock[],
488
+ dataModel: Record<string, unknown> | undefined,
489
+ ): FormBlock[] {
490
+ if (!dataModel) return blocks
491
+ return blocks.map((block) => {
492
+ const name = block.name?.trim()
493
+ if (!name || !(name in dataModel)) return block
494
+ const raw = dataModel[name]
495
+ if (raw === undefined) return block
496
+ if (block.type === 'toggle') {
497
+ return { ...block, defaultValue: raw === true || raw === 'true' }
498
+ }
499
+ return { ...block, defaultValue: raw as FormBlock['defaultValue'] }
500
+ })
501
+ }
502
+
468
503
  export function a2uiToBlocks(doc: A2uiSurfaceDoc | null | undefined): FormSchema {
469
504
  if (!doc || !Array.isArray(doc.components)) {
470
505
  return syncFormDefinition({ title: '', description: '', blocks: [], jsonSchema: {} })
@@ -490,7 +525,12 @@ export function a2uiToBlocks(doc: A2uiSurfaceDoc | null | undefined): FormSchema
490
525
  if (block) blocks.push(block)
491
526
  }
492
527
 
493
- return syncFormDefinition({ title, description, blocks, jsonSchema: {} })
528
+ return syncFormDefinition({
529
+ title,
530
+ description,
531
+ blocks: applyDataModelDefaults(blocks, doc.dataModel),
532
+ jsonSchema: {},
533
+ })
494
534
  }
495
535
 
496
536
  /**
@@ -1,4 +1,4 @@
1
- import type { FormBlock, FormBlockType, FormSchema } from '../types/index'
1
+ import type { FormBlock, FormBlockType, FormCaseFilter, FormResumeFilter, FormSchema } from '../types/index'
2
2
  import { isInputBlockType, isMediaBlockType } from '../types/index'
3
3
  import { generateId } from './id'
4
4
  import {
@@ -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') {
32
+ if (type === 'multiselect' || type === 'checkbox' || type === 'resume_multiselect') {
33
33
  if (Array.isArray(raw)) return raw.map(String)
34
34
  if (typeof raw === 'string' && raw) return [raw]
35
35
  return []
@@ -39,6 +39,44 @@ function normalizeDefaultValue(
39
39
  return String(raw)
40
40
  }
41
41
 
42
+ function normalizeCaseFilter(raw: FormCaseFilter | undefined): FormCaseFilter {
43
+ const pageSizeRaw = typeof raw?.pageSize === 'number' ? raw.pageSize : Number(raw?.pageSize)
44
+ const pageSize =
45
+ Number.isFinite(pageSizeRaw) && pageSizeRaw >= 1 && pageSizeRaw <= 100
46
+ ? Math.floor(pageSizeRaw)
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
+ const agentKind =
52
+ typeof raw?.agentKind === 'string' && raw.agentKind.trim()
53
+ ? raw.agentKind.trim()
54
+ : undefined
55
+ const agentUniqueId =
56
+ typeof raw?.agentUniqueId === 'string' && raw.agentUniqueId.trim()
57
+ ? raw.agentUniqueId.trim()
58
+ : undefined
59
+ return { agentKind, agentUniqueId, flowId, pageSize }
60
+ }
61
+
62
+ function normalizeResumeFilter(raw: FormResumeFilter | undefined): FormResumeFilter {
63
+ const pageSizeRaw = typeof raw?.pageSize === 'number' ? raw.pageSize : Number(raw?.pageSize)
64
+ const pageSize =
65
+ Number.isFinite(pageSizeRaw) && pageSizeRaw >= 1 && pageSizeRaw <= 100
66
+ ? Math.floor(pageSizeRaw)
67
+ : 20
68
+ return {
69
+ names: Array.isArray(raw?.names) ? raw!.names.map(String).filter(Boolean) : [],
70
+ agentUniqueIds: Array.isArray(raw?.agentUniqueIds)
71
+ ? raw!.agentUniqueIds.map(String).filter(Boolean)
72
+ : [],
73
+ resumeUniqueIds: Array.isArray(raw?.resumeUniqueIds)
74
+ ? raw!.resumeUniqueIds.map(String).filter(Boolean)
75
+ : [],
76
+ pageSize,
77
+ }
78
+ }
79
+
42
80
  function normalizeBlock(raw: FormBlock): FormBlock {
43
81
  const fileAcceptTypes = normalizeFileAcceptTypes(raw.fileAcceptTypes)
44
82
  const type = raw.type
@@ -69,6 +107,14 @@ function normalizeBlock(raw: FormBlock): FormBlock {
69
107
  label: opt.label?.trim() || `选项 ${index + 1}`,
70
108
  }))
71
109
  : undefined,
110
+ resumeEnableRefresh:
111
+ type === 'resume_multiselect' ? raw.resumeEnableRefresh !== false : undefined,
112
+ resumeFilter:
113
+ type === 'resume_multiselect' ? normalizeResumeFilter(raw.resumeFilter) : undefined,
114
+ caseEnableRefresh:
115
+ type === 'case_singleselect' ? raw.caseEnableRefresh !== false : undefined,
116
+ caseFilter:
117
+ type === 'case_singleselect' ? normalizeCaseFilter(raw.caseFilter) : undefined,
72
118
  }
73
119
  }
74
120
 
@@ -135,6 +181,23 @@ export function createEmptyBlock(type: FormBlockType): FormBlock {
135
181
  block.fileMaxCount = 1
136
182
  }
137
183
 
184
+ if (type === 'resume_multiselect') {
185
+ block.resumeEnableRefresh = true
186
+ block.resumeFilter = {
187
+ names: [],
188
+ agentUniqueIds: [],
189
+ resumeUniqueIds: [],
190
+ pageSize: 20,
191
+ }
192
+ block.defaultValue = []
193
+ }
194
+
195
+ if (type === 'case_singleselect') {
196
+ block.caseEnableRefresh = true
197
+ block.caseFilter = { pageSize: 20 }
198
+ block.defaultValue = ''
199
+ }
200
+
138
201
  return block
139
202
  }
140
203
 
@@ -0,0 +1,74 @@
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
+ export function caseSingleselectToComponent(
9
+ block: FormBlock,
10
+ ctx: BlockAdapterContext,
11
+ ): BlockToComponentResult {
12
+ const { id, label, path, withData } = ctx
13
+ return withData({
14
+ id,
15
+ component: 'SkoponCaseSelect',
16
+ label,
17
+ placeholder: block.placeholder ?? '',
18
+ help: block.help ?? '',
19
+ enableRefresh: block.caseEnableRefresh !== false,
20
+ caseFilter: {
21
+ agentKind: block.caseFilter?.agentKind,
22
+ agentUniqueId: block.caseFilter?.agentUniqueId,
23
+ flowId: block.caseFilter?.flowId,
24
+ pageSize: block.caseFilter?.pageSize ?? 20,
25
+ },
26
+ value: { path },
27
+ })
28
+ }
29
+
30
+ export function caseSingleselectFromComponent(
31
+ node: A2uiComponentNode,
32
+ base: (type: 'case_singleselect') => FormBlock,
33
+ helpers: {
34
+ asLiteral: (value: unknown) => string | undefined
35
+ nameFromValue: (value: unknown) => string
36
+ },
37
+ ): FormBlock {
38
+ const name = helpers.nameFromValue(node.value)
39
+ const block: FormBlock = {
40
+ ...base('case_singleselect'),
41
+ name,
42
+ caseEnableRefresh: node.enableRefresh !== false,
43
+ }
44
+ const placeholder = helpers.asLiteral(node.placeholder)
45
+ if (placeholder) block.placeholder = placeholder
46
+ const help = helpers.asLiteral(node.help)
47
+ if (help) block.help = help
48
+ if (node.caseFilter && typeof node.caseFilter === 'object') {
49
+ const cf = node.caseFilter as Record<string, unknown>
50
+ const flowIdRaw = typeof cf.flowId === 'number' ? cf.flowId : Number(cf.flowId)
51
+ const pageSizeRaw = typeof cf.pageSize === 'number' ? cf.pageSize : Number(cf.pageSize)
52
+ const agentKind =
53
+ typeof cf.agentKind === 'string' && cf.agentKind.trim()
54
+ ? cf.agentKind.trim()
55
+ : undefined
56
+ const agentUniqueId =
57
+ typeof cf.agentUniqueId === 'string' && cf.agentUniqueId.trim()
58
+ ? cf.agentUniqueId.trim()
59
+ : undefined
60
+ block.caseFilter = {
61
+ agentKind,
62
+ agentUniqueId,
63
+ flowId:
64
+ Number.isFinite(flowIdRaw) && flowIdRaw > 0 ? Math.floor(flowIdRaw) : undefined,
65
+ pageSize:
66
+ Number.isFinite(pageSizeRaw) && pageSizeRaw >= 1 && pageSizeRaw <= 100
67
+ ? Math.floor(pageSizeRaw)
68
+ : 20,
69
+ }
70
+ }
71
+ return block
72
+ }
73
+
74
+ export { asLiteral }
@@ -0,0 +1 @@
1
+ export { SkoponCaseSelectImpl } from '../../catalog/skoponCaseSelect'
@@ -0,0 +1,14 @@
1
+ import type { BlockAdapterPlugin } from '../types'
2
+ import {
3
+ caseSingleselectFromComponent,
4
+ caseSingleselectToComponent,
5
+ } from './adapter'
6
+ import { SkoponCaseSelectImpl } from './catalog'
7
+
8
+ export const caseSingleselectAdapter: BlockAdapterPlugin = {
9
+ type: 'case_singleselect',
10
+ componentName: 'SkoponCaseSelect',
11
+ toComponent: caseSingleselectToComponent,
12
+ fromComponent: caseSingleselectFromComponent,
13
+ catalogComponents: [SkoponCaseSelectImpl],
14
+ }
@@ -0,0 +1,34 @@
1
+ import type { FormBlockType } from '../types/index'
2
+ import type { BlockAdapterPlugin } from './types'
3
+ import { caseSingleselectAdapter } from './case_singleselect'
4
+ import { resumeMultiselectAdapter } from './resume_multiselect'
5
+
6
+ const adapters: BlockAdapterPlugin[] = [resumeMultiselectAdapter, caseSingleselectAdapter]
7
+
8
+ const adapterByType = new Map<FormBlockType, BlockAdapterPlugin>(
9
+ adapters.map((adapter) => [adapter.type, adapter]),
10
+ )
11
+
12
+ const adapterByComponent = new Map<string, BlockAdapterPlugin>(
13
+ adapters.map((adapter) => [adapter.componentName, adapter]),
14
+ )
15
+
16
+ export function getBlockTypeAdapter(type: FormBlockType): BlockAdapterPlugin | undefined {
17
+ return adapterByType.get(type)
18
+ }
19
+
20
+ export function getComponentAdapter(componentName: string): BlockAdapterPlugin | undefined {
21
+ return adapterByComponent.get(componentName)
22
+ }
23
+
24
+ export function getAllBlockAdapters(): BlockAdapterPlugin[] {
25
+ return adapters
26
+ }
27
+
28
+ export function getRegisteredCatalogComponents() {
29
+ return adapters.flatMap((adapter) => adapter.catalogComponents)
30
+ }
31
+
32
+ export function getRegisteredCatalogComponentNames(): string[] {
33
+ return adapters.map((adapter) => adapter.componentName)
34
+ }
@@ -0,0 +1,57 @@
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
+ export function resumeMultiselectToComponent(
9
+ block: FormBlock,
10
+ ctx: BlockAdapterContext,
11
+ ): BlockToComponentResult {
12
+ const { id, label, path, withData } = ctx
13
+ return withData({
14
+ id,
15
+ component: 'SkoponResumeSelect',
16
+ label,
17
+ placeholder: block.placeholder ?? '',
18
+ help: block.help ?? '',
19
+ enableRefresh: block.resumeEnableRefresh !== false,
20
+ resumeFilter: {
21
+ names: block.resumeFilter?.names ?? [],
22
+ agentUniqueIds: block.resumeFilter?.agentUniqueIds ?? [],
23
+ resumeUniqueIds: block.resumeFilter?.resumeUniqueIds ?? [],
24
+ pageSize: block.resumeFilter?.pageSize ?? 20,
25
+ },
26
+ value: { path },
27
+ })
28
+ }
29
+
30
+ export function resumeMultiselectFromComponent(
31
+ node: A2uiComponentNode,
32
+ base: (type: 'resume_multiselect') => FormBlock,
33
+ helpers: { asLiteral: (value: unknown) => string | undefined; nameFromValue: (value: unknown) => string },
34
+ ): FormBlock {
35
+ const name = helpers.nameFromValue(node.value)
36
+ const block: FormBlock = {
37
+ ...base('resume_multiselect'),
38
+ name,
39
+ resumeEnableRefresh: node.enableRefresh !== false,
40
+ }
41
+ const placeholder = helpers.asLiteral(node.placeholder)
42
+ if (placeholder) block.placeholder = placeholder
43
+ const help = helpers.asLiteral(node.help)
44
+ if (help) block.help = help
45
+ if (node.resumeFilter && typeof node.resumeFilter === 'object') {
46
+ const rf = node.resumeFilter as Record<string, unknown>
47
+ block.resumeFilter = {
48
+ names: Array.isArray(rf.names) ? rf.names.map(String) : [],
49
+ agentUniqueIds: Array.isArray(rf.agentUniqueIds) ? rf.agentUniqueIds.map(String) : [],
50
+ resumeUniqueIds: Array.isArray(rf.resumeUniqueIds) ? rf.resumeUniqueIds.map(String) : [],
51
+ pageSize: typeof rf.pageSize === 'number' ? rf.pageSize : 20,
52
+ }
53
+ }
54
+ return block
55
+ }
56
+
57
+ export { asLiteral }
@@ -0,0 +1 @@
1
+ export { SkoponResumeSelectImpl } from '../../catalog/skoponResumeSelect'
@@ -0,0 +1,14 @@
1
+ import type { BlockAdapterPlugin } from '../types'
2
+ import {
3
+ resumeMultiselectFromComponent,
4
+ resumeMultiselectToComponent,
5
+ } from './adapter'
6
+ import { SkoponResumeSelectImpl } from './catalog'
7
+
8
+ export const resumeMultiselectAdapter: BlockAdapterPlugin = {
9
+ type: 'resume_multiselect',
10
+ componentName: 'SkoponResumeSelect',
11
+ toComponent: resumeMultiselectToComponent,
12
+ fromComponent: resumeMultiselectFromComponent,
13
+ catalogComponents: [SkoponResumeSelectImpl],
14
+ }
@@ -0,0 +1,34 @@
1
+ import type {
2
+ A2uiComponentNode,
3
+ FormBlock,
4
+ FormBlockType,
5
+ } from '../types/index'
6
+ import type { ReactComponentImplementation } from '@a2ui/react/v0_9'
7
+
8
+ export interface BlockAdapterContext {
9
+ id: string
10
+ label: string
11
+ name: string
12
+ path: string | undefined
13
+ withData: (node: A2uiComponentNode) => {
14
+ node: A2uiComponentNode
15
+ dataKey?: string
16
+ dataValue?: unknown
17
+ }
18
+ }
19
+
20
+ export type BlockToComponentResult =
21
+ | { node: A2uiComponentNode | null }
22
+ | ReturnType<BlockAdapterContext['withData']>
23
+
24
+ export interface BlockAdapterPlugin {
25
+ type: FormBlockType
26
+ componentName: string
27
+ toComponent: (block: FormBlock, ctx: BlockAdapterContext) => BlockToComponentResult
28
+ fromComponent: (
29
+ node: A2uiComponentNode,
30
+ base: (type: FormBlockType) => FormBlock,
31
+ helpers: { asLiteral: (value: unknown) => string | undefined; nameFromValue: (value: unknown) => string },
32
+ ) => FormBlock | null
33
+ catalogComponents: ReactComponentImplementation[]
34
+ }
@@ -26,6 +26,10 @@ import {
26
26
  formatFileAcceptSummary,
27
27
  } from '../adapter/formFileAccept'
28
28
  import { buildMediaListClassName, normalizeMediaSize } from '../adapter/formMedia'
29
+ import {
30
+ getRegisteredCatalogComponentNames,
31
+ getRegisteredCatalogComponents,
32
+ } from '../blocks/registry'
29
33
  import FilePlaceholderIcon from '../icons/FilePlaceholderIcon'
30
34
  import { useA2uiPreviewMode } from './a2uiPreviewContext'
31
35
  import {
@@ -53,6 +57,7 @@ const SKOPON_COMPONENT_NAMES = new Set([
53
57
  'FileUpload',
54
58
  'TextField',
55
59
  'Text',
60
+ ...getRegisteredCatalogComponentNames(),
56
61
  ])
57
62
 
58
63
  const NON_MARKDOWN_TEXT_VARIANTS = new Set(['h1', 'h2', 'h3', 'h4', 'h5', 'caption'])
@@ -691,6 +696,7 @@ export function buildSkoponCatalog(): Catalog<ReactComponentImplementation> {
691
696
  FileUploadImpl,
692
697
  TextFieldImpl,
693
698
  TextImpl,
699
+ ...getRegisteredCatalogComponents(),
694
700
  ]
695
701
  const functions = [...basicCatalog.functions.values()]
696
702
  return new Catalog(SKOPON_CATALOG_ID, components, functions, basicCatalog.themeSchema)
@@ -0,0 +1,46 @@
1
+ import { createContext, useContext, type ReactNode } from 'react'
2
+ import type { FormCaseFilter } from '../types/index'
3
+
4
+ export interface CaseSearchItem {
5
+ caseUniqueId: string
6
+ caseId?: number
7
+ flowId?: number
8
+ name: string
9
+ link?: string
10
+ platform?: string | null
11
+ description?: string
12
+ }
13
+
14
+ export interface CaseSearchParams {
15
+ filter: FormCaseFilter
16
+ page: number
17
+ }
18
+
19
+ export interface CaseSearchResult {
20
+ list: CaseSearchItem[]
21
+ total: number
22
+ page: number
23
+ pageSize: number
24
+ }
25
+
26
+ export type CaseSearchFn = (params: CaseSearchParams) => Promise<CaseSearchResult>
27
+
28
+ const CaseSearchContext = createContext<CaseSearchFn | null>(null)
29
+
30
+ export function CaseSearchProvider({
31
+ caseSearch,
32
+ children,
33
+ }: {
34
+ caseSearch: CaseSearchFn | null | undefined
35
+ children: ReactNode
36
+ }) {
37
+ return (
38
+ <CaseSearchContext.Provider value={caseSearch ?? null}>
39
+ {children}
40
+ </CaseSearchContext.Provider>
41
+ )
42
+ }
43
+
44
+ export function useCaseSearch(): CaseSearchFn | null {
45
+ return useContext(CaseSearchContext)
46
+ }