@skopon-cool/form-sdk 0.6.0 → 0.6.2

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 (56) hide show
  1. package/README.md +30 -2
  2. package/dist/adapter/formSchema.d.ts +3 -0
  3. package/dist/adapter/formSchema.d.ts.map +1 -1
  4. package/dist/blocks/resume_multiselect/adapter.d.ts.map +1 -1
  5. package/dist/catalog/contentSearchContext.d.ts +35 -0
  6. package/dist/catalog/contentSearchContext.d.ts.map +1 -0
  7. package/dist/catalog/pairResumeContent.d.ts +20 -0
  8. package/dist/catalog/pairResumeContent.d.ts.map +1 -0
  9. package/dist/catalog/skoponResumeSelect.d.ts.map +1 -1
  10. package/dist/client/authStorage.d.ts +9 -0
  11. package/dist/client/authStorage.d.ts.map +1 -1
  12. package/dist/client/contentSearchClient.d.ts +36 -0
  13. package/dist/client/contentSearchClient.d.ts.map +1 -0
  14. package/dist/client/resumeSearchClient.d.ts.map +1 -1
  15. package/dist/client/skoponSdkClients.d.ts +4 -0
  16. package/dist/client/skoponSdkClients.d.ts.map +1 -1
  17. package/dist/client/uploadClient.d.ts +1 -1
  18. package/dist/client/uploadClient.d.ts.map +1 -1
  19. package/dist/components/AskUserFormCard.d.ts +3 -0
  20. package/dist/components/AskUserFormCard.d.ts.map +1 -1
  21. package/dist/components/SkoponA2uiStreamRenderer.d.ts +4 -1
  22. package/dist/components/SkoponA2uiStreamRenderer.d.ts.map +1 -1
  23. package/dist/components/SkoponFormRenderer.d.ts +3 -0
  24. package/dist/components/SkoponFormRenderer.d.ts.map +1 -1
  25. package/dist/form-sdk.css +1 -1
  26. package/dist/index.d.ts +5 -2
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +1963 -1625
  29. package/dist/types/index.d.ts +25 -1
  30. package/dist/types/index.d.ts.map +1 -1
  31. package/package.json +1 -1
  32. package/src/adapter/formSchema.test.ts +8 -0
  33. package/src/adapter/formSchema.ts +49 -0
  34. package/src/blocks/resume_multiselect/adapter.ts +31 -0
  35. package/src/catalog/contentSearchContext.tsx +53 -0
  36. package/src/catalog/pairResumeContent.test.ts +34 -0
  37. package/src/catalog/pairResumeContent.ts +55 -0
  38. package/src/catalog/skoponResumeSelect.tsx +348 -153
  39. package/src/catalog/skoponSelectLoading.test.tsx +84 -5
  40. package/src/client/authStorage.test.ts +67 -1
  41. package/src/client/authStorage.ts +67 -0
  42. package/src/client/contentSearchClient.test.ts +123 -0
  43. package/src/client/contentSearchClient.ts +156 -0
  44. package/src/client/resumeSearchClient.test.ts +5 -0
  45. package/src/client/resumeSearchClient.ts +5 -0
  46. package/src/client/skoponSdkClients.ts +9 -0
  47. package/src/client/uploadClient.test.ts +41 -2
  48. package/src/client/uploadClient.ts +7 -3
  49. package/src/components/AskUserFormCard.tsx +5 -0
  50. package/src/components/SkoponA2uiStreamRenderer.tsx +15 -9
  51. package/src/components/SkoponFormRenderer.tsx +20 -12
  52. package/src/index.ts +19 -0
  53. package/src/styles/a2ui-preview.css +4 -4
  54. package/src/styles/index.css +158 -13
  55. package/src/submit/submit.test.ts +1 -0
  56. package/src/types/index.ts +26 -1
@@ -23,7 +23,13 @@ describe('uploadFileToCdn', () => {
23
23
  })
24
24
 
