@sapia-ai/chatbot-v3 1.0.0

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 (108) hide show
  1. package/dist/EmbeddedVideoPlayer-SaMMfDIW.js +1277 -0
  2. package/dist/EmbeddedVideoPlayer-SaMMfDIW.js.map +1 -0
  3. package/dist/Preview-Db-hgkTh.js +83 -0
  4. package/dist/Preview-Db-hgkTh.js.map +1 -0
  5. package/dist/api/client.d.ts +60 -0
  6. package/dist/api/config.d.ts +18 -0
  7. package/dist/api/constants.d.ts +5 -0
  8. package/dist/api/index.d.ts +5 -0
  9. package/dist/api/sse.d.ts +12 -0
  10. package/dist/api/types/branding.d.ts +17 -0
  11. package/dist/api/types/messages.d.ts +148 -0
  12. package/dist/api/types/responses.d.ts +106 -0
  13. package/dist/api.js +2 -0
  14. package/dist/classPrivateFieldSet2-DtQPURVD.js +32 -0
  15. package/dist/core/bubbles/convert.d.ts +18 -0
  16. package/dist/core/bubbles/types.d.ts +55 -0
  17. package/dist/core/constants.d.ts +14 -0
  18. package/dist/core/events.d.ts +44 -0
  19. package/dist/core/i18n/fallback.d.ts +2 -0
  20. package/dist/core/i18n/languages.d.ts +11 -0
  21. package/dist/core/i18n/translator.d.ts +13 -0
  22. package/dist/core/interview.d.ts +207 -0
  23. package/dist/core/media/mimeTypes.d.ts +10 -0
  24. package/dist/core/media/recorder.d.ts +61 -0
  25. package/dist/core/persistence.d.ts +4 -0
  26. package/dist/core/store.d.ts +49 -0
  27. package/dist/core/theme/branding.d.ts +6 -0
  28. package/dist/core/theme/cssVariables.d.ts +16 -0
  29. package/dist/core/theme/types.d.ts +11 -0
  30. package/dist/core/validation/phoneCountries.d.ts +21 -0
  31. package/dist/core/validation/pii.d.ts +30 -0
  32. package/dist/dash.all.min-DZ1yEcJk.js +27302 -0
  33. package/dist/dash.all.min-DZ1yEcJk.js.map +1 -0
  34. package/dist/defineProperty-DiFpLzd2.js +39 -0
  35. package/dist/dist-BSi28_cP.js +702 -0
  36. package/dist/dist-BSi28_cP.js.map +1 -0
  37. package/dist/dist-DGBYKC8c.js +14327 -0
  38. package/dist/dist-DGBYKC8c.js.map +1 -0
  39. package/dist/dist-FvHBWiRa.js +76 -0
  40. package/dist/dist-FvHBWiRa.js.map +1 -0
  41. package/dist/embed/compat.d.ts +82 -0
  42. package/dist/embed/constants.d.ts +2 -0
  43. package/dist/embed/createChatbot.d.ts +22 -0
  44. package/dist/fix-webm-duration-BW9Fbdzt.js +1057 -0
  45. package/dist/fix-webm-duration-BW9Fbdzt.js.map +1 -0
  46. package/dist/hls-mBncGwsE.js +18657 -0
  47. package/dist/hls-mBncGwsE.js.map +1 -0
  48. package/dist/index.d.ts +7 -0
  49. package/dist/index.js +4428 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/max-Btet1snX.js +12212 -0
  52. package/dist/max-Btet1snX.js.map +1 -0
  53. package/dist/messages-C4jqrtSG.js +187 -0
  54. package/dist/messages-C4jqrtSG.js.map +1 -0
  55. package/dist/react/AnswerInput.d.ts +21 -0
  56. package/dist/react/SapiaChatbot.d.ts +23 -0
  57. package/dist/react/bubbleViewModel.d.ts +50 -0
  58. package/dist/react/strings.d.ts +98 -0
  59. package/dist/react/useBeforeUnload.d.ts +5 -0
  60. package/dist/react/useVideoRecorder.d.ts +24 -0
  61. package/dist/react-BGJ7Szvq.js +216 -0
  62. package/dist/react-BGJ7Szvq.js.map +1 -0
  63. package/dist/react-BMxM8TWJ.js +192 -0
  64. package/dist/react-BMxM8TWJ.js.map +1 -0
  65. package/dist/react-BVzcquey.js +457 -0
  66. package/dist/react-BVzcquey.js.map +1 -0
  67. package/dist/react-BrCGR7Ca.js +2208 -0
  68. package/dist/react-BrCGR7Ca.js.map +1 -0
  69. package/dist/react-Bujo1aK5.js +334 -0
  70. package/dist/react-Bujo1aK5.js.map +1 -0
  71. package/dist/react-CRTg4gz8.js +367 -0
  72. package/dist/react-CRTg4gz8.js.map +1 -0
  73. package/dist/react-GXf8sbzf.js +311 -0
  74. package/dist/react-GXf8sbzf.js.map +1 -0
  75. package/dist/react-lp7yfM94.js +371 -0
  76. package/dist/react-lp7yfM94.js.map +1 -0
  77. package/dist/sapia-chatbot.css +3 -0
  78. package/dist/sapia.d.ts +4 -0
  79. package/dist/ui/chat/ChatBubble/ChatBubble.d.ts +47 -0
  80. package/dist/ui/chat/ChatLog/ChatLog.d.ts +33 -0
  81. package/dist/ui/chat/TypingIndicator/TypingIndicator.d.ts +17 -0
  82. package/dist/ui/chat/types.d.ts +71 -0
  83. package/dist/ui/constants.d.ts +2 -0
  84. package/dist/ui/inputs/DropdownInput/DropdownInput.d.ts +18 -0
  85. package/dist/ui/inputs/OptionList/OptionList.d.ts +18 -0
  86. package/dist/ui/inputs/PhoneAnswerInput/PhoneAnswerInput.d.ts +19 -0
  87. package/dist/ui/inputs/SliderInput/SliderInput.d.ts +19 -0
  88. package/dist/ui/inputs/TextAnswerInput/TextAnswerInput.d.ts +32 -0
  89. package/dist/ui/inputs/VideoRecorder/VideoRecorder.d.ts +54 -0
  90. package/dist/ui/media/EmbeddedVideo/EmbeddedVideo.d.ts +11 -0
  91. package/dist/ui/media/EmbeddedVideo/EmbeddedVideoPlayer.d.ts +16 -0
  92. package/dist/ui/media/EmbeddedVideo/VimeoPlayer.d.ts +20 -0
  93. package/dist/ui/media/UploadProgress/UploadProgress.d.ts +7 -0
  94. package/dist/ui/media/VideoPlayer/VideoPlayer.d.ts +13 -0
  95. package/dist/ui/primitives/Button/Button.d.ts +6 -0
  96. package/dist/ui/primitives/Dialog/Dialog.d.ts +16 -0
  97. package/dist/ui/richText.d.ts +6 -0
  98. package/dist/ui/shell/AccessVerificationForm/AccessVerificationForm.d.ts +21 -0
  99. package/dist/ui/shell/CopyLinkButton/CopyLinkButton.d.ts +11 -0
  100. package/dist/ui/shell/DoneScreen/DoneScreen.d.ts +15 -0
  101. package/dist/ui/shell/Header/Header.d.ts +13 -0
  102. package/dist/ui/shell/PreparingScreen/PreparingScreen.d.ts +9 -0
  103. package/dist/ui/shell/ProgressBar/ProgressBar.d.ts +9 -0
  104. package/dist/ui/shell/WelcomeScreen/WelcomeScreen.d.ts +22 -0
  105. package/dist/ui/shell/WidgetRoot/WidgetRoot.d.ts +12 -0
  106. package/dist-umd/sapia-chatbot.umd.js +3655 -0
  107. package/dist-umd/sapia-chatbot.umd.js.map +1 -0
  108. package/package.json +94 -0
