@skopon-cool/form-sdk 0.2.2 → 0.2.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.
- package/dist/client/authStorage.d.ts +7 -0
- package/dist/client/authStorage.d.ts.map +1 -1
- package/dist/components/AskUserFormCard.d.ts +3 -1
- package/dist/components/AskUserFormCard.d.ts.map +1 -1
- package/dist/components/CurlSubmitBlock.d.ts +3 -1
- package/dist/components/CurlSubmitBlock.d.ts.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1073 -904
- package/dist/submit/buildAnswerAgentBody.d.ts +23 -0
- package/dist/submit/buildAnswerAgentBody.d.ts.map +1 -0
- package/dist/submit/submitAnswerAgent.d.ts +13 -0
- package/dist/submit/submitAnswerAgent.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/client/authStorage.test.ts +31 -0
- package/src/client/authStorage.ts +44 -9
- package/src/components/AskUserFormCard.tsx +39 -11
- package/src/components/CurlSubmitBlock.tsx +70 -25
- package/src/index.ts +15 -0
- package/src/submit/buildAnswerAgentBody.ts +98 -0
- package/src/submit/submit.test.ts +150 -0
- package/src/submit/submitAnswerAgent.ts +104 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { FormSchema } from '../types/index';
|
|
2
|
+
import { type PayloadBlocksDefinition } from './intersectPayloadBlocksWithForm';
|
|
3
|
+
export interface AskUserAnswerAgentContext {
|
|
4
|
+
missionId?: number | string | null;
|
|
5
|
+
tenantId?: number | null;
|
|
6
|
+
tenantUserId?: string | null;
|
|
7
|
+
requestId?: string | null;
|
|
8
|
+
askUserQueryDescription?: string | null;
|
|
9
|
+
callbackUrl?: string | null;
|
|
10
|
+
}
|
|
11
|
+
export interface BuildAnswerAgentBodyOptions {
|
|
12
|
+
context: AskUserAnswerAgentContext;
|
|
13
|
+
payload: unknown;
|
|
14
|
+
form?: PayloadBlocksDefinition | FormSchema | null;
|
|
15
|
+
answer: Record<string, unknown>;
|
|
16
|
+
}
|
|
17
|
+
/** 组装 answer-agent 外包消息体(与 mock AskUserAnswerMockVo 对齐)。 */
|
|
18
|
+
export declare function buildAnswerAgentBody({ context, payload, form, answer, }: BuildAnswerAgentBodyOptions): Record<string, unknown>;
|
|
19
|
+
/** 提交前校验 mock answer-agent 必填字段,返回错误文案或 null。 */
|
|
20
|
+
export declare function validateAnswerAgentBody(body: Record<string, unknown>): string | null;
|
|
21
|
+
/** 提交前校验上下文必填字段,返回错误文案或 null。 */
|
|
22
|
+
export declare function validateAnswerAgentContext(context: AskUserAnswerAgentContext): string | null;
|
|
23
|
+
//# sourceMappingURL=buildAnswerAgentBody.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buildAnswerAgentBody.d.ts","sourceRoot":"","sources":["../../src/submit/buildAnswerAgentBody.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAChD,OAAO,EAEL,KAAK,uBAAuB,EAC7B,MAAM,kCAAkC,CAAA;AAEzC,MAAM,WAAW,yBAAyB;IACxC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;IAClC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,uBAAuB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACvC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC5B;AAmBD,MAAM,WAAW,2BAA2B;IAC1C,OAAO,EAAE,yBAAyB,CAAA;IAClC,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,CAAC,EAAE,uBAAuB,GAAG,UAAU,GAAG,IAAI,CAAA;IAClD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAChC;AAED,4DAA4D;AAC5D,wBAAgB,oBAAoB,CAAC,EACnC,OAAO,EACP,OAAO,EACP,IAAI,EACJ,MAAM,GACP,EAAE,2BAA2B,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAevD;AAED,iDAAiD;AACjD,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,IAAI,CAqBpF;AAED,iCAAiC;AACjC,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,yBAAyB,GACjC,MAAM,GAAG,IAAI,CAQf"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type SkoponAuthStorageOptions } from '../client/authStorage';
|
|
2
|
+
import { type SubmitFormJsonOptions, type SubmitFormJsonResult } from './submitFormJson';
|
|
3
|
+
export interface SubmitAnswerAgentOptions extends SubmitFormJsonOptions {
|
|
4
|
+
authStorage?: SkoponAuthStorageOptions;
|
|
5
|
+
/** 显式覆盖 auth_user 中的 answer_agent_callback_url */
|
|
6
|
+
answerAgentCallbackUrl?: string | null;
|
|
7
|
+
}
|
|
8
|
+
export declare function formatAnswerAgentSubmitMessage(result: SubmitFormJsonResult): {
|
|
9
|
+
type: 'success' | 'error';
|
|
10
|
+
message: string;
|
|
11
|
+
};
|
|
12
|
+
export declare function submitAnswerAgent(body: Record<string, unknown>, options?: SubmitAnswerAgentOptions): Promise<SubmitFormJsonResult>;
|
|
13
|
+
//# sourceMappingURL=submitAnswerAgent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"submitAnswerAgent.d.ts","sourceRoot":"","sources":["../../src/submit/submitAnswerAgent.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,wBAAwB,EAC9B,MAAM,uBAAuB,CAAA;AAE9B,OAAO,EAEL,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EAC1B,MAAM,kBAAkB,CAAA;AAEzB,MAAM,WAAW,wBAAyB,SAAQ,qBAAqB;IACrE,WAAW,CAAC,EAAE,wBAAwB,CAAA;IACtC,kDAAkD;IAClD,sBAAsB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACvC;AAqCD,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,oBAAoB,GAC3B;IAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAchD;AAED,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,wBAAwB,GACjC,OAAO,CAAC,oBAAoB,CAAC,CA4B/B"}
|
package/package.json
CHANGED
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { afterEach, describe, expect, it } from 'vitest'
|
|
5
5
|
import {
|
|
6
|
+
DEFAULT_AUTH_ANSWER_AGENT_CALLBACK_URL_FIELD,
|
|
7
|
+
DEFAULT_ANSWER_AGENT_CALLBACK_URL,
|
|
6
8
|
DEFAULT_AUTH_STORAGE_KEY,
|
|
9
|
+
resolveAnswerAgentCallbackUrl,
|
|
7
10
|
resolveSkoponAuthHeaders,
|
|
8
11
|
} from './authStorage'
|
|
9
12
|
|
|
@@ -59,3 +62,31 @@ describe('resolveSkoponAuthHeaders', () => {
|
|
|
59
62
|
})
|
|
60
63
|
})
|
|
61
64
|
})
|
|
65
|
+
|
|
66
|
+
describe('resolveAnswerAgentCallbackUrl', () => {
|
|
67
|
+
afterEach(() => {
|
|
68
|
+
sessionStorage.clear()
|
|
69
|
+
localStorage.clear()
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('reads answer_agent_callback_url from sessionStorage auth_user', () => {
|
|
73
|
+
sessionStorage.setItem(
|
|
74
|
+
DEFAULT_AUTH_STORAGE_KEY,
|
|
75
|
+
JSON.stringify({
|
|
76
|
+
token: 'tok',
|
|
77
|
+
[DEFAULT_AUTH_ANSWER_AGENT_CALLBACK_URL_FIELD]:
|
|
78
|
+
'/api/v1/mock/ask_user_answer_mock',
|
|
79
|
+
}),
|
|
80
|
+
)
|
|
81
|
+
expect(resolveAnswerAgentCallbackUrl()).toBe('/api/v1/mock/ask_user_answer_mock')
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('returns default mock url when field is missing', () => {
|
|
85
|
+
sessionStorage.setItem(DEFAULT_AUTH_STORAGE_KEY, JSON.stringify({ token: 'tok' }))
|
|
86
|
+
expect(resolveAnswerAgentCallbackUrl()).toBe(DEFAULT_ANSWER_AGENT_CALLBACK_URL)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('returns default mock url when session is missing', () => {
|
|
90
|
+
expect(resolveAnswerAgentCallbackUrl()).toBe(DEFAULT_ANSWER_AGENT_CALLBACK_URL)
|
|
91
|
+
})
|
|
92
|
+
})
|
|
@@ -9,6 +9,9 @@ export interface SkoponAuthStorageOptions {
|
|
|
9
9
|
|
|
10
10
|
export const DEFAULT_AUTH_STORAGE_KEY = 'auth_user'
|
|
11
11
|
export const DEFAULT_AUTH_TOKEN_FIELD = 'token'
|
|
12
|
+
export const DEFAULT_AUTH_ANSWER_AGENT_CALLBACK_URL_FIELD = 'answer_agent_callback_url'
|
|
13
|
+
/** 与 skopon-admin 登录写入的 mock answer-agent 路径一致 */
|
|
14
|
+
export const DEFAULT_ANSWER_AGENT_CALLBACK_URL = '/api/v1/mock/ask_user_answer_mock'
|
|
12
15
|
|
|
13
16
|
function readStorage(
|
|
14
17
|
kind: 'sessionStorage' | 'localStorage',
|
|
@@ -21,19 +24,51 @@ function readStorage(
|
|
|
21
24
|
}
|
|
22
25
|
}
|
|
23
26
|
|
|
27
|
+
function readSessionJson(raw: string): Record<string, unknown> | null {
|
|
28
|
+
try {
|
|
29
|
+
const parsed = JSON.parse(raw) as unknown
|
|
30
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return null
|
|
31
|
+
return parsed as Record<string, unknown>
|
|
32
|
+
} catch {
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
24
37
|
function readTokenFromSessionJson(
|
|
25
38
|
raw: string,
|
|
26
39
|
tokenField: string,
|
|
27
40
|
): string {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
const parsed = readSessionJson(raw)
|
|
42
|
+
if (!parsed) return ''
|
|
43
|
+
const token = parsed[tokenField]
|
|
44
|
+
return typeof token === 'string'
|
|
45
|
+
? token.trim().replace(/^bearer\s+/i, '').trim()
|
|
46
|
+
: ''
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 从宿主登录缓存读取 answer_agent_callback_url(无则返回空串)。 */
|
|
50
|
+
export function resolveAnswerAgentCallbackUrl(
|
|
51
|
+
options: SkoponAuthStorageOptions & {
|
|
52
|
+
answerAgentCallbackUrlField?: string
|
|
53
|
+
} = {},
|
|
54
|
+
): string {
|
|
55
|
+
const storageKind = options.storage ?? 'sessionStorage'
|
|
56
|
+
const storageKey = options.storageKey ?? DEFAULT_AUTH_STORAGE_KEY
|
|
57
|
+
const field =
|
|
58
|
+
options.answerAgentCallbackUrlField ?? DEFAULT_AUTH_ANSWER_AGENT_CALLBACK_URL_FIELD
|
|
59
|
+
|
|
60
|
+
const storage = readStorage(storageKind)
|
|
61
|
+
if (!storage) return DEFAULT_ANSWER_AGENT_CALLBACK_URL
|
|
62
|
+
|
|
63
|
+
const raw = storage.getItem(storageKey)
|
|
64
|
+
if (!raw) return DEFAULT_ANSWER_AGENT_CALLBACK_URL
|
|
65
|
+
|
|
66
|
+
const parsed = readSessionJson(raw)
|
|
67
|
+
if (!parsed) return DEFAULT_ANSWER_AGENT_CALLBACK_URL
|
|
68
|
+
|
|
69
|
+
const url = parsed[field]
|
|
70
|
+
const trimmed = typeof url === 'string' ? url.trim() : ''
|
|
71
|
+
return trimmed || DEFAULT_ANSWER_AGENT_CALLBACK_URL
|
|
37
72
|
}
|
|
38
73
|
|
|
39
74
|
/** 从宿主登录缓存读取 Bearer token,返回 Authorization 头(无 token 时返回空对象)。 */
|
|
@@ -2,7 +2,10 @@ import { useEffect, useMemo, useRef, useState } from 'react'
|
|
|
2
2
|
import { Button, Spin, Tag, Tooltip } from 'antd'
|
|
3
3
|
import type { FormDetailResult, FormSchema, SubmitMode } from '../types/index'
|
|
4
4
|
import { buildAskUserSurface } from '../adapter/resolveSurface'
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
buildAnswerAgentBody,
|
|
7
|
+
type AskUserAnswerAgentContext,
|
|
8
|
+
} from '../submit/buildAnswerAgentBody'
|
|
6
9
|
import {
|
|
7
10
|
extractExtraBlockValues,
|
|
8
11
|
getPayloadInputFieldNames,
|
|
@@ -11,7 +14,11 @@ import {
|
|
|
11
14
|
parsePayloadBlocksJson,
|
|
12
15
|
payloadHasInputBlocks,
|
|
13
16
|
} from '../submit/intersectPayloadBlocksWithForm'
|
|
14
|
-
import {
|
|
17
|
+
import { submitFormJson } from '../submit/submitFormJson'
|
|
18
|
+
import {
|
|
19
|
+
formatAnswerAgentSubmitMessage,
|
|
20
|
+
submitAnswerAgent,
|
|
21
|
+
} from '../submit/submitAnswerAgent'
|
|
15
22
|
import { validateFormSubmitValues } from '../submit/validateFormSubmit'
|
|
16
23
|
import type { CaseSearchFn } from '../catalog/caseSearchContext'
|
|
17
24
|
import type { ResumeSearchFn } from '../catalog/resumeSearchContext'
|
|
@@ -38,6 +45,7 @@ export interface AskUserFormCardProps {
|
|
|
38
45
|
formUniqueId?: string | null
|
|
39
46
|
callbackUrl?: string | null
|
|
40
47
|
submitMode?: SubmitMode
|
|
48
|
+
answerAgentContext?: AskUserAnswerAgentContext
|
|
41
49
|
/** 需 useCallback 稳定引用,避免重复拉取 */
|
|
42
50
|
fetchFormDetail: (ref: { formUniqueId: string }) => Promise<FormDetailResult>
|
|
43
51
|
onNotify?: (type: 'success' | 'error', message: string) => void
|
|
@@ -52,6 +60,7 @@ export default function AskUserFormCard({
|
|
|
52
60
|
formUniqueId,
|
|
53
61
|
callbackUrl,
|
|
54
62
|
submitMode = 'curl',
|
|
63
|
+
answerAgentContext,
|
|
55
64
|
fetchFormDetail,
|
|
56
65
|
onNotify,
|
|
57
66
|
resumeSearch = null,
|
|
@@ -68,6 +77,14 @@ export default function AskUserFormCard({
|
|
|
68
77
|
|
|
69
78
|
const resolvedFormUniqueId = formUniqueId?.trim() ?? ''
|
|
70
79
|
|
|
80
|
+
const resolvedAnswerAgentContext = useMemo(
|
|
81
|
+
(): AskUserAnswerAgentContext => ({
|
|
82
|
+
...answerAgentContext,
|
|
83
|
+
callbackUrl: answerAgentContext?.callbackUrl ?? callbackUrl ?? null,
|
|
84
|
+
}),
|
|
85
|
+
[answerAgentContext, callbackUrl],
|
|
86
|
+
)
|
|
87
|
+
|
|
71
88
|
useEffect(() => {
|
|
72
89
|
mountedRef.current = true
|
|
73
90
|
return () => {
|
|
@@ -170,7 +187,7 @@ export default function AskUserFormCard({
|
|
|
170
187
|
<CurlSubmitBlock
|
|
171
188
|
payload={payload}
|
|
172
189
|
callbackUrl={callbackUrl}
|
|
173
|
-
|
|
190
|
+
answerAgentContext={resolvedAnswerAgentContext}
|
|
174
191
|
unpublishedFormId={formDisabled && resolvedFormUniqueId ? resolvedFormUniqueId : undefined}
|
|
175
192
|
onNotify={onNotify}
|
|
176
193
|
/>
|
|
@@ -188,7 +205,7 @@ export default function AskUserFormCard({
|
|
|
188
205
|
onNotifyRef.current?.('error', validationError.message)
|
|
189
206
|
return
|
|
190
207
|
}
|
|
191
|
-
const
|
|
208
|
+
const answer = { ...cardValues, ...extraValues }
|
|
192
209
|
|
|
193
210
|
if (submitMode === 'post') {
|
|
194
211
|
const url = (callbackUrl ?? '').trim()
|
|
@@ -196,7 +213,7 @@ export default function AskUserFormCard({
|
|
|
196
213
|
onNotifyRef.current?.('error', 'callback_url 为空,无法提交')
|
|
197
214
|
return
|
|
198
215
|
}
|
|
199
|
-
const result = await submitFormJson(url,
|
|
216
|
+
const result = await submitFormJson(url, answer)
|
|
200
217
|
if (!mountedRef.current) return
|
|
201
218
|
if (result.ok) {
|
|
202
219
|
onNotifyRef.current?.('success', '提交成功')
|
|
@@ -206,14 +223,19 @@ export default function AskUserFormCard({
|
|
|
206
223
|
return
|
|
207
224
|
}
|
|
208
225
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
226
|
+
const body = buildAnswerAgentBody({
|
|
227
|
+
context: resolvedAnswerAgentContext,
|
|
228
|
+
payload,
|
|
229
|
+
form: formDef ?? payloadDef,
|
|
230
|
+
answer,
|
|
231
|
+
})
|
|
232
|
+
const result = await submitAnswerAgent(body)
|
|
212
233
|
if (!mountedRef.current) return
|
|
213
|
-
|
|
234
|
+
const notify = formatAnswerAgentSubmitMessage(result)
|
|
235
|
+
onNotifyRef.current?.(notify.type, notify.message)
|
|
214
236
|
} catch (error) {
|
|
215
237
|
if (!mountedRef.current) return
|
|
216
|
-
const detail = error instanceof Error ? error.message : '
|
|
238
|
+
const detail = error instanceof Error ? error.message : '提交失败'
|
|
217
239
|
onNotifyRef.current?.('error', detail)
|
|
218
240
|
} finally {
|
|
219
241
|
if (mountedRef.current) setSubmitting(false)
|
|
@@ -242,7 +264,13 @@ export default function AskUserFormCard({
|
|
|
242
264
|
/>
|
|
243
265
|
|
|
244
266
|
<div className="ask-user-form-actions">
|
|
245
|
-
<Tooltip
|
|
267
|
+
<Tooltip
|
|
268
|
+
title={
|
|
269
|
+
submitMode === 'post'
|
|
270
|
+
? '提交 JSON 到 callback_url'
|
|
271
|
+
: '提交到 answer_agent_callback_url'
|
|
272
|
+
}
|
|
273
|
+
>
|
|
246
274
|
<Button
|
|
247
275
|
type="primary"
|
|
248
276
|
size="small"
|
|
@@ -1,10 +1,18 @@
|
|
|
1
|
-
import { useMemo } from 'react'
|
|
1
|
+
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
2
2
|
import { Button, Tag, Tooltip, Typography } from 'antd'
|
|
3
|
+
import { buildCurlStatement } from '../submit/buildCurlStatement'
|
|
3
4
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from '../submit/
|
|
7
|
-
import {
|
|
5
|
+
buildAnswerAgentBody,
|
|
6
|
+
type AskUserAnswerAgentContext,
|
|
7
|
+
} from '../submit/buildAnswerAgentBody'
|
|
8
|
+
import {
|
|
9
|
+
extractExtraBlockValues,
|
|
10
|
+
parsePayloadBlocksJson,
|
|
11
|
+
} from '../submit/intersectPayloadBlocksWithForm'
|
|
12
|
+
import {
|
|
13
|
+
formatAnswerAgentSubmitMessage,
|
|
14
|
+
submitAnswerAgent,
|
|
15
|
+
} from '../submit/submitAnswerAgent'
|
|
8
16
|
|
|
9
17
|
export type CurlSubmitBlockCopyMode = 'display' | 'submitFields'
|
|
10
18
|
|
|
@@ -16,43 +24,74 @@ export interface CurlSubmitBlockProps {
|
|
|
16
24
|
incompleteFormId?: string | null
|
|
17
25
|
/** display:复制与显示一致;submitFields:复制向 callback 提交返回字段的 curl */
|
|
18
26
|
copyMode?: CurlSubmitBlockCopyMode
|
|
27
|
+
answerAgentContext?: AskUserAnswerAgentContext
|
|
19
28
|
onNotify?: (type: 'success' | 'error', message: string) => void
|
|
20
29
|
}
|
|
21
30
|
|
|
31
|
+
function resolveAnswerFromPayload(payload: unknown): Record<string, unknown> {
|
|
32
|
+
const def = parsePayloadBlocksJson(payload)
|
|
33
|
+
if (def) return extractExtraBlockValues(def.blocks)
|
|
34
|
+
if (payload && typeof payload === 'object' && !Array.isArray(payload)) {
|
|
35
|
+
return payload as Record<string, unknown>
|
|
36
|
+
}
|
|
37
|
+
return {}
|
|
38
|
+
}
|
|
39
|
+
|
|
22
40
|
export default function CurlSubmitBlock({
|
|
23
41
|
payload,
|
|
24
42
|
callbackUrl,
|
|
25
43
|
title,
|
|
26
44
|
unpublishedFormId,
|
|
27
45
|
incompleteFormId,
|
|
28
|
-
|
|
46
|
+
answerAgentContext,
|
|
29
47
|
onNotify,
|
|
30
48
|
}: CurlSubmitBlockProps) {
|
|
49
|
+
const [submitting, setSubmitting] = useState(false)
|
|
50
|
+
const mountedRef = useRef(true)
|
|
51
|
+
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
mountedRef.current = true
|
|
54
|
+
return () => {
|
|
55
|
+
mountedRef.current = false
|
|
56
|
+
}
|
|
57
|
+
}, [])
|
|
58
|
+
|
|
31
59
|
const displayCurl = useMemo(
|
|
32
60
|
() => buildCurlStatement(payload, callbackUrl),
|
|
33
61
|
[payload, callbackUrl],
|
|
34
62
|
)
|
|
35
|
-
const copyCurl = useMemo(() => {
|
|
36
|
-
if (copyMode === 'submitFields') {
|
|
37
|
-
return buildAskUserSubmitCurlFromPayload(payload, callbackUrl)
|
|
38
|
-
}
|
|
39
|
-
return displayCurl
|
|
40
|
-
}, [copyMode, payload, callbackUrl, displayCurl])
|
|
41
63
|
|
|
42
|
-
|
|
64
|
+
const resolvedContext = useMemo(
|
|
65
|
+
(): AskUserAnswerAgentContext => ({
|
|
66
|
+
...answerAgentContext,
|
|
67
|
+
callbackUrl: answerAgentContext?.callbackUrl ?? callbackUrl ?? null,
|
|
68
|
+
}),
|
|
69
|
+
[answerAgentContext, callbackUrl],
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
async function handleSubmit() {
|
|
73
|
+
if (submitting) return
|
|
74
|
+
setSubmitting(true)
|
|
43
75
|
try {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
76
|
+
const answer = resolveAnswerFromPayload(payload)
|
|
77
|
+
const body = buildAnswerAgentBody({
|
|
78
|
+
context: resolvedContext,
|
|
79
|
+
payload,
|
|
80
|
+
answer,
|
|
81
|
+
})
|
|
82
|
+
const result = await submitAnswerAgent(body)
|
|
83
|
+
if (!mountedRef.current) return
|
|
84
|
+
const notify = formatAnswerAgentSubmitMessage(result)
|
|
85
|
+
onNotify?.(notify.type, notify.message)
|
|
86
|
+
} catch (error) {
|
|
87
|
+
if (!mountedRef.current) return
|
|
88
|
+
const detail = error instanceof Error ? error.message : '提交失败'
|
|
89
|
+
onNotify?.('error', detail)
|
|
90
|
+
} finally {
|
|
91
|
+
if (mountedRef.current) setSubmitting(false)
|
|
48
92
|
}
|
|
49
93
|
}
|
|
50
94
|
|
|
51
|
-
const copyTooltip =
|
|
52
|
-
copyMode === 'submitFields'
|
|
53
|
-
? '复制提交字段 curl(向 callback 回传)'
|
|
54
|
-
: '复制 curl'
|
|
55
|
-
|
|
56
95
|
return (
|
|
57
96
|
<div className="ask-user-curl-card">
|
|
58
97
|
<div className="ask-user-curl-card-header">
|
|
@@ -69,9 +108,15 @@ export default function CurlSubmitBlock({
|
|
|
69
108
|
</Tooltip>
|
|
70
109
|
) : null}
|
|
71
110
|
</div>
|
|
72
|
-
<Tooltip title=
|
|
73
|
-
<Button
|
|
74
|
-
|
|
111
|
+
<Tooltip title="提交到 answer_agent_callback_url">
|
|
112
|
+
<Button
|
|
113
|
+
size="small"
|
|
114
|
+
type="text"
|
|
115
|
+
loading={submitting}
|
|
116
|
+
disabled={submitting}
|
|
117
|
+
onClick={() => void handleSubmit()}
|
|
118
|
+
>
|
|
119
|
+
提交
|
|
75
120
|
</Button>
|
|
76
121
|
</Tooltip>
|
|
77
122
|
</div>
|
package/src/index.ts
CHANGED
|
@@ -66,10 +66,25 @@ export {
|
|
|
66
66
|
|
|
67
67
|
export { createFormClient, type FormClient, type FormClientOptions } from './client/formClient'
|
|
68
68
|
export {
|
|
69
|
+
buildAnswerAgentBody,
|
|
70
|
+
validateAnswerAgentBody,
|
|
71
|
+
validateAnswerAgentContext,
|
|
72
|
+
type AskUserAnswerAgentContext,
|
|
73
|
+
type BuildAnswerAgentBodyOptions,
|
|
74
|
+
} from './submit/buildAnswerAgentBody'
|
|
75
|
+
export {
|
|
76
|
+
submitAnswerAgent,
|
|
77
|
+
formatAnswerAgentSubmitMessage,
|
|
78
|
+
type SubmitAnswerAgentOptions,
|
|
79
|
+
} from './submit/submitAnswerAgent'
|
|
80
|
+
export {
|
|
81
|
+
resolveAnswerAgentCallbackUrl,
|
|
69
82
|
resolveSkoponAuthHeaders,
|
|
70
83
|
resolveClientHeaders,
|
|
71
84
|
DEFAULT_AUTH_STORAGE_KEY,
|
|
72
85
|
DEFAULT_AUTH_TOKEN_FIELD,
|
|
86
|
+
DEFAULT_AUTH_ANSWER_AGENT_CALLBACK_URL_FIELD,
|
|
87
|
+
DEFAULT_ANSWER_AGENT_CALLBACK_URL,
|
|
73
88
|
type SkoponAuthStorageOptions,
|
|
74
89
|
type ClientHeadersOptions,
|
|
75
90
|
} from './client/authStorage'
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { FormSchema } from '../types/index'
|
|
2
|
+
import {
|
|
3
|
+
parsePayloadBlocksJson,
|
|
4
|
+
type PayloadBlocksDefinition,
|
|
5
|
+
} from './intersectPayloadBlocksWithForm'
|
|
6
|
+
|
|
7
|
+
export interface AskUserAnswerAgentContext {
|
|
8
|
+
missionId?: number | string | null
|
|
9
|
+
tenantId?: number | null
|
|
10
|
+
tenantUserId?: string | null
|
|
11
|
+
requestId?: string | null
|
|
12
|
+
askUserQueryDescription?: string | null
|
|
13
|
+
callbackUrl?: string | null
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function toBlocksJsonShape(
|
|
17
|
+
source: PayloadBlocksDefinition | FormSchema | unknown,
|
|
18
|
+
): unknown {
|
|
19
|
+
if (!source || typeof source !== 'object' || Array.isArray(source)) {
|
|
20
|
+
return source ?? null
|
|
21
|
+
}
|
|
22
|
+
const obj = source as Record<string, unknown>
|
|
23
|
+
if (!Array.isArray(obj.blocks)) {
|
|
24
|
+
return source
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
title: typeof obj.title === 'string' ? obj.title : '',
|
|
28
|
+
description: typeof obj.description === 'string' ? obj.description : '',
|
|
29
|
+
blocks: obj.blocks,
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface BuildAnswerAgentBodyOptions {
|
|
34
|
+
context: AskUserAnswerAgentContext
|
|
35
|
+
payload: unknown
|
|
36
|
+
form?: PayloadBlocksDefinition | FormSchema | null
|
|
37
|
+
answer: Record<string, unknown>
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** 组装 answer-agent 外包消息体(与 mock AskUserAnswerMockVo 对齐)。 */
|
|
41
|
+
export function buildAnswerAgentBody({
|
|
42
|
+
context,
|
|
43
|
+
payload,
|
|
44
|
+
form,
|
|
45
|
+
answer,
|
|
46
|
+
}: BuildAnswerAgentBodyOptions): Record<string, unknown> {
|
|
47
|
+
const question = parsePayloadBlocksJson(payload) ?? payload
|
|
48
|
+
const formBody = form ? toBlocksJsonShape(form) : question
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
mission_id: context.missionId ?? null,
|
|
52
|
+
tenant_id: context.tenantId ?? null,
|
|
53
|
+
tenant_user_id: context.tenantUserId ?? null,
|
|
54
|
+
request_id: context.requestId ?? null,
|
|
55
|
+
ask_user_query_description: context.askUserQueryDescription ?? null,
|
|
56
|
+
callback_url: (context.callbackUrl ?? '').trim() || null,
|
|
57
|
+
form: formBody,
|
|
58
|
+
question,
|
|
59
|
+
answer,
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** 提交前校验 mock answer-agent 必填字段,返回错误文案或 null。 */
|
|
64
|
+
export function validateAnswerAgentBody(body: Record<string, unknown>): string | null {
|
|
65
|
+
const callbackUrl = body.callback_url
|
|
66
|
+
if (typeof callbackUrl !== 'string' || !callbackUrl.trim()) {
|
|
67
|
+
return 'callback_url 为空,无法提交'
|
|
68
|
+
}
|
|
69
|
+
if (body.tenant_id == null) {
|
|
70
|
+
return 'tenant_id 缺失,无法提交'
|
|
71
|
+
}
|
|
72
|
+
const tenantUserId = body.tenant_user_id
|
|
73
|
+
if (typeof tenantUserId !== 'string' || !tenantUserId.trim()) {
|
|
74
|
+
return 'tenant_user_id 缺失,无法提交'
|
|
75
|
+
}
|
|
76
|
+
const requestId = body.request_id
|
|
77
|
+
if (typeof requestId !== 'string' || !requestId.trim()) {
|
|
78
|
+
return 'request_id 缺失,无法提交'
|
|
79
|
+
}
|
|
80
|
+
const missionId = body.mission_id
|
|
81
|
+
if (missionId == null || missionId === '') {
|
|
82
|
+
return 'mission_id 缺失,无法提交'
|
|
83
|
+
}
|
|
84
|
+
return null
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** 提交前校验上下文必填字段,返回错误文案或 null。 */
|
|
88
|
+
export function validateAnswerAgentContext(
|
|
89
|
+
context: AskUserAnswerAgentContext,
|
|
90
|
+
): string | null {
|
|
91
|
+
return validateAnswerAgentBody(
|
|
92
|
+
buildAnswerAgentBody({
|
|
93
|
+
context,
|
|
94
|
+
payload: {},
|
|
95
|
+
answer: {},
|
|
96
|
+
}),
|
|
97
|
+
)
|
|
98
|
+
}
|