25
25
  it('uploads multipart data to the fixed endpoint with session Bearer auth', async () => {
26
- sessionStorage.setItem('auth_user', JSON.stringify({ token: 'token-1' }))
26
+ sessionStorage.setItem(
27
+ 'auth_user',
28
+ JSON.stringify({
29
+ token: 'token-1',
30
+ skopon_api_base_url: 'https://admin-test.hibot.lol/api/skopon/api/v1/',
31
+ }),
32
+ )
27
33
  const fetchMock = vi.fn(async () =>
28
34
  response({
29
35
  success: true,
@@ -39,7 +45,9 @@ describe('uploadFileToCdn', () => {
39
45
  })
40
46
  expect(fetchMock).toHaveBeenCalledTimes(1)
41
47
  const [url, init] = fetchMock.mock.calls[0] as unknown as [string, RequestInit]
42
- expect(url).toBe(CDN_UPLOAD_PATH)
48
+ expect(url).toBe(
49
+ `https://admin-test.hibot.lol/api/skopon/api/v1${CDN_UPLOAD_PATH}`,
50
+ )
43
51
  expect(init.method).toBe('POST')
44
52
  expect(init.headers).toEqual({ Authorization: 'Bearer token-1' })
45
53
  expect((init.headers as Record<string, string>)['Content-Type']).toBeUndefined()
@@ -48,6 +56,37 @@ describe('uploadFileToCdn', () => {
48
56
  expect((init.body as FormData).get('purpose')).toBeNull()
49
57
  })
50
58
 
59
+ it('falls back to the current origin API base when session config is missing', async () => {
60
+ const fetchMock = vi.fn(async () =>
61
+ response({ success: true, data: { url: 'https://cdn.example.com/a.txt' } }),
62
+ )
63
+ vi.stubGlobal('fetch', fetchMock)
64
+
65
+ await uploadFileToCdn(new File(['x'], 'x.txt'))
66
+
67
+ expect(fetchMock).toHaveBeenCalledWith(
68
+ `${window.location.origin}/api/v1${CDN_UPLOAD_PATH}`,
69
+ expect.objectContaining({ method: 'POST' }),
70
+ )
71
+ })
72
+
73
+ it('rejects an invalid configured base URL without sending a request', async () => {
74
+ sessionStorage.setItem(
75
+ 'auth_user',
76
+ JSON.stringify({
77
+ token: 'token-1',
78
+ skopon_api_base_url: '/api/v1',
79
+ }),
80
+ )
81
+ const fetchMock = vi.fn()
82
+ vi.stubGlobal('fetch', fetchMock)
83
+
84
+ await expect(uploadFileToCdn(new File(['x'], 'x.txt'))).rejects.toThrow(
85
+ 'skopon_api_base_url 格式无效,无法上传文件',
86
+ )
87
+ expect(fetchMock).not.toHaveBeenCalled()
88
+ })
89
+
51
90
  it.each([
52
91
  [{ success: false, message: '七牛上传失败', data: {} }, '七牛上传失败'],
53
92
  [{ success: true, data: { url: ' ' } }, '响应缺少文件地址'],
@@ -1,7 +1,10 @@
1
1
  import { parseEnvelope } from './apiEnvelope'
2
- import { resolveSkoponAuthHeaders } from './authStorage'
2
+ import {
3
+ resolveSkoponApiBaseUrl,
4
+ resolveSkoponAuthHeaders,
5
+ } from './authStorage'
3
6
 
4
- export const CDN_UPLOAD_PATH = '/api/v1/dev/upload/cdn'
7
+ export const CDN_UPLOAD_PATH = '/univ/upload/cdn'
5
8
  export const CDN_UPLOAD_TIMEOUT_MS = 120_000
6
9
 
7
10
  export interface CdnUploadResult {
@@ -27,6 +30,7 @@ export async function uploadFileToCdn(
27
30
  file: File,
28
31
  options: { signal?: AbortSignal } = {},
29
32
  ): Promise<CdnUploadResult> {
33
+ const uploadUrl = `${resolveSkoponApiBaseUrl()}${CDN_UPLOAD_PATH}`
30
34
  const controller = new AbortController()
31
35
  let timedOut = false
32
36
 
@@ -45,7 +49,7 @@ export async function uploadFileToCdn(
45
49
  try {
46
50
  const formData = new FormData()
47
51
  formData.append('file', file)
48
- const response = await fetch(CDN_UPLOAD_PATH, {
52
+ const response = await fetch(uploadUrl, {
49
53
  method: 'POST',
50
54
  headers: resolveSkoponAuthHeaders(),
51
55
  body: formData,
@@ -27,6 +27,7 @@ import {
27
27
  } from '../submit/submitAnswerAgent'
28
28
  import { validateFormSubmitValues } from '../submit/validateFormSubmit'
29
29
  import type { CaseSearchFn } from '../catalog/caseSearchContext'
30
+ import type { ContentSearchFn } from '../catalog/contentSearchContext'
30
31
  import type { ResumeSearchFn } from '../catalog/resumeSearchContext'
31
32
  import { FormRuntimeProvider, useFormRuntime } from '../runtime/formRuntimeContext'
32
33
  import { FormRuntimeWsClient } from '../runtime/formRuntimeWs'
@@ -75,6 +76,8 @@ export interface AskUserFormCardProps {
75
76
  onNotify?: (type: 'success' | 'error', message: string) => void
76
77
  /** 简历多选组件:注入搜索函数 */
77
78
  resumeSearch?: ResumeSearchFn | null
79
+ /** 简历多选组件:内容制作搜索 */
80
+ contentSearch?: ContentSearchFn | null
78
81
  /** 案例单选组件:注入搜索函数 */
79
82
  caseSearch?: CaseSearchFn | null
80
83
  }
@@ -95,6 +98,7 @@ function AskUserFormCardBody({
95
98
  fetchFormDetail,
96
99
  onNotify,
97
100
  resumeSearch = null,
101
+ contentSearch = null,
98
102
  caseSearch = null,
99
103
  onSurfaceReadyChange,
100
104
  onAutoControlReady,
@@ -491,6 +495,7 @@ function AskUserFormCardBody({
491
495
  interactive
492
496
  wrapProvider={false}
493
497
  resumeSearch={resumeSearch}
498
+ contentSearch={contentSearch}
494
499
  caseSearch={caseSearch}
495
500
  onUploadingChange={setUploading}
496
501
  />
@@ -7,6 +7,7 @@ import { buildSkoponCatalog } from '../catalog/a2uiCustomCatalog'
7
7
  import { A2uiPreviewModeProvider } from '../catalog/a2uiPreviewContext'
8
8
  import { FileUploadStatusProvider } from '../catalog/fileUploadStatusContext'
9
9
  import { CaseSearchProvider, type CaseSearchFn } from '../catalog/caseSearchContext'
10
+ import { ContentSearchProvider, type ContentSearchFn } from '../catalog/contentSearchContext'
10
11
  import { ResumeSearchProvider, type ResumeSearchFn } from '../catalog/resumeSearchContext'
11
12
  import SkoponFormProvider from './SkoponFormProvider'
12
13
 
@@ -18,6 +19,8 @@ export interface SkoponA2uiStreamRendererProps {
18
19
  interactive?: boolean
19
20
  /** 简历多选组件:注入搜索函数 */
20
21
  resumeSearch?: ResumeSearchFn | null
22
+ /** 简历多选组件:内容制作搜索 */
23
+ contentSearch?: ContentSearchFn | null
21
24
  /** 案例单选组件:注入搜索函数 */
22
25
  caseSearch?: CaseSearchFn | null
23
26
  }
@@ -32,6 +35,7 @@ export default function SkoponA2uiStreamRenderer({
32
35
  emptyHint = null,
33
36
  interactive = true,
34
37
  resumeSearch = null,
38
+ contentSearch = null,
35
39
  caseSearch = null,
36
40
  }: SkoponA2uiStreamRendererProps) {
37
41
  useEffect(() => {
@@ -185,15 +189,17 @@ export default function SkoponA2uiStreamRenderer({
185
189
  <SkoponFormProvider>
186
190
  <FileUploadStatusProvider>
187
191
  <ResumeSearchProvider resumeSearch={resumeSearch}>
188
- <CaseSearchProvider caseSearch={caseSearch}>
189
- <A2uiPreviewModeProvider interactive={interactive}>
190
- <div className="a2ui-surface a2ui-container">
191
- {surfaces.map((surface) => (
192
- <A2uiSurface key={surface.id} surface={surface} />
193
- ))}
194
- </div>
195
- </A2uiPreviewModeProvider>
196
- </CaseSearchProvider>
192
+ <ContentSearchProvider contentSearch={contentSearch}>
193
+ <CaseSearchProvider caseSearch={caseSearch}>
194
+ <A2uiPreviewModeProvider interactive={interactive}>
195
+ <div className="a2ui-surface a2ui-container">
196
+ {surfaces.map((surface) => (
197
+ <A2uiSurface key={surface.id} surface={surface} />
198
+ ))}
199
+ </div>
200
+ </A2uiPreviewModeProvider>
201
+ </CaseSearchProvider>
202
+ </ContentSearchProvider>
197
203
  </ResumeSearchProvider>
198
204
  </FileUploadStatusProvider>
199
205
  </SkoponFormProvider>
@@ -17,6 +17,7 @@ import { SKOPON_CATALOG_ID, buildSkoponCatalog } from '../catalog/a2uiCustomCata
17
17
  import { A2uiPreviewModeProvider } from '../catalog/a2uiPreviewContext'
18
18
  import { FileUploadStatusProvider } from '../catalog/fileUploadStatusContext'
19
19
  import { CaseSearchProvider, type CaseSearchFn } from '../catalog/caseSearchContext'
20
+ import { ContentSearchProvider, type ContentSearchFn } from '../catalog/contentSearchContext'
20
21
  import { ResumeSearchProvider, type ResumeSearchFn } from '../catalog/resumeSearchContext'
21
22
  import SkoponFormProvider from './SkoponFormProvider'
22
23
 
@@ -34,6 +35,8 @@ export interface SkoponFormRendererProps {
34
35
  fieldNames?: string[]
35
36
  /** 简历多选组件:注入搜索函数 */
36
37
  resumeSearch?: ResumeSearchFn | null
38
+ /** 简历多选组件:内容制作搜索 */
39
+ contentSearch?: ContentSearchFn | null
37
40
  /** 案例单选组件:注入搜索函数 */
38
41
  caseSearch?: CaseSearchFn | null
39
42
  /** 任意文件上传任务的聚合状态 */
@@ -51,6 +54,7 @@ const SkoponFormRenderer = forwardRef<SkoponFormRendererRef, SkoponFormRendererP
51
54
  interactive = true,
52
55
  fieldNames = [],
53
56
  resumeSearch = null,
57
+ contentSearch = null,
54
58
  caseSearch = null,
55
59
  onUploadingChange,
56
60
  wrapProvider = true,
@@ -128,9 +132,11 @@ const SkoponFormRenderer = forwardRef<SkoponFormRendererRef, SkoponFormRendererP
128
132
  const empty = (
129
133
  <FileUploadStatusProvider onUploadingChange={onUploadingChange}>
130
134
  <ResumeSearchProvider resumeSearch={resumeSearch}>
131
- <CaseSearchProvider caseSearch={caseSearch}>
132
- <A2uiPreviewModeProvider interactive={interactive}>{emptyHint}</A2uiPreviewModeProvider>
133
- </CaseSearchProvider>
135
+ <ContentSearchProvider contentSearch={contentSearch}>
136
+ <CaseSearchProvider caseSearch={caseSearch}>
137
+ <A2uiPreviewModeProvider interactive={interactive}>{emptyHint}</A2uiPreviewModeProvider>
138
+ </CaseSearchProvider>
139
+ </ContentSearchProvider>
134
140
  </ResumeSearchProvider>
135
141
  </FileUploadStatusProvider>
136
142
  )
@@ -140,15 +146,17 @@ const SkoponFormRenderer = forwardRef<SkoponFormRendererRef, SkoponFormRendererP
140
146
  const content = (
141
147
  <FileUploadStatusProvider onUploadingChange={onUploadingChange}>
142
148
  <ResumeSearchProvider resumeSearch={resumeSearch}>
143
- <CaseSearchProvider caseSearch={caseSearch}>
144
- <A2uiPreviewModeProvider interactive={interactive}>
145
- <div className="a2ui-surface a2ui-container">
146
- {surfaces.map((surface) => (
147
- <A2uiSurface key={surface.id} surface={surface} />
148
- ))}
149
- </div>
150
- </A2uiPreviewModeProvider>
151
- </CaseSearchProvider>
149
+ <ContentSearchProvider contentSearch={contentSearch}>
150
+ <CaseSearchProvider caseSearch={caseSearch}>
151
+ <A2uiPreviewModeProvider interactive={interactive}>
152
+ <div className="a2ui-surface a2ui-container">
153
+ {surfaces.map((surface) => (
154
+ <A2uiSurface key={surface.id} surface={surface} />
155
+ ))}
156
+ </div>
157
+ </A2uiPreviewModeProvider>
158
+ </CaseSearchProvider>
159
+ </ContentSearchProvider>
152
160
  </ResumeSearchProvider>
153
161
  </FileUploadStatusProvider>
154
162
  )
package/src/index.ts CHANGED
@@ -8,6 +8,7 @@ export type {
8
8
  FormBlockOption,
9
9
  FormBlockType,
10
10
  FormCaseFilter,
11
+ FormContentFilter,
11
12
  FormConditionOperator,
12
13
  FormDefinitionPayload,
13
14
  FormDetailResult,
@@ -106,6 +107,7 @@ export {
106
107
  export {
107
108
  resolveAnswerAgentCallbackUrl,
108
109
  resolveRealtimeWsUrl,
110
+ resolveSkoponApiBaseUrl,
109
111
  resolveSkoponAuthHeaders,
110
112
  resolveSubmitAuthHeaders,
111
113
  resolveClientHeaders,
@@ -113,7 +115,9 @@ export {
113
115
  DEFAULT_AUTH_TOKEN_FIELD,
114
116
  DEFAULT_AUTH_ANSWER_AGENT_CALLBACK_URL_FIELD,
115
117
  DEFAULT_AUTH_REALTIME_WS_URL_FIELD,
118
+ DEFAULT_AUTH_SKOPON_API_BASE_URL_FIELD,
116
119
  DEFAULT_ANSWER_AGENT_CALLBACK_URL,
120
+ DEFAULT_SKOPON_API_BASE_PATH,
117
121
  type SkoponAuthStorageOptions,
118
122
  type ClientHeadersOptions,
119
123
  } from './client/authStorage'
@@ -121,6 +125,11 @@ export {
121
125
  createResumeSearchClient,
122
126
  type ResumeSearchClientOptions,
123
127
  } from './client/resumeSearchClient'
128
+ export {
129
+ createContentSearchClient,
130
+ DEFAULT_CONTENT_FLOW_TYPE,
131
+ type ContentSearchClientOptions,
132
+ } from './client/contentSearchClient'
124
133
  export {
125
134
  createCaseSearchClient,
126
135
  type CaseSearchClientOptions,
@@ -134,6 +143,7 @@ export {
134
143
  export { default as SkoponFormRenderer } from './components/SkoponFormRenderer'
135
144
  export type { SkoponFormRendererProps, SkoponFormRendererRef } from './components/SkoponFormRenderer'
136
145
  export type { ResumeSearchFn as SkoponResumeSearchFn } from './catalog/resumeSearchContext'
146
+ export type { ContentSearchFn as SkoponContentSearchFn } from './catalog/contentSearchContext'
137
147
  export type { CaseSearchFn as SkoponCaseSearchFn } from './catalog/caseSearchContext'
138
148
 
139
149
  export { default as SkoponA2uiStreamRenderer } from './components/SkoponA2uiStreamRenderer'
@@ -152,6 +162,15 @@ export { antdTheme as skoponAntdTheme } from './theme/antd-theme'
152
162
  export { brandColors as skoponBrandColors } from './theme/brand-colors'
153
163
 
154
164
 
165
+ export {
166
+ ContentSearchProvider,
167
+ useContentSearch,
168
+ type ContentSearchFn,
169
+ type ContentSearchItem,
170
+ type ContentSearchParams,
171
+ type ContentSearchResult,
172
+ } from './catalog/contentSearchContext'
173
+
155
174
  export {
156
175
  ResumeSearchProvider,
157
176
  useResumeSearch,
@@ -255,7 +255,7 @@
255
255
  }
256
256
 
257
257
  /* 原生 A2UI 按钮/chip;勿作用于 Ant Design(Switch、Select、Picker 等)及 Skopon 简历/案例卡片 */
258
- .a2ui-surface.a2ui-container button:not([class*='ant-']):not(.skopon-resume-select-card) {
258
+ .a2ui-surface.a2ui-container button:not([class*='ant-']):not(.skopon-resume-select-card):not(.skopon-resume-select-card-stage-button) {
259
259
  min-height: var(--button-height-md);
260
260
  padding: 0 var(--button-padding-x-md);
261
261
  font-family: var(--font-sans);
@@ -273,12 +273,12 @@
273
273
  color var(--duration-fast) var(--ease-default);
274
274
  }
275
275
 
276
- .a2ui-surface.a2ui-container button:not([class*='ant-']):not(.skopon-resume-select-card):hover:not(:disabled) {
276
+ .a2ui-surface.a2ui-container button:not([class*='ant-']):not(.skopon-resume-select-card):not(.skopon-resume-select-card-stage-button):hover:not(:disabled) {
277
277
  border-color: var(--color-border-strong);
278
278
  background: var(--color-bg-subtle);
279
279
  }
280
280
 
281
- .a2ui-surface.a2ui-container button:not([class*='ant-']):not(.skopon-resume-select-card):disabled {
281
+ .a2ui-surface.a2ui-container button:not([class*='ant-']):not(.skopon-resume-select-card):not(.skopon-resume-select-card-stage-button):disabled {
282
282
  cursor: not-allowed;
283
283
  opacity: 0.45;
284
284
  }
@@ -313,7 +313,7 @@
313
313
  color: var(--color-danger-text) !important;
314
314
  }
315
315
 
316
- .a2ui-surface.a2ui-container img:not(.form-media-item) {
316
+ .a2ui-surface.a2ui-container img:not(.form-media-item):not(.skopon-resume-select-card-cover-img) {
317
317
  max-width: 100%;
318
318
  height: auto;
319
319
  border-radius: var(--radius-md);
@@ -286,6 +286,7 @@
286
286
  display: flex;
287
287
  flex-direction: row;
288
288
  flex-wrap: nowrap;
289
+ align-items: flex-start;
289
290
  gap: var(--space-3, 12px);
290
291
  margin-top: var(--space-2, 8px);
291
292
  overflow-x: auto;
@@ -316,19 +317,163 @@
316
317
  }
317
318
  .skopon-resume-select-card--profile {
318
319
  flex-direction: column;
319
- align-items: center;
320
- width: 200px;
321
- min-width: 200px;
322
- max-width: 200px;
323
- height: 320px;
324
- min-height: 320px;
325
- max-height: 320px;
326
- padding: 10px;
320
+ align-items: stretch;
321
+ align-self: flex-start;
322
+ gap: 8px;
323
+ width: 240px;
324
+ min-width: 240px;
325
+ max-width: 240px;
326
+ height: fit-content;
327
+ min-height: 0;
328
+ max-height: none;
329
+ padding: 12px;
330
+ overflow: hidden;
331
+ border-radius: 8px;
332
+ }
333
+ .skopon-resume-select-card--disabled {
334
+ cursor: not-allowed;
335
+ opacity: 0.72;
336
+ }
337
+ .skopon-resume-select-card-person {
338
+ display: flex;
339
+ align-items: flex-start;
340
+ gap: 8px;
341
+ min-width: 0;
342
+ }
343
+ .skopon-resume-select-card-person-text {
344
+ flex: 1;
345
+ min-width: 0;
346
+ }
347
+ .skopon-resume-select-card-person-name {
348
+ font-size: 14px;
349
+ font-weight: 600;
350
+ line-height: 1.3;
351
+ text-align: left;
352
+ color: var(--color-text, rgba(0, 0, 0, 0.88));
353
+ overflow: hidden;
354
+ text-overflow: ellipsis;
355
+ white-space: nowrap;
356
+ }
357
+ .skopon-resume-select-card-specialties {
358
+ display: flex;
359
+ flex-wrap: wrap;
360
+ gap: 4px;
361
+ margin-top: 4px;
362
+ }
363
+ .skopon-resume-select-card-content-title {
364
+ flex: none;
365
+ margin: 0;
366
+ padding: 0;
367
+ font-size: 13px;
368
+ font-weight: 500;
369
+ line-height: 1.4;
370
+ text-align: left;
371
+ color: var(--color-text, rgba(0, 0, 0, 0.88));
372
+ display: -webkit-box;
373
+ -webkit-line-clamp: 2;
374
+ -webkit-box-orient: vertical;
327
375
  overflow: hidden;
328
376
  }
329
- .a2ui-surface.a2ui-container button.skopon-resume-select-card--profile {
330
- padding: 10px;
331
- min-height: 320px;
377
+ .skopon-resume-select-card-content-tags {
378
+ display: flex;
379
+ flex-direction: column;
380
+ gap: 4px;
381
+ min-width: 0;
382
+ }
383
+ .skopon-resume-select-card-content-tags-label {
384
+ color: var(--color-text-secondary, rgba(0, 0, 0, 0.45));
385
+ font-size: 11px;
386
+ line-height: 1.2;
387
+ }
388
+ .skopon-resume-select-card-content-tags-list {
389
+ display: flex;
390
+ flex-wrap: wrap;
391
+ gap: 4px;
392
+ }
393
+ .skopon-resume-select-card-content-tag {
394
+ margin: 0;
395
+ border-color: color-mix(in srgb, var(--color-primary, #208368) 24%, transparent);
396
+ background: color-mix(in srgb, var(--color-primary, #208368) 8%, transparent);
397
+ color: var(--color-primary, #208368);
398
+ font-size: 11px;
399
+ line-height: 18px;
400
+ }
401
+ .skopon-resume-select-card-stage {
402
+ position: relative;
403
+ flex: none;
404
+ align-self: stretch;
405
+ width: 100%;
406
+ height: auto;
407
+ aspect-ratio: 9 / 16;
408
+ margin: 0;
409
+ padding: 0;
410
+ overflow: hidden;
411
+ border-radius: 6px;
412
+ background: #111;
413
+ }
414
+ .skopon-resume-select-card-stage-button,
415
+ .skopon-resume-select-card-stage-video {
416
+ position: absolute;
417
+ top: 0;
418
+ right: 0;
419
+ bottom: 0;
420
+ left: 0;
421
+ display: block;
422
+ width: 100%;
423
+ height: 100%;
424
+ margin: 0;
425
+ padding: 0;
426
+ border: 0;
427
+ border-radius: 0;
428
+ background: #111;
429
+ cursor: pointer;
430
+ overflow: hidden;
431
+ }
432
+ .skopon-resume-select-card-stage-button:disabled {
433
+ cursor: default;
434
+ }
435
+ .a2ui-surface.a2ui-container .skopon-resume-select-card-stage img.skopon-resume-select-card-cover-img,
436
+ .a2ui-surface.a2ui-container .skopon-resume-select-card-stage video.skopon-resume-select-card-stage-video,
437
+ .skopon-resume-select-card-stage img.skopon-resume-select-card-cover-img,
438
+ .skopon-resume-select-card-stage video.skopon-resume-select-card-stage-video {
439
+ position: absolute;
440
+ top: 0;
441
+ right: 0;
442
+ bottom: 0;
443
+ left: 0;
444
+ display: block;
445
+ width: 100%;
446
+ height: 100%;
447
+ max-width: none;
448
+ max-height: none;
449
+ margin: 0;
450
+ border-radius: 0;
451
+ object-fit: cover;
452
+ object-position: center;
453
+ }
454
+ .skopon-resume-select-card-cover-placeholder {
455
+ position: absolute;
456
+ left: 50%;
457
+ top: 50%;
458
+ font-size: 22px;
459
+ color: rgba(255, 255, 255, 0.45);
460
+ transform: translate(-50%, -50%);
461
+ }
462
+ .skopon-resume-select-card-play {
463
+ position: absolute;
464
+ left: 50%;
465
+ top: 50%;
466
+ display: flex;
467
+ align-items: center;
468
+ justify-content: center;
469
+ width: 36px;
470
+ height: 36px;
471
+ margin: -18px 0 0 -18px;
472
+ border-radius: 50%;
473
+ background: rgba(0, 0, 0, 0.55);
474
+ color: #fff;
475
+ font-size: 16px;
476
+ pointer-events: none;
332
477
  }
333
478
  .skopon-resume-select-card:not(.skopon-resume-select-card--profile) {
334
479
  flex-direction: row;
@@ -342,7 +487,6 @@
342
487
  }
343
488
  .skopon-resume-select-card--selected {
344
489
  border-color: var(--color-primary, #208368);
345
- background: color-mix(in srgb, var(--color-primary, #208368) 8%, transparent);
346
490
  }
347
491
  .skopon-resume-select-card:disabled {
348
492
  cursor: not-allowed;
@@ -352,7 +496,8 @@
352
496
  flex-shrink: 0;
353
497
  }
354
498
  .skopon-resume-select-card--profile .skopon-resume-select-card-avatar {
355
- margin-bottom: 6px;
499
+ margin: 0;
500
+ flex-shrink: 0;
356
501
  }
357
502
  .skopon-resume-select-card-name {
358
503
  font-weight: 600;
@@ -401,6 +401,7 @@ describe('intersectPayloadBlocksWithForm', () => {
401
401
  expect(block?.resumeFilter).toEqual({
402
402
  names: [],
403
403
  agentUniqueIds: [],
404
+ tagUniqueIds: [],
404
405
  resumeUniqueIds: [],
405
406
  pageSize: 20,
406
407
  })
@@ -99,13 +99,32 @@ export interface FormBlockCondition {
99
99
  /** 简历多选组件:渲染时用于 /univ/resume/search 的筛选条件 */
100
100
  export interface FormResumeFilter {
101
101
  names?: string[]
102
- /** Agent 类型(编辑器辅助,运行时不用) */
102
+ /**
103
+ * 智能体类型 unique_id。
104
+ * 运行时作为 agent_type:先找出该类型的智能体,再查他们的简历。
105
+ */
103
106
  agentKind?: string
104
107
  agentUniqueIds?: string[]
108
+ /** 智能体标签 unique_id,多值 AND;与 agentKind 一起定位智能体后再查简历 */
109
+ tagUniqueIds?: string[]
105
110
  resumeUniqueIds?: string[]
106
111
  pageSize?: number
107
112
  }
108
113
 
114
+ /** 内容制作搜索:渲染时用于 /univ/flow/list?owner_scope=content */
115
+ export interface FormContentFilter {
116
+ /** 流程类型 unique_id,默认 content-production */
117
+ flowType?: string
118
+ /** 流程类别 unique_id */
119
+ category?: string
120
+ /** 内容标签 unique_id */
121
+ tagUniqueIds?: string[]
122
+ /** 多标签匹配方式;all=全部命中,any=命中任一 */
123
+ tagMatch?: 'all' | 'any'
124
+ /** 每次向内容接口请求的条数,不是一屏展示张数 */
125
+ pageSize?: number
126
+ }
127
+
109
128
  /** 案例多选组件:渲染时用于 /univ/case/list 的筛选条件 */
110
129
  export interface FormCaseFilter {
111
130
  /** Agent 类型(编辑器辅助,运行时不用) */
@@ -155,8 +174,14 @@ export interface FormBlock {
155
174
  resumeMinCount?: number
156
175
  /** 简历多选:最多选中数量,配置值 ≥ 1 */
157
176
  resumeMaxCount?: number
177
+ /** 简历多选:一轮展示几张,默认 5。不是最多可选数,也不是接口 page_size */
178
+ /** content-only 仅选内容,提交 flow_unique_id;默认仍为简历与内容组合。 */
179
+ resumeDisplayMode?: 'profile-content' | 'content-only'
180
+ resumeDisplayCount?: number
158
181
  /** 简历多选:列表筛选条件(持久化进 block json) */
159
182
  resumeFilter?: FormResumeFilter
183
+ /** 简历多选:内容制作筛选(持久化进 block json) */
184
+ contentFilter?: FormContentFilter
160
185
  /** 案例单选:是否显示「换一批」 */
161
186
  caseEnableRefresh?: boolean
162
187
  /** 案例多选:最少选中数量,配置值 ≥ 1 */