@@ -0,0 +1,187 @@
1
+ import e from "axios";
2
+ //#region src/api/constants.ts
3
+ var t = "ph-app-id", n = "apollographql-client-name", r = "fi-sapia", i = "apollographql-client-version", a = [
4
+ "local",
5
+ "dev",
6
+ "qa",
7
+ "sandbox",
8
+ "product"
9
+ ], o = [
10
+ "ap-southeast-2",
11
+ "eu-west-1",
12
+ "us-east-1"
13
+ ], s = "chatbot", c = {
14
+ dev: "https://fi-api.dev.predictivehire.com",
15
+ qa: "https://fi-api.qa.predictivehire.com",
16
+ sandbox: "https://fi-api.sandbox.predictivehire.com",
17
+ product: "https://fi-api.product.predictivehire.com"
18
+ }, l = "http://fi.localhost.com:5004", u = ({ env: e, apiOrigin: t }) => t ? t.replace(/\/$/, "") : e === "local" ? l : c[e], d = (e) => `${u(e)}/api/${e.region}/${s}`, f = (e) => {
19
+ let t, n = [];
20
+ for (let r of e.split("\n")) {
21
+ if (r === "" || r.startsWith(":")) continue;
22
+ let e = r.indexOf(":"), i = e === -1 ? r : r.slice(0, e), a = e === -1 ? "" : r.slice(e + 1);
23
+ a.startsWith(" ") && (a = a.slice(1)), i === "event" ? t = a : i === "data" && n.push(a);
24
+ }
25
+ return {
26
+ event: t,
27
+ data: n.join("\n")
28
+ };
29
+ };
30
+ async function* p(e) {
31
+ let t = e.getReader(), n = new TextDecoder(), r = "";
32
+ try {
33
+ for (;;) {
34
+ let { value: e, done: i } = await t.read();
35
+ if (i) break;
36
+ r += n.decode(e, { stream: !0 }).replace(/\r\n/g, "\n");
37
+ let a;
38
+ for (; (a = r.indexOf("\n\n")) !== -1;) {
39
+ let e = r.slice(0, a);
40
+ r = r.slice(a + 2);
41
+ let t = f(e);
42
+ (t.event !== void 0 || t.data !== "") && (yield t);
43
+ }
44
+ }
45
+ } finally {
46
+ t.cancel().catch(() => {});
47
+ }
48
+ }
49
+ //#endregion
50
+ //#region src/api/client.ts
51
+ var m = (...e) => globalThis.fetch(...e), h = class {
52
+ constructor(a) {
53
+ var o, s, c;
54
+ this.baseUrl = d(a), this.baseHeaders = {
55
+ [t]: a.appId,
56
+ [n]: r,
57
+ [i]: "1.0"
58
+ }, this.http = (o = a.httpClient) == null ? e.create({ headers: this.baseHeaders }) : o, this.putImpl = (s = a.putImpl) == null ? e.put : s, this.fetchImpl = (c = a.fetchImpl) == null ? m : c;
59
+ }
60
+ fiTokenHeaders() {
61
+ return this.fiToken ? { [this.fiToken.name]: this.fiToken.value } : {};
62
+ }
63
+ getFiToken() {
64
+ return this.fiToken;
65
+ }
66
+ async startAts(e) {
67
+ let t = await this.http.post(`${this.baseUrl}/${e}/start/ats`);
68
+ return this.fiToken = t.data.fiToken, t.data;
69
+ }
70
+ async nextStream(e, t, n) {
71
+ let r = await this.fetchImpl(`${this.baseUrl}/${e}/next/stream`, {
72
+ method: "POST",
73
+ headers: {
74
+ ...this.baseHeaders,
75
+ ...this.fiTokenHeaders(),
76
+ "Content-Type": "application/json",
77
+ Accept: "text/event-stream"
78
+ },
79
+ body: JSON.stringify({ candidateResponse: t }),
80
+ signal: n.signal
81
+ });
82
+ if (!r.ok || !r.body) throw Error(`next/stream failed: ${r.status}`);
83
+ let i;
84
+ for await (let e of p(r.body)) {
85
+ let t = JSON.parse(e.data);
86
+ switch (t.type) {
87
+ case "RUN_ERROR":
88
+ var a;
89
+ throw Error((a = t.message) == null ? "next/stream error" : a);
90
+ case "STATE_SNAPSHOT":
91
+ var o, s;
92
+ (o = t.snapshot) != null && o.question && ((s = n.onMeta) == null || s.call(n, g(t.snapshot.question)));
93
+ break;
94
+ case "TEXT_MESSAGE_CONTENT":
95
+ var c;
96
+ t.delta && n.onDelta(t.delta, (c = t.messageId) == null ? "" : c);
97
+ break;
98
+ case "RUN_FINISHED":
99
+ i = t.result;
100
+ break;
101
+ }
102
+ if (i) break;
103
+ }
104
+ if (!i) throw Error("next/stream ended without a RUN_FINISHED frame");
105
+ return i.fiToken && (this.fiToken = i.fiToken), i;
106
+ }
107
+ async editAnswer(e, t) {
108
+ return (await this.http.post(`${this.baseUrl}/${e}/edit-answer`, { editAnswerInput: t }, { headers: this.fiTokenHeaders() })).data;
109
+ }
110
+ async extractHumanName(e, t) {
111
+ return (await this.http.post(`${this.baseUrl}/${e}/extract-human-name`, {
112
+ userText: t.userText,
113
+ extractTypes: [t.extractType]
114
+ })).data;
115
+ }
116
+ async getVideoUploadUrl(e, t, n) {
117
+ return (await this.http.get(`${this.baseUrl}/${e}/questions/${t}/video-upload-signed-url`, {
118
+ params: { extension: n },
119
+ headers: this.fiTokenHeaders()
120
+ })).data;
121
+ }
122
+ async uploadVideo(e, t, n, r) {
123
+ await this.putImpl(e, t, {
124
+ headers: { "Content-Type": `video/${n}` },
125
+ onUploadProgress: (e) => {
126
+ r && e.total && r(e.loaded / e.total);
127
+ }
128
+ });
129
+ }
130
+ async getVideoAccessUrl(e, t, n) {
131
+ return (await this.http.get(`${this.baseUrl}/${e}/questions/${t}/video-access-signed-url`, {
132
+ params: { extension: n },
133
+ headers: this.fiTokenHeaders()
134
+ })).data;
135
+ }
136
+ async detectAgc(e) {
137
+ return (await this.http.post(`${this.baseUrl}/${e}/detect-agc`, {}, { headers: this.fiTokenHeaders() })).data;
138
+ }
139
+ async getAccessManagement(e) {
140
+ return (await this.http.get(`${this.baseUrl}/${e}/access-management`)).data;
141
+ }
142
+ async verifyAccess(e, t) {
143
+ return (await this.http.post(`${this.baseUrl}/${e}/verify-access`, { candidateEmail: t })).data;
144
+ }
145
+ async getTranslation(e) {
146
+ return (await this.http.get(`${this.baseUrl}/translation/${e}`)).data;
147
+ }
148
+ }, g = (e) => {
149
+ var t;
150
+ return {
151
+ _id: e._id,
152
+ masterId: e.masterId,
153
+ types: e.questionTypes,
154
+ rules: e.rules,
155
+ options: e.options,
156
+ tags: e.tags,
157
+ usageWhitelist: e.usageWhitelist,
158
+ kind: e.kind,
159
+ contents: ((t = e.contents) == null ? [] : t).map((e) => ({
160
+ value: "",
161
+ types: e.types,
162
+ component: e.component
163
+ }))
164
+ };
165
+ }, _ = /* @__PURE__ */ function(e) {
166
+ return e.MAX_TEXT_LENGTH = "maxTextLength", e.MIN_TEXT_LENGTH = "minTextLength", e.RECOMMENDED_TEXT_LENGTH = "recommendedTextLength", e.MAX_VOICE_SECONDS = "maxVoiceSeconds", e.MIN_VOICE_SECONDS = "minVoiceSeconds", e.RECOMMENDED_VOICE_SECONDS = "recommendedVoiceSeconds", e;
167
+ }({}), v = /* @__PURE__ */ function(e) {
168
+ return e.AGENT = "AGENT", e.CANDIDATE = "CANDIDATE", e;
169
+ }({}), y = /* @__PURE__ */ function(e) {
170
+ return e.HINT = "HINT", e.FTQ = "FTQ", e.MCQ = "MCQ", e.VOICE = "VOICE", e.VIDEO = "VIDEO", e.SLIDER = "SLIDER", e.DROPDOWN = "DROPDOWN", e.BGQ = "BUTTON_GROUP_QUESTION", e;
171
+ }({}), b = /* @__PURE__ */ function(e) {
172
+ return e.TEXT = "TEXT", e.VOICE = "VOICE", e.VIDEO = "VIDEO", e.SLIDER = "SLIDER", e;
173
+ }({}), x = /* @__PURE__ */ function(e) {
174
+ return e.GENERAL_PY = "GENERAL_PY", e.CUSTOMER_SATISFACTION = "CUSTOMER_SATISFACTION", e.PII = "PII", e.SENSITIVE = "SENSITIVE", e.DISPLAY_IN_TI = "DISPLAY_IN_TI", e.HEXACO_PERSONALITY_TEST = "HEXACO_PERSONALITY_TEST", e;
175
+ }({}), S = /* @__PURE__ */ function(e) {
176
+ return e.FULL_NAME = "fullName", e.FIRST_NAME = "first_name", e.LAST_NAME = "last_name", e.EMAIL = "email", e.PHONE_NUMBER = "phoneNumber", e.LOCATION = "location", e;
177
+ }({}), C = /* @__PURE__ */ function(e) {
178
+ return e.TEST_CAMERA = "test_camera", e;
179
+ }({}), w = /* @__PURE__ */ function(e) {
180
+ return e.PRIMARY = "PRIMARY", e.SECONDARY = "SECONDARY", e.DEFAULT = "DEFAULT", e;
181
+ }({}), T = /* @__PURE__ */ function(e) {
182
+ return e.FI_ATS_ASSESSMENT_ORDER_COMPLETED = "FI_ATS_ASSESSMENT_ORDER_COMPLETED", e.FI_ASSESSMENT_STARTED = "FI_ASSESSMENT_STARTED", e.FI_ASSESSMENT_PII_SAVED = "FI_ASSESSMENT_PII_SAVED", e.FI_ASSESSMENT_PII_COMPLETED = "FI_ASSESSMENT_PII_COMPLETED", e.FI_ASSESSMENT_INPROGRESS = "FI_ASSESSMENT_INPROGRESS", e.FI_ASSESSMENT_COMPLETED = "FI_ASSESSMENT_COMPLETED", e.FI_ASSESSMENT_ENDED = "FI_ASSESSMENT_ENDED", e.FI_ASSESSMENT_DROPPED_OUT = "FI_ASSESSMENT_DROPPED_OUT", e.FI_PREDICTION_STARTED = "FI_PREDICTION_STARTED", e.FI_PREDICTION_COMPLETED = "FI_PREDICTION_COMPLETED", e.FI_ASSESSMENT_DUPLICATED = "FI_ASSESSMENT_DUPLICATED", e;
183
+ }({});
184
+ //#endregion
185
+ export { u as _, x as a, _ as c, c as d, s as f, d as g, o as h, v as i, C as l, a as m, w as n, S as o, l as p, b as r, y as s, T as t, h as u };
186
+
187
+ //# sourceMappingURL=messages-C4jqrtSG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages-C4jqrtSG.js","names":[],"sources":["../src/api/constants.ts","../src/api/config.ts","../src/api/sse.ts","../src/api/client.ts","../src/api/types/messages.ts"],"sourcesContent":["// Layer A — HTTP header names/values (ported from legacy FIApis.ts checkAxiosInstance).\n\nexport const PH_APP_ID_HEADER = 'ph-app-id'\nexport const CLIENT_NAME_HEADER = 'apollographql-client-name'\nexport const CLIENT_NAME_VALUE = 'fi-sapia'\nexport const CLIENT_VERSION_HEADER = 'apollographql-client-version'\nexport const CLIENT_VERSION_VALUE = '1.0'\n","// Layer A — environment/region configuration. Framework-agnostic, zero React.\n\nexport const ENVIRONMENTS = ['local', 'dev', 'qa', 'sandbox', 'product'] as const\nexport type Env = (typeof ENVIRONMENTS)[number]\n\nexport const REGIONS = ['ap-southeast-2', 'eu-west-1', 'us-east-1'] as const\nexport type Region = (typeof REGIONS)[number]\n\nexport const CHATBOT_API_PATH = 'chatbot'\n\n/** Backend origins per environment (ported from legacy src/types/Api.ts Domain map). */\nexport const API_ORIGIN_BY_ENV: Record<Exclude<Env, 'local'>, string> = {\n dev: 'https://fi-api.dev.predictivehire.com',\n qa: 'https://fi-api.qa.predictivehire.com',\n sandbox: 'https://fi-api.sandbox.predictivehire.com',\n product: 'https://fi-api.product.predictivehire.com',\n}\n\nexport const DEFAULT_LOCAL_API_ORIGIN = 'http://fi.localhost.com:5004'\n\nexport type ChatbotClientConfig = {\n env: Env\n region: Region\n appId: string\n /** Overrides the env-derived origin (required for env \"local\" if not on the default port). */\n apiOrigin?: string\n}\n\nexport const resolveApiOrigin = ({ env, apiOrigin }: Pick<ChatbotClientConfig, 'env' | 'apiOrigin'>): string => {\n if (apiOrigin) return apiOrigin.replace(/\\/$/, '')\n if (env === 'local') return DEFAULT_LOCAL_API_ORIGIN\n return API_ORIGIN_BY_ENV[env]\n}\n\n/** e.g. https://fi-api.dev.predictivehire.com/api/us-east-1/chatbot */\nexport const buildChatbotBaseUrl = (config: ChatbotClientConfig): string =>\n `${resolveApiOrigin(config)}/api/${config.region}/${CHATBOT_API_PATH}`\n","// Layer A — minimal Server-Sent-Events reader over a fetch ReadableStream.\n// No dependency: parses `event:`/`data:` frames separated by a blank line.\n// Assumes UTF-8 text frames terminated by \"\\n\\n\" (CRLF is normalised to LF).\n\nexport type SseFrame = {\n /** SSE event name (`event:` line); undefined for a default message frame. */\n event?: string\n /** Joined `data:` lines (one frame may carry several). */\n data: string\n}\n\n/** Parse a single frame block (text between blank-line separators). */\nconst parseFrame = (block: string): SseFrame => {\n let event: string | undefined\n const dataLines: string[] = []\n for (const line of block.split('\\n')) {\n if (line === '' || line.startsWith(':')) continue // blank or comment\n const idx = line.indexOf(':')\n const field = idx === -1 ? line : line.slice(0, idx)\n let value = idx === -1 ? '' : line.slice(idx + 1)\n if (value.startsWith(' ')) value = value.slice(1) // strip one leading space (SSE spec)\n if (field === 'event') event = value\n else if (field === 'data') dataLines.push(value)\n }\n return { event, data: dataLines.join('\\n') }\n}\n\n/**\n * Reads an SSE body, yielding parsed {event,data} frames as they arrive.\n * Cancels the underlying reader when the consumer stops (break / throw / abort),\n * so an aborted fetch tears the stream down cleanly.\n */\nexport async function* readSse(body: ReadableStream<Uint8Array>): AsyncGenerator<SseFrame> {\n const reader = body.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n try {\n for (;;) {\n const { value, done } = await reader.read()\n if (done) break\n buffer += decoder.decode(value, { stream: true }).replace(/\\r\\n/g, '\\n')\n let sep: number\n while ((sep = buffer.indexOf('\\n\\n')) !== -1) {\n const block = buffer.slice(0, sep)\n buffer = buffer.slice(sep + 2)\n const frame = parseFrame(block)\n // Skip empty/keep-alive frames (comment-only or blank); a named event always yields.\n if (frame.event !== undefined || frame.data !== '') yield frame\n }\n }\n } finally {\n reader.cancel().catch(() => {})\n }\n}\n","// Layer A — ChatbotApiClient: all backend calls, no business logic, zero React.\n// Replaces legacy FIApis.ts module-level singletons with an injectable per-instance client.\n\nimport axios, { type AxiosInstance, type AxiosProgressEvent } from 'axios'\nimport {\n CLIENT_NAME_HEADER,\n CLIENT_NAME_VALUE,\n CLIENT_VERSION_HEADER,\n CLIENT_VERSION_VALUE,\n PH_APP_ID_HEADER,\n} from './constants'\nimport { buildChatbotBaseUrl, type ChatbotClientConfig } from './config'\nimport { readSse } from './sse'\nimport type {\n CandidateResponseInput,\n EditCandidateResponseInput,\n Question,\n} from './types/messages'\nimport type {\n AccessManagementResponse,\n AgUiFrame,\n DetectAgcResponse,\n EditAnswerResponse,\n ExtractHumanNameResponse,\n ExtractHumanNameType,\n FiToken,\n NextResponse,\n SnapshotQuestion,\n StartAtsResponse,\n TranslationResponse,\n VerifyAccessResponse,\n VideoSignedUrlResponse,\n} from './types/responses'\n\nexport type ChatbotApiClientOptions = ChatbotClientConfig & {\n /** Injectable for tests / instrumented hosts. */\n httpClient?: AxiosInstance\n /** Bare PUT for S3 uploads (no API headers); injectable for tests. */\n putImpl?: typeof axios.put\n /** fetch impl for the streaming transport (Axios can't stream); injectable for tests / mock streams. */\n fetchImpl?: typeof fetch\n}\n\n/** Handlers for nextStream — fired as frames arrive on the SSE. */\nexport type NextStreamHandlers = {\n /**\n * A chunk of agent message text. `messageId` (\"<baseId>:<contentIndex>\")\n * identifies which message it belongs to; a change of baseId marks a new\n * bundled message (consecutive hints arrive as separate baseIds in one run).\n */\n onDelta: (text: string, messageId: string) => void\n /** Optional leading frame carrying the question structure (types/rules/options) before the text. */\n onMeta?: (question: Question) => void\n /** Aborts the in-flight stream (new request / unmount). */\n signal?: AbortSignal\n}\n\n/** Default streaming transport: delegates to the platform fetch (errors lazily if absent). */\nconst defaultFetch: typeof fetch = (...args) => globalThis.fetch(...args)\n\nexport class ChatbotApiClient {\n private readonly http: AxiosInstance\n private readonly putImpl: typeof axios.put\n private readonly fetchImpl: typeof fetch\n private readonly baseUrl: string\n /** API/client headers reused by both the Axios instance and the fetch streaming path. */\n private readonly baseHeaders: Record<string, string>\n private fiToken?: FiToken\n\n constructor(options: ChatbotApiClientOptions) {\n this.baseUrl = buildChatbotBaseUrl(options)\n this.baseHeaders = {\n [PH_APP_ID_HEADER]: options.appId,\n [CLIENT_NAME_HEADER]: CLIENT_NAME_VALUE,\n [CLIENT_VERSION_HEADER]: CLIENT_VERSION_VALUE,\n }\n this.http = options.httpClient ?? axios.create({ headers: this.baseHeaders })\n this.putImpl = options.putImpl ?? axios.put\n this.fetchImpl = options.fetchImpl ?? defaultFetch\n }\n\n /** Dynamic session-token header for calls after /start/ats (token name varies per session). */\n private fiTokenHeaders(): Record<string, string> {\n return this.fiToken ? { [this.fiToken.name]: this.fiToken.value } : {}\n }\n\n getFiToken(): FiToken | undefined {\n return this.fiToken\n }\n\n async startAts(assessmentId: string): Promise<StartAtsResponse> {\n const res = await this.http.post<StartAtsResponse>(`${this.baseUrl}/${assessmentId}/start/ats`)\n this.fiToken = res.data.fiToken\n return res.data\n }\n\n /**\n * Loads the next question over /next/stream and consumes the\n * backend's AG-UI event stream (@see https://docs.ag-ui.com/concepts/events).\n * Each frame is data-only (`data: <json>`); the discriminator is the JSON\n * `type`. Fires onMeta from STATE_SNAPSHOT and onDelta per TEXT_MESSAGE_CONTENT,\n * then resolves with the authoritative NextResponse carried by RUN_FINISHED.\n */\n async nextStream(\n assessmentId: string,\n candidateResponse: CandidateResponseInput | undefined,\n handlers: NextStreamHandlers,\n ): Promise<NextResponse> {\n const res = await this.fetchImpl(`${this.baseUrl}/${assessmentId}/next/stream`, {\n method: 'POST',\n headers: {\n ...this.baseHeaders,\n ...this.fiTokenHeaders(),\n 'Content-Type': 'application/json',\n Accept: 'text/event-stream',\n },\n body: JSON.stringify({ candidateResponse }),\n signal: handlers.signal,\n })\n if (!res.ok || !res.body) throw new Error(`next/stream failed: ${res.status}`)\n\n let final: NextResponse | undefined\n for await (const frame of readSse(res.body)) {\n const event = JSON.parse(frame.data) as AgUiFrame\n switch (event.type) {\n case 'RUN_ERROR':\n throw new Error(event.message ?? 'next/stream error')\n case 'STATE_SNAPSHOT':\n if (event.snapshot?.question) handlers.onMeta?.(toQuestion(event.snapshot.question))\n break\n case 'TEXT_MESSAGE_CONTENT':\n if (event.delta) handlers.onDelta(event.delta, event.messageId ?? '')\n break\n case 'RUN_FINISHED':\n final = event.result\n break\n // RUN_STARTED / TEXT_MESSAGE_START / TEXT_MESSAGE_END / CUSTOM — no client action.\n }\n if (final) break\n }\n if (!final) throw new Error('next/stream ended without a RUN_FINISHED frame')\n if (final.fiToken) this.fiToken = final.fiToken\n return final\n }\n\n async editAnswer(\n assessmentId: string,\n editCandidateResponse: EditCandidateResponseInput,\n ): Promise<EditAnswerResponse> {\n const res = await this.http.post<EditAnswerResponse>(\n `${this.baseUrl}/${assessmentId}/edit-answer`,\n { editAnswerInput: editCandidateResponse },\n { headers: this.fiTokenHeaders() },\n )\n return res.data\n }\n\n async extractHumanName(\n assessmentId: string,\n input: { userText: string; extractType: ExtractHumanNameType },\n ): Promise<ExtractHumanNameResponse> {\n const res = await this.http.post<ExtractHumanNameResponse>(\n `${this.baseUrl}/${assessmentId}/extract-human-name`,\n { userText: input.userText, extractTypes: [input.extractType] },\n )\n return res.data\n }\n\n async getVideoUploadUrl(\n assessmentId: string,\n questionId: string,\n extension: string,\n ): Promise<VideoSignedUrlResponse> {\n const res = await this.http.get<VideoSignedUrlResponse>(\n `${this.baseUrl}/${assessmentId}/questions/${questionId}/video-upload-signed-url`,\n { params: { extension }, headers: this.fiTokenHeaders() },\n )\n return res.data\n }\n\n /** PUTs the recorded blob to the S3 signed URL, reporting upload progress 0–1. */\n async uploadVideo(\n signedUrl: string,\n blob: Blob,\n extension: string,\n onProgress?: (fraction: number) => void,\n ): Promise<void> {\n // Bare axios (not this.http): the S3 signed URL must not receive our API\n // auth/client headers, which aren't part of the signature.\n await this.putImpl(signedUrl, blob, {\n headers: { 'Content-Type': `video/${extension}` },\n onUploadProgress: (event: AxiosProgressEvent) => {\n if (onProgress && event.total) onProgress(event.loaded / event.total)\n },\n })\n }\n\n async getVideoAccessUrl(\n assessmentId: string,\n questionId: string,\n extension: string,\n ): Promise<VideoSignedUrlResponse> {\n const res = await this.http.get<VideoSignedUrlResponse>(\n `${this.baseUrl}/${assessmentId}/questions/${questionId}/video-access-signed-url`,\n { params: { extension }, headers: this.fiTokenHeaders() },\n )\n return res.data\n }\n\n async detectAgc(assessmentId: string): Promise<DetectAgcResponse> {\n const res = await this.http.post<DetectAgcResponse>(\n `${this.baseUrl}/${assessmentId}/detect-agc`,\n {},\n { headers: this.fiTokenHeaders() },\n )\n return res.data\n }\n\n async getAccessManagement(assessmentId: string): Promise<AccessManagementResponse> {\n const res = await this.http.get<AccessManagementResponse>(\n `${this.baseUrl}/${assessmentId}/access-management`,\n )\n return res.data\n }\n\n async verifyAccess(assessmentId: string, candidateEmail: string): Promise<VerifyAccessResponse> {\n const res = await this.http.post<VerifyAccessResponse>(\n `${this.baseUrl}/${assessmentId}/verify-access`,\n { candidateEmail },\n )\n return res.data\n }\n\n async getTranslation(language: string): Promise<TranslationResponse> {\n const res = await this.http.get<TranslationResponse>(`${this.baseUrl}/translation/${language}`)\n return res.data\n }\n}\n\n/**\n * Adapt an AG-UI STATE_SNAPSHOT question to our wire Question. `questionTypes`→`types`;\n * carry the BE render indicators (kind, per-content component) for agent-side rendering.\n * No text here (it streams via TEXT_MESSAGE_CONTENT), so each content keeps types + component.\n */\nconst toQuestion = (q: SnapshotQuestion): Question => ({\n _id: q._id,\n masterId: q.masterId,\n types: q.questionTypes,\n rules: q.rules,\n options: q.options,\n tags: q.tags,\n usageWhitelist: q.usageWhitelist,\n kind: q.kind,\n contents: (q.contents ?? []).map((c) => ({ value: '', types: c.types, component: c.component })),\n})\n","// Layer A — wire types shared with the backend.\n// Ported from legacy phapp-fi-widget src/types/Next.ts (values must match the BE contract).\n\nexport enum RuleName {\n MAX_TEXT_LENGTH = 'maxTextLength',\n MIN_TEXT_LENGTH = 'minTextLength',\n RECOMMENDED_TEXT_LENGTH = 'recommendedTextLength',\n MAX_VOICE_SECONDS = 'maxVoiceSeconds',\n MIN_VOICE_SECONDS = 'minVoiceSeconds',\n RECOMMENDED_VOICE_SECONDS = 'recommendedVoiceSeconds',\n}\n\nexport enum MessageSender {\n AGENT = 'AGENT',\n CANDIDATE = 'CANDIDATE',\n}\n\nexport enum QuestionType {\n HINT = 'HINT',\n FTQ = 'FTQ',\n MCQ = 'MCQ',\n VOICE = 'VOICE',\n VIDEO = 'VIDEO',\n SLIDER = 'SLIDER',\n DROPDOWN = 'DROPDOWN',\n BGQ = 'BUTTON_GROUP_QUESTION',\n}\n\nexport enum ContentType {\n TEXT = 'TEXT',\n VOICE = 'VOICE',\n VIDEO = 'VIDEO',\n SLIDER = 'SLIDER',\n}\n\nexport enum PHService {\n GENERAL_PY = 'GENERAL_PY',\n CUSTOMER_SATISFACTION = 'CUSTOMER_SATISFACTION',\n PII = 'PII',\n SENSITIVE = 'SENSITIVE',\n DISPLAY_IN_TI = 'DISPLAY_IN_TI',\n HEXACO_PERSONALITY_TEST = 'HEXACO_PERSONALITY_TEST',\n}\n\n/** Identified via question tag { name: \"PII_TYPE\", value: <PiiQuestionType> } */\nexport enum PiiQuestionType {\n FULL_NAME = 'fullName',\n FIRST_NAME = 'first_name',\n LAST_NAME = 'last_name',\n EMAIL = 'email',\n PHONE_NUMBER = 'phoneNumber',\n LOCATION = 'location',\n}\n\n/** Identified via question tag { name: \"VIDEO_TYPE\", value: <VideoQuestionType> } */\nexport enum VideoQuestionType {\n TEST_CAMERA = 'test_camera',\n}\n\nexport enum ButtonType {\n PRIMARY = 'PRIMARY',\n SECONDARY = 'SECONDARY',\n DEFAULT = 'DEFAULT',\n}\n\nexport enum AssessmentStatus {\n FI_ATS_ASSESSMENT_ORDER_COMPLETED = 'FI_ATS_ASSESSMENT_ORDER_COMPLETED',\n FI_ASSESSMENT_STARTED = 'FI_ASSESSMENT_STARTED',\n FI_ASSESSMENT_PII_SAVED = 'FI_ASSESSMENT_PII_SAVED',\n FI_ASSESSMENT_PII_COMPLETED = 'FI_ASSESSMENT_PII_COMPLETED',\n FI_ASSESSMENT_INPROGRESS = 'FI_ASSESSMENT_INPROGRESS',\n FI_ASSESSMENT_COMPLETED = 'FI_ASSESSMENT_COMPLETED',\n FI_ASSESSMENT_ENDED = 'FI_ASSESSMENT_ENDED',\n FI_ASSESSMENT_DROPPED_OUT = 'FI_ASSESSMENT_DROPPED_OUT',\n FI_PREDICTION_STARTED = 'FI_PREDICTION_STARTED',\n FI_PREDICTION_COMPLETED = 'FI_PREDICTION_COMPLETED',\n FI_ASSESSMENT_DUPLICATED = 'FI_ASSESSMENT_DUPLICATED',\n}\n\nexport type MessageIdentifier = string\n\nexport type Trigger = {\n action: string\n}\n\nexport type Style = {\n buttonType: ButtonType\n}\n\nexport type Content = {\n value: string\n text: string\n id: string\n}\n\nexport type ButtonGroupContent = Content & {\n triggers: Trigger[]\n style: Style\n}\n\nexport type AnswerContent = {\n text: string\n value: string\n /** Sanitized rich-text HTML for re-display; `text` stays the plain-text source for scoring. */\n html?: string\n triggers?: Trigger[]\n style?: Style\n}\n\nexport type Tag = {\n name: string\n value: string\n}\n\nexport type QuestionRule = {\n name: RuleName\n type: string\n value: number | string\n}\n\nexport type BubbleContent = {\n value: string\n types: ContentType[]\n /** BE A2UI-style render component (streaming STATE_SNAPSHOT / loaded chatLogs only). */\n component?: 'Text' | 'VoicePlayer' | 'VideoPlayer' | 'Slider'\n}\n\nexport type MCQQuestionOption = {\n id: string\n text: string\n value: string\n}\n\nexport type DropdownOption = MCQQuestionOption\n\nexport type BGQButton = MCQQuestionOption & {\n triggers: Trigger[]\n style: Style\n}\n\nexport type Question = {\n masterId?: string\n types: QuestionType[]\n rules?: QuestionRule[]\n contents: BubbleContent[]\n options?: MCQQuestionOption[] | BGQButton[]\n usageWhitelist?: PHService[]\n _id: string\n tags?: Tag[]\n /** BE message kind (streaming STATE_SNAPSHOT / loaded chatLogs only); \"Hint\" | \"Question\". */\n kind?: 'Hint' | 'Question'\n}\n\nexport type Answer = {\n _id: string\n question: Question\n text: string\n value: string\n}\n\nexport interface Message<T> {\n id: string\n sender: MessageSender\n messageIdentifier?: MessageIdentifier\n data: T\n}\n\nexport type CandidateResponse = Message<Answer>\nexport type AgentResponse = Message<Question>\n\nexport type CandidateResponseInput = {\n userAgent: string\n questionId: string\n answer?: AnswerContent\n}\n\nexport type EditCandidateResponseInput = Required<Omit<CandidateResponseInput, 'userAgent'>>\n"],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import { InterviewEngine } from '../core/interview';
2
+ import { Region } from '../api/config';
3
+ import { CurrentInputKind, TextRules } from './bubbleViewModel';
4
+ import { UiStrings } from './strings';
5
+ export type AnswerInputProps = {
6
+ engine: InterviewEngine;
7
+ kind: CurrentInputKind;
8
+ disabled: boolean;
9
+ textRules: TextRules;
10
+ strings: UiStrings;
11
+ candidateCountryCode?: string;
12
+ region: Region;
13
+ /** Max video answer length in seconds (video questions). */
14
+ maxVideoSeconds?: number;
15
+ /** True while re-editing an FTQ/PII answer; shows a cancel affordance. */
16
+ editing?: boolean;
17
+ cancelEditLabel?: string;
18
+ /** Called when the candidate pastes into a text answer (AGC paste log). */
19
+ onPaste?: () => void;
20
+ };
21
+ export declare const AnswerInput: ({ engine, kind, disabled, textRules, strings, candidateCountryCode, region, maxVideoSeconds, editing, cancelEditLabel, onPaste, }: AnswerInputProps) => import("react").JSX.Element;
@@ -0,0 +1,23 @@
1
+ import { Env, Region } from '../api/config';
2
+ import { ChatbotEventEmitter, ChatbotEventMap, ChatbotEventName } from '../core/events';
3
+ import { Theme } from '../core/theme/types';
4
+ export type ChatbotEventHandler = <E extends ChatbotEventName>(event: E, payload: ChatbotEventMap[E]) => void;
5
+ export type SapiaChatbotProps = {
6
+ env: Env;
7
+ region: Region;
8
+ appId: string;
9
+ assessmentId: string;
10
+ /** Overrides the env-derived API origin (mainly for env "local"). */
11
+ apiOrigin?: string;
12
+ theme?: Theme;
13
+ showHeader?: boolean;
14
+ /** Interview language override (e.g. "fr"); defaults to the browser language. */
15
+ language?: string;
16
+ /** Receives every widget event; or pass an emitter via createChatbot. */
17
+ onEvent?: ChatbotEventHandler;
18
+ /** @internal used by embed/createChatbot to share its emitter. */
19
+ emitter?: ChatbotEventEmitter;
20
+ /** @internal streaming transport override (mock SSE for the demo / tests). */
21
+ fetchImpl?: typeof fetch;
22
+ };
23
+ export declare const SapiaChatbot: ({ env, region, appId, assessmentId, apiOrigin, theme, showHeader, language, onEvent, emitter, fetchImpl, }: SapiaChatbotProps) => import("react").JSX.Element;
@@ -0,0 +1,50 @@
1
+ import { MessageBubble } from '../core/bubbles/types';
2
+ import { ChatLogItem } from '../ui/chat/types';
3
+ /** Per-card eyebrow + optional dark badge, keyed by agent bubble id. */
4
+ export type QuestionCard = {
5
+ label: string;
6
+ badge?: string;
7
+ };
8
+ export declare const mapBubblesToChatItems: (bubbles: MessageBubble[], cards?: Map<string, QuestionCard>, streamingBubbleId?: string) => ChatLogItem[];
9
+ export type TextRules = {
10
+ minLength?: number;
11
+ maxLength?: number;
12
+ recommendedLength?: number;
13
+ };
14
+ /**
15
+ * The agent bubble whose answer the bottom input targets: the question under
16
+ * edit when editing, otherwise the last agent bubble (current question).
17
+ */
18
+ export declare const currentAgentBubble: (bubbles: MessageBubble[], editingQuestionId?: string) => MessageBubble | undefined;
19
+ /** Id of the question the bottom input currently targets (for remounting per question). */
20
+ export declare const currentQuestionId: (bubbles: MessageBubble[], editingQuestionId?: string) => string | undefined;
21
+ /** Eyebrow/badge copy builders, supplied by react/strings (keeps i18n out of the view-model). */
22
+ export type QuestionCardLabels = {
23
+ /** "Question {n} of {total}" for written/video questions. */
24
+ counter: (n: number, total: number) => string;
25
+ /** Type eyebrow for MCQ/BGQ/DROPDOWN. */
26
+ singleChoice: string;
27
+ /** Dark badge shown on video question cards. */
28
+ videoBadge: string;
29
+ };
30
+ /**
31
+ * Marks every agent question turn as a card (matching the prototype's permanent
32
+ * question styling); conversational agent turns stay plain. Written/video show
33
+ * "Question n of N"; choice questions show a type eyebrow; video adds a badge.
34
+ * The ordinal increments for each counted question even when its number isn't
35
+ * displayed. Editing a past answer suppresses all cards (inline re-edit).
36
+ */
37
+ export declare const buildQuestionCards: (bubbles: MessageBubble[], total: number, labels: QuestionCardLabels, editingQuestionId?: string) => Map<string, QuestionCard>;
38
+ /**
39
+ * Bubble id of the active video question, so its recorder renders inside the
40
+ * card. Undefined unless the current question is a (non-edited) video question.
41
+ */
42
+ export declare const activeVideoCardId: (bubbles: MessageBubble[], editingQuestionId?: string) => string | undefined;
43
+ /** The bottom free-text/PII input is shown only for FTQ-typed questions. */
44
+ export declare const isTextInputActive: (bubbles: MessageBubble[], editingQuestionId?: string) => boolean;
45
+ /** Which composed input the current question needs (video, PII subtype, or plain text). */
46
+ export type CurrentInputKind = 'text' | 'email' | 'name' | 'phone' | 'location' | 'video';
47
+ export declare const currentInputKind: (bubbles: MessageBubble[], editingQuestionId?: string) => CurrentInputKind;
48
+ export declare const currentTextRules: (bubbles: MessageBubble[], editingQuestionId?: string) => TextRules;
49
+ /** Max video answer length in seconds (legacy reuses the voice-seconds rule). */
50
+ export declare const currentMaxVideoSeconds: (bubbles: MessageBubble[], editingQuestionId?: string) => number;
@@ -0,0 +1,98 @@
1
+ import { Translator } from '../core/i18n/translator';
2
+ export declare const buildStrings: (t: Translator) => {
3
+ headerTitle: string;
4
+ headerSubtitle: string;
5
+ speakerInterviewer: string;
6
+ singleChoiceLabel: string;
7
+ videoBadge: string;
8
+ typingLabel: string;
9
+ typingReading: string;
10
+ typingThinking: string;
11
+ progressLabel: string;
12
+ /** Header pill / question-card badge: "Question {current} of {total}". */
13
+ questionCounter: (current: number, total: number) => string;
14
+ gettingStarted: string;
15
+ pauseHint: string;
16
+ copyLinkLabel: string;
17
+ copiedLabel: string;
18
+ /** Header title "{role} role with {company}". */
19
+ roleWithCompany: (role: string, company: string) => string;
20
+ welcomeGreeting: (name?: string) => string;
21
+ welcomeTitle: string;
22
+ welcomeEyebrow: string;
23
+ welcomeCopyResume: string;
24
+ welcomeIntro: (role?: string, company?: string) => string;
25
+ welcomePause: string;
26
+ welcomeNext: string;
27
+ welcomeReassurance: string;
28
+ answerLooksGood: string;
29
+ composerFooter: string;
30
+ richToolbar: string;
31
+ richBold: string;
32
+ richItalic: string;
33
+ richUnderline: string;
34
+ richBulletList: string;
35
+ richNumberList: string;
36
+ inputPlaceholder: string;
37
+ inputLabel: string;
38
+ sendLabel: string;
39
+ retryLabel: string;
40
+ editLabel: string;
41
+ cancelEditLabel: string;
42
+ optionsGroupLabel: string;
43
+ sliderLabel: string;
44
+ sliderHint: string;
45
+ submitRatingLabel: string;
46
+ dropdownSearchPlaceholder: string;
47
+ dropdownSearchLabel: string;
48
+ emailPlaceholder: string;
49
+ phoneCountryLabel: string;
50
+ phoneInputLabel: string;
51
+ phonePlaceholder: string;
52
+ nameConfirmTitle: string;
53
+ nameConfirmBody: (suggested: string) => string;
54
+ useSuggestedLabel: (suggested: string) => string;
55
+ useOriginalLabel: string;
56
+ minLengthHint: (min: number) => string;
57
+ loadingLabel: string;
58
+ preparingLabel: string;
59
+ doneTitle: (name?: string) => string;
60
+ doneSubtitle: (company?: string) => string;
61
+ doneNextLabel: string;
62
+ doneSteps: [string, string, string];
63
+ doneRatingPrompt: string;
64
+ startFailedMessage: string;
65
+ /** PII validation error message for a given fi_error_pii* key. */
66
+ piiError: (key: string) => string;
67
+ videoOpenCamera: string;
68
+ videoStartRecording: string;
69
+ videoStopRecording: string;
70
+ videoReRecord: string;
71
+ videoSubmit: string;
72
+ videoCameraError: string;
73
+ videoPreview: string;
74
+ videoCamera: string;
75
+ videoMicrophone: string;
76
+ videoOn: string;
77
+ videoOff: string;
78
+ videoFinishHint: string;
79
+ videoCameraOff: string;
80
+ videoAttempts: (current: number, max: number) => string;
81
+ videoUploadProgress: string;
82
+ videoUploadFailed: string;
83
+ videoRetryUpload: string;
84
+ videoPlay: string;
85
+ videoAnswer: string;
86
+ embeddedVideoLabel: string;
87
+ accessTitle: string;
88
+ accessDescription: string;
89
+ accessInputLabel: string;
90
+ accessPlaceholder: string;
91
+ accessSubmit: string;
92
+ accessMismatch: string;
93
+ agcTitle: string;
94
+ agcMessage: (attempt: number) => string;
95
+ agcEdit: string;
96
+ agcContinue: string;
97
+ };
98
+ export type UiStrings = ReturnType<typeof buildStrings>;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Warns the candidate before leaving while `active` (e.g. a video upload or
3
+ * answer submission in flight) — legacy useBeforeUnload during video upload.
4
+ */
5
+ export declare const useBeforeUnload: (active: boolean) => void;
@@ -0,0 +1,24 @@
1
+ import { RecorderMedia, RecorderState } from '../core/media/recorder';
2
+ /** Max number of takes a candidate may record (client-side UX cap). */
3
+ export declare const MAX_VIDEO_ATTEMPTS = 5;
4
+ export type UseVideoRecorder = {
5
+ state: RecorderState;
6
+ videoRef: React.RefObject<HTMLVideoElement | null>;
7
+ /** Camera/mic enabled flags (track.enabled), toggled in the setup phase. */
8
+ camOn: boolean;
9
+ micOn: boolean;
10
+ /** 3-2-1 countdown value before recording starts, or null when inactive. */
11
+ countdown: number | null;
12
+ /** Current take number and the cap. */
13
+ attempts: number;
14
+ maxAttempts: number;
15
+ /** Object URL for the recorded take, available in the review phase. */
16
+ reviewUrl: string | null;
17
+ openCamera: () => void;
18
+ beginCountdown: () => void;
19
+ stopRecording: () => void;
20
+ reRecord: () => void;
21
+ toggleCamera: () => void;
22
+ toggleMicrophone: () => void;
23
+ };
24
+ export declare const useVideoRecorder: (maxSeconds: number, media?: RecorderMedia) => UseVideoRecorder;