@everfur/sdk 0.1.1 → 0.4.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.
- package/CHANGELOG.md +703 -4
- package/README.md +63 -23
- package/consent/package.json +8 -0
- package/dist/Chat-BZZ-Py5l.d.ts +128 -0
- package/dist/Chat-Dq4fffIO.d.cts +128 -0
- package/dist/DepthViews-BG3E25MD.d.ts +16 -0
- package/dist/DepthViews-B_hIypI6.d.cts +19 -0
- package/dist/DepthViews-CL75jyRA.d.cts +16 -0
- package/dist/DepthViews-CkYri51q.d.ts +19 -0
- package/dist/ErrorPolicyPort-CNf4uQZP.d.cts +22 -0
- package/dist/ErrorPolicyPort-CwbfmxzJ.d.ts +22 -0
- package/dist/{EverfurResult-D92-uL82.d.cts → EverfurResult-DN9pL2Ab.d.cts} +11 -10
- package/dist/{EverfurResult-D92-uL82.d.ts → EverfurResult-DN9pL2Ab.d.ts} +11 -10
- package/dist/{PhotoController-BItt5M7u.d.cts → PhotoController-3l7MT9jA.d.cts} +1 -1
- package/dist/{PhotoController-D8zMTdcW.d.ts → PhotoController-vzY5bfY_.d.ts} +1 -1
- package/dist/animations/index.cjs +1 -1997
- package/dist/animations/index.d.cts +35 -35
- package/dist/animations/index.d.ts +35 -35
- package/dist/animations/index.js +1 -1972
- package/dist/attachments-BorG422I.d.cts +146 -0
- package/dist/attachments-BrUWQYNK.d.ts +257 -0
- package/dist/attachments-D_SECjr1.d.cts +257 -0
- package/dist/attachments-DpldrvIW.d.ts +146 -0
- package/dist/bookingCopy-CqtDYvCR.d.ts +732 -0
- package/dist/bookingCopy-SREvkG19.d.cts +732 -0
- package/dist/branding-CsRdeNwK.d.cts +566 -0
- package/dist/branding-CsRdeNwK.d.ts +566 -0
- package/dist/callCopy-DoQKG3-L.d.cts +40 -0
- package/dist/callCopy-DoQKG3-L.d.ts +40 -0
- package/dist/casesRepository-DwtZVsbj.d.cts +51 -0
- package/dist/casesRepository-v6QczS9N.d.ts +51 -0
- package/dist/chat/index.cjs +1 -1170
- package/dist/chat/index.d.cts +32 -53
- package/dist/chat/index.d.ts +32 -53
- package/dist/chat/index.js +1 -1167
- package/dist/client/index.cjs +9 -2315
- package/dist/client/index.d.cts +80 -16
- package/dist/client/index.d.ts +80 -16
- package/dist/client/index.js +9 -2215
- package/dist/{config-CiJ0PVBB.d.ts → config--Wu1Umhd.d.cts} +64 -26
- package/dist/{config-BSjBdxrZ.d.cts → config-C7eb_IMD.d.ts} +64 -26
- package/dist/consent/index.cjs +1 -0
- package/dist/consent/index.d.cts +33 -0
- package/dist/consent/index.d.ts +33 -0
- package/dist/consent/index.js +1 -0
- package/dist/context-CL3lyuy1.d.cts +159 -0
- package/dist/context-CahAEha8.d.ts +159 -0
- package/dist/copy-DdVFDL01.d.ts +198 -0
- package/dist/copy-UeUJVk8X.d.cts +198 -0
- package/dist/core/index.cjs +13 -3178
- package/dist/core/index.d.cts +164 -29
- package/dist/core/index.d.ts +164 -29
- package/dist/core/index.js +13 -3166
- package/dist/depth-D5HwcvKC.d.cts +533 -0
- package/dist/depth-DvPwPuT_.d.ts +533 -0
- package/dist/entitlementRepository-B7egEdW6.d.ts +43 -0
- package/dist/entitlementRepository-BpJ7tiwl.d.cts +43 -0
- package/dist/{identity-Brl-lDd6.d.cts → identity-C2-08BZW.d.cts} +20 -7
- package/dist/{identity-DK9zORrG.d.ts → identity-CEi9GRFZ.d.ts} +20 -7
- package/dist/{ids-CJ1S6adf.d.cts → ids-B2GAAifq.d.cts} +1 -1
- package/dist/{ids-CJ1S6adf.d.ts → ids-B2GAAifq.d.ts} +1 -1
- package/dist/index.cjs +12 -4494
- package/dist/index.d.cts +30 -121
- package/dist/index.d.ts +30 -121
- package/dist/index.js +12 -4470
- package/dist/models-B-Uh2LTf.d.ts +214 -0
- package/dist/models-D2EUxPZY.d.cts +214 -0
- package/dist/notifications/index.cjs +2 -0
- package/dist/notifications/index.d.cts +17 -0
- package/dist/notifications/index.d.ts +17 -0
- package/dist/notifications/index.js +2 -0
- package/dist/optionalModule-DbJmsq5f.d.cts +21 -0
- package/dist/optionalModule-DbJmsq5f.d.ts +21 -0
- package/dist/pets-DJv2sj2H.d.ts +92 -0
- package/dist/pets-JVCXgRgR.d.cts +92 -0
- package/dist/petsRepository-B_gKbAzJ.d.cts +142 -0
- package/dist/petsRepository-Bk6GDwLs.d.ts +142 -0
- package/dist/photo/index.cjs +1 -2189
- package/dist/photo/index.d.cts +6 -6
- package/dist/photo/index.d.ts +6 -6
- package/dist/photo/index.js +1 -2186
- package/dist/ports-BN6RHF9W.d.cts +48 -0
- package/dist/ports-BN6RHF9W.d.ts +48 -0
- package/dist/ports-BY2ph0_y.d.cts +376 -0
- package/dist/ports-D7YW8qz9.d.ts +376 -0
- package/dist/profile-CSs1wlXT.d.cts +93 -0
- package/dist/profile-CSs1wlXT.d.ts +93 -0
- package/dist/records/depth/index.cjs +2 -0
- package/dist/records/depth/index.d.cts +13 -0
- package/dist/records/depth/index.d.ts +13 -0
- package/dist/records/depth/index.js +2 -0
- package/dist/records/index.cjs +3 -2839
- package/dist/records/index.d.cts +137 -203
- package/dist/records/index.d.ts +137 -203
- package/dist/records/index.js +3 -2833
- package/dist/requestFunnel-BjDpqqvm.d.cts +110 -0
- package/dist/requestFunnel-DFMGcluw.d.ts +110 -0
- package/dist/{resolve-Dq_4_agU.d.ts → resolve-D4Ywz5OS.d.cts} +10 -4
- package/dist/{resolve-Dq_4_agU.d.cts → resolve-D4Ywz5OS.d.ts} +10 -4
- package/dist/{runtime-Bvr8yqXm.d.cts → runtimeTypes-gZEJu-da.d.cts} +104 -132
- package/dist/{runtime-BIYzf9cK.d.ts → runtimeTypes-w5KqBMcs.d.ts} +104 -132
- package/dist/server/events/index.cjs +2 -0
- package/dist/server/events/index.d.cts +550 -0
- package/dist/server/events/index.d.ts +550 -0
- package/dist/server/events/index.js +2 -0
- package/dist/server/index.cjs +2 -533
- package/dist/server/index.d.cts +73 -18
- package/dist/server/index.d.ts +73 -18
- package/dist/server/index.js +2 -530
- package/dist/species-BXAIMh7I.d.cts +9 -0
- package/dist/species-BXAIMh7I.d.ts +9 -0
- package/dist/televet/booking/index.cjs +1 -0
- package/dist/televet/booking/index.d.cts +294 -0
- package/dist/televet/booking/index.d.ts +294 -0
- package/dist/televet/booking/index.js +1 -0
- package/dist/televet/call/index.cjs +1 -0
- package/dist/televet/call/index.d.cts +141 -0
- package/dist/televet/call/index.d.ts +141 -0
- package/dist/televet/call/index.js +1 -0
- package/dist/televet/index.cjs +1 -0
- package/dist/televet/index.d.cts +142 -0
- package/dist/televet/index.d.ts +142 -0
- package/dist/televet/index.js +1 -0
- package/dist/testing/index.cjs +5 -823
- package/dist/testing/index.d.cts +18 -4
- package/dist/testing/index.d.ts +18 -4
- package/dist/testing/index.js +5 -820
- package/dist/testing/rn/index.cjs +1 -449
- package/dist/testing/rn/index.d.cts +18 -29
- package/dist/testing/rn/index.d.ts +18 -29
- package/dist/testing/rn/index.js +1 -444
- package/dist/testing/web/index.cjs +1 -0
- package/dist/testing/web/index.d.cts +132 -0
- package/dist/testing/web/index.d.ts +132 -0
- package/dist/testing/web/index.js +1 -0
- package/dist/timelineRows-C15jxFFH.d.ts +19 -0
- package/dist/timelineRows-odO8g9r2.d.cts +19 -0
- package/dist/typeStyle-CKqVYD6L.d.cts +111 -0
- package/dist/typeStyle-DmoyacDG.d.ts +111 -0
- package/dist/uploadTransport-D0M0T4hN.d.ts +38 -0
- package/dist/uploadTransport-DKJHs3Yj.d.cts +38 -0
- package/dist/useRecordsDepth-C6rjEaLn.d.cts +237 -0
- package/dist/useRecordsDepth-crhBllQK.d.ts +237 -0
- package/dist/useVetVisit-Bc84hWUU.d.cts +187 -0
- package/dist/useVetVisit-CjmMa896.d.ts +187 -0
- package/dist/video/index.cjs +1 -1941
- package/dist/video/index.d.cts +4 -4
- package/dist/video/index.d.ts +4 -4
- package/dist/video/index.js +1 -1938
- package/dist/view-Bxwxp4xD.d.ts +310 -0
- package/dist/view-DyitEO0m.d.cts +310 -0
- package/dist/visitIntent-D7_yVp1I.d.cts +8 -0
- package/dist/visitIntent-D7_yVp1I.d.ts +8 -0
- package/dist/web/consent/index.cjs +1 -0
- package/dist/web/consent/index.d.cts +34 -0
- package/dist/web/consent/index.d.ts +34 -0
- package/dist/web/consent/index.js +1 -0
- package/dist/web/index.cjs +14 -0
- package/dist/web/index.d.cts +218 -0
- package/dist/web/index.d.ts +218 -0
- package/dist/web/index.js +14 -0
- package/dist/web/notifications/index.cjs +2 -0
- package/dist/web/notifications/index.d.cts +213 -0
- package/dist/web/notifications/index.d.ts +213 -0
- package/dist/web/notifications/index.js +2 -0
- package/dist/web/records/depth/index.cjs +2 -0
- package/dist/web/records/depth/index.d.cts +12 -0
- package/dist/web/records/depth/index.d.ts +12 -0
- package/dist/web/records/depth/index.js +2 -0
- package/dist/web/records/index.cjs +4 -0
- package/dist/web/records/index.d.cts +285 -0
- package/dist/web/records/index.d.ts +285 -0
- package/dist/web/records/index.js +4 -0
- package/dist/web/televet/booking/index.cjs +1 -0
- package/dist/web/televet/booking/index.d.cts +349 -0
- package/dist/web/televet/booking/index.d.ts +349 -0
- package/dist/web/televet/booking/index.js +1 -0
- package/dist/web/televet/call/index.cjs +1 -0
- package/dist/web/televet/call/index.d.cts +142 -0
- package/dist/web/televet/call/index.d.ts +142 -0
- package/dist/web/televet/call/index.js +1 -0
- package/dist/web/televet/index.cjs +1 -0
- package/dist/web/televet/index.d.cts +136 -0
- package/dist/web/televet/index.d.ts +136 -0
- package/dist/web/televet/index.js +1 -0
- package/notifications/package.json +8 -0
- package/package.json +266 -10
- package/records/depth/package.json +8 -0
- package/server/events/device-blocked.cjs +15 -0
- package/server/events/package.json +9 -0
- package/televet/booking/package.json +8 -0
- package/televet/call/package.json +8 -0
- package/televet/package.json +8 -0
- package/testing/web/native-blocked.cjs +12 -0
- package/testing/web/package.json +8 -0
- package/web/consent/native-blocked.cjs +12 -0
- package/web/consent/package.json +8 -0
- package/web/native-blocked.cjs +12 -0
- package/web/notifications/native-blocked.cjs +12 -0
- package/web/notifications/package.json +8 -0
- package/web/package.json +8 -0
- package/web/records/depth/native-blocked.cjs +12 -0
- package/web/records/depth/package.json +8 -0
- package/web/records/native-blocked.cjs +12 -0
- package/web/records/package.json +8 -0
- package/web/televet/booking/native-blocked.cjs +12 -0
- package/web/televet/booking/package.json +8 -0
- package/web/televet/call/native-blocked.cjs +12 -0
- package/web/televet/call/package.json +8 -0
- package/web/televet/native-blocked.cjs +12 -0
- package/web/televet/package.json +8 -0
- package/dist/ChatController-CKdBvPj2.d.ts +0 -146
- package/dist/ChatController-CpUMvvZf.d.cts +0 -146
- package/dist/FilePort-BabWrv7I.d.cts +0 -22
- package/dist/FilePort-BabWrv7I.d.ts +0 -22
- package/dist/petsRepository-BEGb97M9.d.cts +0 -326
- package/dist/petsRepository-Bu18r2kK.d.ts +0 -326
- package/dist/requestFunnel-DuUH-kAe.d.cts +0 -28
- package/dist/requestFunnel-dio5OmR9.d.ts +0 -28
package/dist/photo/index.js
CHANGED
|
@@ -1,2186 +1 @@
|
|
|
1
|
-
import { createContext, useState, useEffect, useCallback, useSyncExternalStore, useMemo, useContext, useRef } from 'react';
|
|
2
|
-
import 'react-native-safe-area-context';
|
|
3
|
-
import { View, Pressable, Text, ScrollView, StyleSheet, Animated, Easing } from 'react-native';
|
|
4
|
-
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
5
|
-
|
|
6
|
-
// src/rn/photo/usePhoto.ts
|
|
7
|
-
var CONTRACT_RETRYABILITY = Object.freeze([
|
|
8
|
-
Object.freeze({ wireCode: "AccessDeniedException", envelope: "http_envelope", httpStatus: 403, retryable: false }),
|
|
9
|
-
Object.freeze({ wireCode: "AuthorizationException", envelope: "http_envelope", httpStatus: 401, retryable: false }),
|
|
10
|
-
Object.freeze({ wireCode: "ConflictException", envelope: "http_envelope", httpStatus: 409, retryable: true }),
|
|
11
|
-
Object.freeze({ wireCode: "DAILY_QUOTA_EXCEEDED", envelope: "http_envelope", httpStatus: 429, retryable: true }),
|
|
12
|
-
Object.freeze({ wireCode: "IDEMPOTENCY_KEY_IN_FLIGHT", envelope: "http_envelope", httpStatus: 409, retryable: true }),
|
|
13
|
-
Object.freeze({ wireCode: "IDEMPOTENCY_KEY_REUSED_DIFFERENT_REQUEST", envelope: "http_envelope", httpStatus: 409, retryable: false }),
|
|
14
|
-
Object.freeze({ wireCode: "InternalFailureException", envelope: "http_envelope", httpStatus: 500, retryable: true }),
|
|
15
|
-
Object.freeze({ wireCode: "NotImplementedException", envelope: "http_envelope", httpStatus: 501, retryable: false }),
|
|
16
|
-
Object.freeze({ wireCode: "PaymentRequiredException", envelope: "http_envelope", httpStatus: 402, retryable: false }),
|
|
17
|
-
Object.freeze({ wireCode: "ResourceNotFoundException", envelope: "http_envelope", httpStatus: 404, retryable: false }),
|
|
18
|
-
Object.freeze({ wireCode: "ServiceUnavailableException", envelope: "http_envelope", httpStatus: 503, retryable: true }),
|
|
19
|
-
Object.freeze({ wireCode: "TIER_UPGRADE_REQUIRED", envelope: "http_envelope", httpStatus: 402, retryable: false }),
|
|
20
|
-
Object.freeze({ wireCode: "ThrottlingException", envelope: "http_envelope", httpStatus: 429, retryable: true }),
|
|
21
|
-
Object.freeze({ wireCode: "ValidationException", envelope: "http_envelope", httpStatus: 400, retryable: false }),
|
|
22
|
-
Object.freeze({ wireCode: "accessDenied", envelope: "http_envelope", httpStatus: 403, retryable: false }),
|
|
23
|
-
Object.freeze({ wireCode: "authExpired", envelope: "http_envelope", httpStatus: 401, retryable: true }),
|
|
24
|
-
Object.freeze({ wireCode: "internal_error", envelope: "sse_frame", httpStatus: 200, retryable: false }),
|
|
25
|
-
Object.freeze({ wireCode: "invalidRequest", envelope: "http_envelope", httpStatus: 400, retryable: false }),
|
|
26
|
-
Object.freeze({ wireCode: "not_found", envelope: "sse_frame", httpStatus: 200, retryable: false }),
|
|
27
|
-
Object.freeze({ wireCode: "session_required", envelope: "http_envelope", httpStatus: 403, retryable: false }),
|
|
28
|
-
Object.freeze({ wireCode: "timeout", envelope: "sse_frame", httpStatus: 200, retryable: true }),
|
|
29
|
-
Object.freeze({ wireCode: "unavailable", envelope: "http_envelope", httpStatus: 503, retryable: true })
|
|
30
|
-
]);
|
|
31
|
-
Object.freeze(
|
|
32
|
-
CONTRACT_RETRYABILITY.map((row) => row.wireCode)
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
// src/client/errors/contract.ts
|
|
36
|
-
var NORMALIZED_FOLD = Object.freeze({
|
|
37
|
-
// the default status-mapped envelope
|
|
38
|
-
ValidationException: "validationFailed",
|
|
39
|
-
AuthorizationException: "authRejected",
|
|
40
|
-
PaymentRequiredException: "paymentRequired",
|
|
41
|
-
AccessDeniedException: "accessDenied",
|
|
42
|
-
ResourceNotFoundException: "resourceNotFound",
|
|
43
|
-
ConflictException: "conflict",
|
|
44
|
-
ThrottlingException: "rateLimited",
|
|
45
|
-
InternalFailureException: "internalError",
|
|
46
|
-
NotImplementedException: "notImplemented",
|
|
47
|
-
ServiceUnavailableException: "serviceUnavailable",
|
|
48
|
-
// dict-detail codes on the widget surface
|
|
49
|
-
authExpired: "authExpired",
|
|
50
|
-
unavailable: "serviceUnavailable",
|
|
51
|
-
session_required: "sessionRequired",
|
|
52
|
-
accessDenied: "accessDenied",
|
|
53
|
-
invalidRequest: "sdkUpdateRequired",
|
|
54
|
-
// typed codes
|
|
55
|
-
IDEMPOTENCY_KEY_IN_FLIGHT: "idempotencyInFlight",
|
|
56
|
-
IDEMPOTENCY_KEY_REUSED_DIFFERENT_REQUEST: "idempotencyConflict",
|
|
57
|
-
DAILY_QUOTA_EXCEEDED: "quotaExceeded",
|
|
58
|
-
TIER_UPGRADE_REQUIRED: "paymentRequired",
|
|
59
|
-
// SSE error frames
|
|
60
|
-
not_found: "conversationNotFound",
|
|
61
|
-
timeout: "streamTimeout",
|
|
62
|
-
internal_error: "internalError"
|
|
63
|
-
});
|
|
64
|
-
var CONTRACT_ERROR_ROWS = Object.freeze(
|
|
65
|
-
CONTRACT_RETRYABILITY.map(
|
|
66
|
-
(generated) => Object.freeze({
|
|
67
|
-
wireCode: generated.wireCode,
|
|
68
|
-
envelope: generated.envelope,
|
|
69
|
-
retryable: generated.retryable,
|
|
70
|
-
normalized: NORMALIZED_FOLD[generated.wireCode]
|
|
71
|
-
})
|
|
72
|
-
)
|
|
73
|
-
);
|
|
74
|
-
var BY_UPPER = new Map(
|
|
75
|
-
CONTRACT_ERROR_ROWS.map((r) => [r.wireCode.toUpperCase(), r])
|
|
76
|
-
);
|
|
77
|
-
function contractErrorFor(wireCode) {
|
|
78
|
-
if (typeof wireCode !== "string") return void 0;
|
|
79
|
-
const key = wireCode.trim().toUpperCase();
|
|
80
|
-
return key.length > 0 ? BY_UPPER.get(key) : void 0;
|
|
81
|
-
}
|
|
82
|
-
var CATEGORY_OF = Object.freeze({
|
|
83
|
-
// the caller's sign-in state is wrong and a credential change is the remedy
|
|
84
|
-
authExpired: "authentication",
|
|
85
|
-
authRejected: "authentication",
|
|
86
|
-
sessionRequired: "authentication",
|
|
87
|
-
// signed in, correctly identified, and still not allowed
|
|
88
|
-
accessDenied: "authorization",
|
|
89
|
-
// a plan gate, not a permission failure: the remedy is commercial
|
|
90
|
-
paymentRequired: "entitlement",
|
|
91
|
-
// slow down / come back later
|
|
92
|
-
quotaExceeded: "rateLimit",
|
|
93
|
-
rateLimited: "rateLimit",
|
|
94
|
-
// the request itself was wrong; retrying it unchanged can never succeed
|
|
95
|
-
validationFailed: "invalidRequest",
|
|
96
|
-
sdkUpdateRequired: "invalidRequest",
|
|
97
|
-
// the target is gone, hidden, or was never visible to this caller
|
|
98
|
-
conversationNotFound: "notFound",
|
|
99
|
-
petNotFound: "notFound",
|
|
100
|
-
resourceNotFound: "notFound",
|
|
101
|
-
// something else already holds the thing
|
|
102
|
-
conflict: "conflict",
|
|
103
|
-
idempotencyConflict: "conflict",
|
|
104
|
-
idempotencyInFlight: "conflict",
|
|
105
|
-
// the pipe, not the request: timeouts, drops, a briefly unreachable dependency
|
|
106
|
-
streamTimeout: "connection",
|
|
107
|
-
streamIncomplete: "connection",
|
|
108
|
-
upstreamLlmTimeout: "connection",
|
|
109
|
-
serviceUnavailable: "connection",
|
|
110
|
-
// the server admits fault
|
|
111
|
-
internalError: "server",
|
|
112
|
-
notImplemented: "server",
|
|
113
|
-
mediaUploadFailed: "media",
|
|
114
|
-
moderationBlocked: "policy",
|
|
115
|
-
unknown: "unknown"
|
|
116
|
-
});
|
|
117
|
-
function categoryFor(code) {
|
|
118
|
-
return CATEGORY_OF[code];
|
|
119
|
-
}
|
|
120
|
-
function fromWire(wire) {
|
|
121
|
-
const contractRow = contractErrorFor(wire);
|
|
122
|
-
if (contractRow) return contractRow.normalized;
|
|
123
|
-
const upper = (wire ?? "").trim().toUpperCase();
|
|
124
|
-
const canonical = upper.replace(/[^A-Z0-9]/g, "");
|
|
125
|
-
if (canonical === "AUTHREJECTED") return "authRejected";
|
|
126
|
-
if (upper.startsWith("AUTH")) return "authExpired";
|
|
127
|
-
switch (canonical) {
|
|
128
|
-
case "DAILYQUOTAEXCEEDED":
|
|
129
|
-
case "QUOTAEXCEEDED":
|
|
130
|
-
return "quotaExceeded";
|
|
131
|
-
case "RATELIMITED":
|
|
132
|
-
case "TOOMANYREQUESTS":
|
|
133
|
-
return "rateLimited";
|
|
134
|
-
case "IDEMPOTENCYKEYCONFLICT":
|
|
135
|
-
case "IDEMPOTENCYCONFLICT":
|
|
136
|
-
return "idempotencyConflict";
|
|
137
|
-
case "ACCESSDENIED":
|
|
138
|
-
case "FORBIDDEN":
|
|
139
|
-
return "accessDenied";
|
|
140
|
-
case "NOTFOUND":
|
|
141
|
-
case "CONVERSATIONNOTFOUND":
|
|
142
|
-
return "conversationNotFound";
|
|
143
|
-
case "PETNOTFOUND":
|
|
144
|
-
return "petNotFound";
|
|
145
|
-
case "TIMEOUT":
|
|
146
|
-
case "STREAMTIMEOUT":
|
|
147
|
-
return "streamTimeout";
|
|
148
|
-
// Analysis-domain deterministic 400s (NON-retryable). Additive per SPEC-00 §3.1 / SPEC-11b §2.6:
|
|
149
|
-
// fold to a NON-retried target so the SDK never retries a permanently-failing bad-input request.
|
|
150
|
-
// (Comment above the group so the empty fall-through cases stay lint-clean under no-fallthrough.)
|
|
151
|
-
case "MEDIAUPLOADFAILED":
|
|
152
|
-
case "UNRECOGNIZEDVIDEOFORMAT":
|
|
153
|
-
case "FILETOOSMALL":
|
|
154
|
-
return "mediaUploadFailed";
|
|
155
|
-
case "MODERATIONBLOCKED":
|
|
156
|
-
return "moderationBlocked";
|
|
157
|
-
case "UPSTREAMLLMTIMEOUT":
|
|
158
|
-
return "upstreamLlmTimeout";
|
|
159
|
-
case "INTERNALERROR":
|
|
160
|
-
case "INTERNALSERVERERROR":
|
|
161
|
-
return "internalError";
|
|
162
|
-
case "STREAMINCOMPLETE":
|
|
163
|
-
case "CHATSTREAMINCOMPLETE":
|
|
164
|
-
return "streamIncomplete";
|
|
165
|
-
// Deterministic 400s that can only come from an SDK bug -> unknown (NON-retryable). NEVER internalError
|
|
166
|
-
// (which IS auto-retried): mapping a bad-input 400 to a retryable code retries a request that never succeeds.
|
|
167
|
-
case "INVALIDANALYSISTYPE":
|
|
168
|
-
case "INVALIDUPLOADPURPOSE":
|
|
169
|
-
return "unknown";
|
|
170
|
-
default:
|
|
171
|
-
return "unknown";
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
var AUTO_RETRYABLE_CODES = /* @__PURE__ */ new Set([
|
|
175
|
-
"streamTimeout",
|
|
176
|
-
"rateLimited",
|
|
177
|
-
"internalError",
|
|
178
|
-
"upstreamLlmTimeout",
|
|
179
|
-
"streamIncomplete"
|
|
180
|
-
]);
|
|
181
|
-
function isAutoRetryable(code) {
|
|
182
|
-
return AUTO_RETRYABLE_CODES.has(code);
|
|
183
|
-
}
|
|
184
|
-
var RETRYABLE_CONTRACT_CODES = /* @__PURE__ */ new Set([
|
|
185
|
-
"serviceUnavailable",
|
|
186
|
-
"conflict",
|
|
187
|
-
"idempotencyInFlight"
|
|
188
|
-
]);
|
|
189
|
-
function isRetryableCode(code) {
|
|
190
|
-
return isAutoRetryable(code) || RETRYABLE_CONTRACT_CODES.has(code);
|
|
191
|
-
}
|
|
192
|
-
var BACKOFF_BASE_MS = 500;
|
|
193
|
-
var BACKOFF_CAP_MS = 8e3;
|
|
194
|
-
var MAX_RETRY_ATTEMPTS = 4;
|
|
195
|
-
function backoffDelayMs(attempt, rng = Math.random) {
|
|
196
|
-
const bounded = Math.max(0, attempt);
|
|
197
|
-
const ceiling = Math.min(BACKOFF_BASE_MS * 2 ** bounded, BACKOFF_CAP_MS);
|
|
198
|
-
return Math.floor(rng() * ceiling);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// src/client/errors/EverfurError.ts
|
|
202
|
-
var USER_FACING_COPY = Object.freeze({
|
|
203
|
-
authExpired: "Please sign in again to continue.",
|
|
204
|
-
authRejected: "Please sign in again to continue.",
|
|
205
|
-
sessionRequired: "This app needs to be updated to keep using Everfur.",
|
|
206
|
-
accessDenied: "This feature isn't available on your account right now.",
|
|
207
|
-
paymentRequired: "This is part of a paid plan.",
|
|
208
|
-
quotaExceeded: "You have used today's messages. This resets tomorrow.",
|
|
209
|
-
rateLimited: "You are sending messages quickly. Try again in a few seconds.",
|
|
210
|
-
validationFailed: "Something in that request was not right. Check the details and try again.",
|
|
211
|
-
sdkUpdateRequired: "This app needs to be updated to keep using Everfur.",
|
|
212
|
-
conflict: "That is already running. Wait for it to finish, then try again.",
|
|
213
|
-
idempotencyConflict: "That message was already delivered.",
|
|
214
|
-
idempotencyInFlight: "That is still processing. Give it a moment.",
|
|
215
|
-
conversationNotFound: "This conversation is no longer available.",
|
|
216
|
-
petNotFound: "We could not find that pet. Please pick a pet and try again.",
|
|
217
|
-
resourceNotFound: "We could not find that.",
|
|
218
|
-
streamTimeout: "The response timed out. Tap retry to try again.",
|
|
219
|
-
mediaUploadFailed: "We could not upload that photo. Please attach it again.",
|
|
220
|
-
moderationBlocked: "This request cannot be answered here.",
|
|
221
|
-
upstreamLlmTimeout: "The response timed out. Tap retry to try again.",
|
|
222
|
-
internalError: "We hit a snag on our end. Please try again.",
|
|
223
|
-
notImplemented: "That is not available yet.",
|
|
224
|
-
serviceUnavailable: "That service is briefly unavailable. Try again shortly.",
|
|
225
|
-
streamIncomplete: "The connection dropped mid-answer. Tap retry to continue.",
|
|
226
|
-
unknown: "Something went wrong. Please try again."
|
|
227
|
-
});
|
|
228
|
-
function userFacingError(code) {
|
|
229
|
-
return USER_FACING_COPY[code];
|
|
230
|
-
}
|
|
231
|
-
function defineHidden(target, key, value) {
|
|
232
|
-
Object.defineProperty(target, key, {
|
|
233
|
-
value,
|
|
234
|
-
enumerable: false,
|
|
235
|
-
writable: false,
|
|
236
|
-
configurable: false
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
function makeEverfurError(code, opts) {
|
|
240
|
-
contractErrorFor(opts?.wireCode);
|
|
241
|
-
const error = {
|
|
242
|
-
code,
|
|
243
|
-
category: categoryFor(code),
|
|
244
|
-
retryable: opts?.retryable ?? isRetryableCode(code),
|
|
245
|
-
displayMessage: userFacingError(code)
|
|
246
|
-
};
|
|
247
|
-
if (opts?.httpStatus !== void 0) error.httpStatus = opts.httpStatus;
|
|
248
|
-
if (opts?.requestId) error.requestId = opts.requestId;
|
|
249
|
-
if (opts?.wireMessage) defineHidden(error, "wireMessage", opts.wireMessage);
|
|
250
|
-
if (opts?.raw !== void 0 && opts.raw !== null) defineHidden(error, "raw", opts.raw);
|
|
251
|
-
return Object.freeze(error);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// src/client/errors/EverfurResult.ts
|
|
255
|
-
function ok(value, warning) {
|
|
256
|
-
return Object.freeze(
|
|
257
|
-
{ ok: true, value }
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
function err(error) {
|
|
261
|
-
return Object.freeze({ ok: false, error });
|
|
262
|
-
}
|
|
263
|
-
var EverfurConfigError = class _EverfurConfigError extends Error {
|
|
264
|
-
constructor(kind, message) {
|
|
265
|
-
super(message);
|
|
266
|
-
this.name = "EverfurConfigError";
|
|
267
|
-
this.kind = kind;
|
|
268
|
-
Object.setPrototypeOf(this, _EverfurConfigError.prototype);
|
|
269
|
-
}
|
|
270
|
-
};
|
|
271
|
-
|
|
272
|
-
// src/client/protocol/frames.ts
|
|
273
|
-
function assertNever(x) {
|
|
274
|
-
throw new Error(
|
|
275
|
-
`assertNever: unhandled discriminated-union member ${JSON.stringify(x)}`
|
|
276
|
-
);
|
|
277
|
-
}
|
|
278
|
-
function errorFrame(fields) {
|
|
279
|
-
return Object.freeze({
|
|
280
|
-
kind: "error",
|
|
281
|
-
terminal: true,
|
|
282
|
-
code: fields.code,
|
|
283
|
-
retryable: fields.retryable,
|
|
284
|
-
wireMessage: fields.wireMessage,
|
|
285
|
-
synthesized: fields.synthesized,
|
|
286
|
-
lastEventId: fields.lastEventId,
|
|
287
|
-
serverRequestId: null
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// src/client/identity.ts
|
|
292
|
-
function authHeaders(auth) {
|
|
293
|
-
const headers = {};
|
|
294
|
-
if (auth.mode === "personalized") {
|
|
295
|
-
headers.authorization = `Bearer ${auth.token}`;
|
|
296
|
-
} else {
|
|
297
|
-
headers["x-everfur-partner-key"] = auth.publishableKey;
|
|
298
|
-
}
|
|
299
|
-
if (auth.userRef) headers["x-everfur-user-ref"] = auth.userRef;
|
|
300
|
-
if (auth.sessionId) headers["x-everfur-session-id"] = auth.sessionId;
|
|
301
|
-
return headers;
|
|
302
|
-
}
|
|
303
|
-
function withAuthHeaders(req, auth) {
|
|
304
|
-
return { ...req, headers: { ...stripAuthHeaders(req.headers), ...authHeaders(auth) } };
|
|
305
|
-
}
|
|
306
|
-
function stripAuthHeaders(headers) {
|
|
307
|
-
if (!headers) return {};
|
|
308
|
-
const out = {};
|
|
309
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
310
|
-
const lower = key.toLowerCase();
|
|
311
|
-
if (lower === "authorization" || lower === "x-everfur-partner-key") continue;
|
|
312
|
-
out[key] = value;
|
|
313
|
-
}
|
|
314
|
-
return out;
|
|
315
|
-
}
|
|
316
|
-
async function authedSend(req, ctx) {
|
|
317
|
-
if (ctx.mode === "anonymous") {
|
|
318
|
-
return ctx.transport.request(
|
|
319
|
-
withAuthHeaders(req, {
|
|
320
|
-
mode: "anonymous",
|
|
321
|
-
publishableKey: ctx.publishableKey,
|
|
322
|
-
userRef: ctx.userRef,
|
|
323
|
-
...ctx.sessionId ? { sessionId: ctx.sessionId } : {}
|
|
324
|
-
})
|
|
325
|
-
);
|
|
326
|
-
}
|
|
327
|
-
let token;
|
|
328
|
-
try {
|
|
329
|
-
token = await ctx.identity.currentToken();
|
|
330
|
-
} catch {
|
|
331
|
-
return err(authRejectedError());
|
|
332
|
-
}
|
|
333
|
-
const first = await ctx.transport.request(
|
|
334
|
-
withAuthHeaders(req, {
|
|
335
|
-
mode: "personalized",
|
|
336
|
-
token,
|
|
337
|
-
userRef: ctx.userRef,
|
|
338
|
-
...ctx.sessionId ? { sessionId: ctx.sessionId } : {}
|
|
339
|
-
})
|
|
340
|
-
);
|
|
341
|
-
if (first.ok || first.error.httpStatus !== 401) return first;
|
|
342
|
-
let refreshed;
|
|
343
|
-
try {
|
|
344
|
-
refreshed = await ctx.identity.currentToken({ forceRefresh: true });
|
|
345
|
-
} catch {
|
|
346
|
-
return err(authRejectedError());
|
|
347
|
-
}
|
|
348
|
-
const retry = await ctx.transport.request(
|
|
349
|
-
withAuthHeaders(req, {
|
|
350
|
-
mode: "personalized",
|
|
351
|
-
token: refreshed,
|
|
352
|
-
userRef: ctx.userRef,
|
|
353
|
-
...ctx.sessionId ? { sessionId: ctx.sessionId } : {}
|
|
354
|
-
})
|
|
355
|
-
);
|
|
356
|
-
if (!retry.ok && retry.error.httpStatus === 401) {
|
|
357
|
-
return err(authRejectedError(retry.error.httpStatus));
|
|
358
|
-
}
|
|
359
|
-
return retry;
|
|
360
|
-
}
|
|
361
|
-
function authRejectedError(httpStatus) {
|
|
362
|
-
return makeEverfurError("authRejected", {
|
|
363
|
-
retryable: false,
|
|
364
|
-
...httpStatus !== void 0 ? { httpStatus } : {}
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// src/client/transport.ts
|
|
369
|
-
function networkError(cause) {
|
|
370
|
-
return makeEverfurError("internalError", { retryable: true, wireMessage: describeCause(cause) });
|
|
371
|
-
}
|
|
372
|
-
function describeCause(cause) {
|
|
373
|
-
if (cause instanceof Error) return cause.message;
|
|
374
|
-
return typeof cause === "string" ? cause : "network request failed";
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
// src/client/funnel/requestFunnel.ts
|
|
378
|
-
var FUNNEL_UNARY_TIMEOUT_MS = 3e4;
|
|
379
|
-
var RETRY_OVERRIDE_MAX_MS = BACKOFF_CAP_MS * 2;
|
|
380
|
-
var IDEMPOTENCY_HEADER = "idempotency-key";
|
|
381
|
-
var MUTATING_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
382
|
-
var NOOP_OBSERVER = Object.freeze({
|
|
383
|
-
onEvent() {
|
|
384
|
-
}
|
|
385
|
-
});
|
|
386
|
-
var defaultFunnelTimers = Object.freeze({
|
|
387
|
-
setTimeout: (handler, ms) => setTimeout(handler, ms),
|
|
388
|
-
clearTimeout: (handle) => clearTimeout(handle),
|
|
389
|
-
now: () => Date.now()
|
|
390
|
-
});
|
|
391
|
-
function createRequestFunnel(deps) {
|
|
392
|
-
const observer = deps.observer ?? NOOP_OBSERVER;
|
|
393
|
-
const timers = deps.timers ?? defaultFunnelTimers;
|
|
394
|
-
const rng = deps.rng ?? Math.random;
|
|
395
|
-
const sleep = deps.sleep ?? ((ms, signal) => delayAbortable(ms, timers, signal));
|
|
396
|
-
const ceiling = deps.maxAttempts ?? MAX_RETRY_ATTEMPTS;
|
|
397
|
-
const timeoutMs = deps.unaryTimeoutMs ?? FUNNEL_UNARY_TIMEOUT_MS;
|
|
398
|
-
const mintKey = deps.newIdempotencyKey ?? defaultIdempotencyKeyFactory();
|
|
399
|
-
const emit = (event) => {
|
|
400
|
-
try {
|
|
401
|
-
observer.onEvent(event);
|
|
402
|
-
} catch {
|
|
403
|
-
}
|
|
404
|
-
};
|
|
405
|
-
async function send(call, req, opts) {
|
|
406
|
-
const perCallMaxAttempts = opts?.maxAttempts;
|
|
407
|
-
const maxAttempts = Math.max(1, perCallMaxAttempts ?? ceiling);
|
|
408
|
-
const idempotent = opts?.idempotent ?? MUTATING_METHODS.has(req.method);
|
|
409
|
-
const idempotencyKey = idempotent ? mintKey() : null;
|
|
410
|
-
const base = assembleHeaders(req, idempotencyKey);
|
|
411
|
-
let attempt = 0;
|
|
412
|
-
for (; ; ) {
|
|
413
|
-
const stamp = (extra) => Object.freeze({
|
|
414
|
-
call,
|
|
415
|
-
method: req.method,
|
|
416
|
-
path: req.path,
|
|
417
|
-
attempt,
|
|
418
|
-
...idempotencyKey ? { idempotencyKey } : {},
|
|
419
|
-
...extra
|
|
420
|
-
});
|
|
421
|
-
emit(stamp({ phase: "attempt" }));
|
|
422
|
-
const deadline = boundedSignal(timeoutMs, timers);
|
|
423
|
-
const merged = mergeSignals(deadline.signal, req.signal);
|
|
424
|
-
let settled;
|
|
425
|
-
try {
|
|
426
|
-
settled = await authedSend({ ...base, signal: merged.signal }, deps.auth);
|
|
427
|
-
} catch (cause) {
|
|
428
|
-
settled = err(networkError(cause));
|
|
429
|
-
} finally {
|
|
430
|
-
merged.dispose();
|
|
431
|
-
deadline.cancel();
|
|
432
|
-
}
|
|
433
|
-
if (settled.ok) {
|
|
434
|
-
emit(stamp({ phase: "settled", ok: true, reason: "ok" }));
|
|
435
|
-
return settled;
|
|
436
|
-
}
|
|
437
|
-
const error = settled.error;
|
|
438
|
-
const decision = decideRetry(error, attempt, {
|
|
439
|
-
maxAttempts,
|
|
440
|
-
rng,
|
|
441
|
-
now: timers.now(),
|
|
442
|
-
aborted: req.signal?.aborted === true,
|
|
443
|
-
...perCallMaxAttempts !== void 0 ? { perCallMaxAttempts } : {},
|
|
444
|
-
...deps.errorPolicy !== void 0 ? { policy: deps.errorPolicy } : {}
|
|
445
|
-
});
|
|
446
|
-
const settledEvent = {
|
|
447
|
-
ok: false,
|
|
448
|
-
code: error.code,
|
|
449
|
-
reason: decision.reason,
|
|
450
|
-
...error.httpStatus !== void 0 ? { httpStatus: error.httpStatus } : {},
|
|
451
|
-
...error.requestId !== void 0 ? { requestId: error.requestId } : {}
|
|
452
|
-
};
|
|
453
|
-
if (!decision.retry) {
|
|
454
|
-
emit(stamp({ phase: "settled", ...settledEvent }));
|
|
455
|
-
return err(error);
|
|
456
|
-
}
|
|
457
|
-
emit(stamp({ phase: "retry", ...settledEvent, delayMs: decision.delayMs }));
|
|
458
|
-
await sleep(decision.delayMs, req.signal);
|
|
459
|
-
if (req.signal?.aborted === true) {
|
|
460
|
-
emit(stamp({ phase: "settled", ...settledEvent, reason: "aborted" }));
|
|
461
|
-
return err(error);
|
|
462
|
-
}
|
|
463
|
-
attempt += 1;
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
async function authorize(req) {
|
|
467
|
-
return withAuthHeaders(req, await resolveDeviceAuth(deps.auth));
|
|
468
|
-
}
|
|
469
|
-
async function* stream(call, req, signal) {
|
|
470
|
-
emit(
|
|
471
|
-
Object.freeze({
|
|
472
|
-
call,
|
|
473
|
-
phase: "stream.open",
|
|
474
|
-
method: req.method,
|
|
475
|
-
path: req.path,
|
|
476
|
-
attempt: 0
|
|
477
|
-
})
|
|
478
|
-
);
|
|
479
|
-
try {
|
|
480
|
-
yield* deps.auth.transport.stream(req, signal);
|
|
481
|
-
} catch {
|
|
482
|
-
yield errorFrame({
|
|
483
|
-
// The same wire spelling sse.ts synthesizes, so `fromWire` folds it to the retryable `internalError`
|
|
484
|
-
// like any other transport failure. wireMessage stays null: it is the SERVER's text, and a thrown
|
|
485
|
-
// host exception's `.message` is not that.
|
|
486
|
-
code: "internal_error",
|
|
487
|
-
retryable: true,
|
|
488
|
-
wireMessage: null,
|
|
489
|
-
synthesized: true,
|
|
490
|
-
lastEventId: null});
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
return Object.freeze({ send, authorize, stream });
|
|
494
|
-
}
|
|
495
|
-
function assembleHeaders(req, idempotencyKey) {
|
|
496
|
-
const headers = {};
|
|
497
|
-
for (const [key, value] of Object.entries(req.headers ?? {})) {
|
|
498
|
-
if (key.toLowerCase() === IDEMPOTENCY_HEADER) continue;
|
|
499
|
-
headers[key] = value;
|
|
500
|
-
}
|
|
501
|
-
if (idempotencyKey !== null) headers["Idempotency-Key"] = idempotencyKey;
|
|
502
|
-
return { ...req, headers };
|
|
503
|
-
}
|
|
504
|
-
function defaultIdempotencyKeyFactory() {
|
|
505
|
-
let counter = 0;
|
|
506
|
-
return () => `efk_${Date.now().toString(36)}_${(counter++).toString(36)}_${Math.random().toString(36).slice(2, 12)}`;
|
|
507
|
-
}
|
|
508
|
-
var POLICY_MAX_ATTEMPTS_CAP = 10;
|
|
509
|
-
function decideRetry(error, attempt, ctx) {
|
|
510
|
-
if (ctx.aborted) return { retry: false, reason: "aborted" };
|
|
511
|
-
const throttle = throttleKind(error);
|
|
512
|
-
if (throttle === "quota") {
|
|
513
|
-
return { retry: false, reason: "daily-quota-exhausted" };
|
|
514
|
-
}
|
|
515
|
-
if (throttle !== "burst" && !isAutoRetryable(error.code)) {
|
|
516
|
-
return { retry: false, reason: "terminal-code" };
|
|
517
|
-
}
|
|
518
|
-
const reason = throttle === "burst" ? "burst-throttle" : "retryable-code";
|
|
519
|
-
const policyVetoedRetry = () => {
|
|
520
|
-
if (ctx.policy === void 0) return false;
|
|
521
|
-
try {
|
|
522
|
-
return !ctx.policy.shouldRetry(error.code, attempt);
|
|
523
|
-
} catch {
|
|
524
|
-
return false;
|
|
525
|
-
}
|
|
526
|
-
};
|
|
527
|
-
if (policyVetoedRetry()) return { retry: false, reason: "terminal-code" };
|
|
528
|
-
const policyCeiling = () => {
|
|
529
|
-
if (ctx.policy === void 0) return null;
|
|
530
|
-
let raw;
|
|
531
|
-
try {
|
|
532
|
-
raw = ctx.policy.maxAttempts(error.code);
|
|
533
|
-
} catch {
|
|
534
|
-
return null;
|
|
535
|
-
}
|
|
536
|
-
if (typeof raw !== "number" || !Number.isFinite(raw)) return null;
|
|
537
|
-
return Math.max(1, Math.min(POLICY_MAX_ATTEMPTS_CAP, Math.floor(raw)));
|
|
538
|
-
};
|
|
539
|
-
const ceiling = ctx.perCallMaxAttempts ?? policyCeiling() ?? ctx.maxAttempts;
|
|
540
|
-
if (attempt + 1 >= ceiling) return { retry: false, reason: "attempts-exhausted" };
|
|
541
|
-
const override = retryOverrideMs(error, ctx.now);
|
|
542
|
-
if (override !== null) {
|
|
543
|
-
if (override > RETRY_OVERRIDE_MAX_MS) {
|
|
544
|
-
return { retry: false, reason: "server-reset-beyond-budget" };
|
|
545
|
-
}
|
|
546
|
-
return { retry: true, reason, delayMs: override };
|
|
547
|
-
}
|
|
548
|
-
return { retry: true, reason, delayMs: backoffDelayMs(attempt, ctx.rng) };
|
|
549
|
-
}
|
|
550
|
-
function throttleKind(error) {
|
|
551
|
-
const code = error.code === "unknown" ? fromWire(rawWireCode(error.raw)) : error.code;
|
|
552
|
-
if (code === "quotaExceeded") return "quota";
|
|
553
|
-
if (code === "rateLimited") return "burst";
|
|
554
|
-
if (error.httpStatus === 429) {
|
|
555
|
-
return "burst";
|
|
556
|
-
}
|
|
557
|
-
return null;
|
|
558
|
-
}
|
|
559
|
-
function retryOverrideMs(error, now) {
|
|
560
|
-
const at = findResetsAt(error.raw);
|
|
561
|
-
if (at === null) return null;
|
|
562
|
-
const delta = at - now;
|
|
563
|
-
return delta > 0 ? delta : 0;
|
|
564
|
-
}
|
|
565
|
-
function findResetsAt(raw) {
|
|
566
|
-
for (const scope of candidateScopes(raw)) {
|
|
567
|
-
const value = scope["resets_at"];
|
|
568
|
-
const parsed = parseInstantMs(value);
|
|
569
|
-
if (parsed !== null) return parsed;
|
|
570
|
-
}
|
|
571
|
-
return null;
|
|
572
|
-
}
|
|
573
|
-
function candidateScopes(raw) {
|
|
574
|
-
if (!isRecord(raw)) return [];
|
|
575
|
-
const out = [raw];
|
|
576
|
-
for (const nested of ["error", "detail"]) {
|
|
577
|
-
const value = raw[nested];
|
|
578
|
-
if (isRecord(value)) out.push(value);
|
|
579
|
-
}
|
|
580
|
-
return out;
|
|
581
|
-
}
|
|
582
|
-
function parseInstantMs(value) {
|
|
583
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
584
|
-
return value < 1e11 ? Math.round(value * 1e3) : Math.round(value);
|
|
585
|
-
}
|
|
586
|
-
if (typeof value === "string" && value.trim().length > 0) {
|
|
587
|
-
const parsed = Date.parse(value);
|
|
588
|
-
return Number.isFinite(parsed) ? parsed : null;
|
|
589
|
-
}
|
|
590
|
-
return null;
|
|
591
|
-
}
|
|
592
|
-
function rawWireCode(raw) {
|
|
593
|
-
for (const scope of candidateScopes(raw)) {
|
|
594
|
-
const code = scope["code"];
|
|
595
|
-
if (typeof code === "string" && code.length > 0) return code;
|
|
596
|
-
}
|
|
597
|
-
return null;
|
|
598
|
-
}
|
|
599
|
-
function isRecord(value) {
|
|
600
|
-
return typeof value === "object" && value !== null;
|
|
601
|
-
}
|
|
602
|
-
async function resolveDeviceAuth(auth) {
|
|
603
|
-
if (auth.mode === "anonymous") {
|
|
604
|
-
return {
|
|
605
|
-
mode: "anonymous",
|
|
606
|
-
publishableKey: auth.publishableKey,
|
|
607
|
-
userRef: auth.userRef,
|
|
608
|
-
...auth.sessionId ? { sessionId: auth.sessionId } : {}
|
|
609
|
-
};
|
|
610
|
-
}
|
|
611
|
-
return {
|
|
612
|
-
mode: "personalized",
|
|
613
|
-
token: await auth.identity.currentToken(),
|
|
614
|
-
userRef: auth.userRef,
|
|
615
|
-
...auth.sessionId ? { sessionId: auth.sessionId } : {}
|
|
616
|
-
};
|
|
617
|
-
}
|
|
618
|
-
function boundedSignal(ms, timers) {
|
|
619
|
-
const ctrl = new AbortController();
|
|
620
|
-
const handle = timers.setTimeout(() => ctrl.abort(), ms);
|
|
621
|
-
return { signal: ctrl.signal, cancel: () => timers.clearTimeout(handle) };
|
|
622
|
-
}
|
|
623
|
-
function mergeSignals(deadline, caller) {
|
|
624
|
-
const noop = () => void 0;
|
|
625
|
-
if (!caller) return { signal: deadline, dispose: noop };
|
|
626
|
-
const ctrl = new AbortController();
|
|
627
|
-
if (deadline.aborted || caller.aborted) {
|
|
628
|
-
ctrl.abort();
|
|
629
|
-
return { signal: ctrl.signal, dispose: noop };
|
|
630
|
-
}
|
|
631
|
-
const onAbort = () => ctrl.abort();
|
|
632
|
-
deadline.addEventListener("abort", onAbort, { once: true });
|
|
633
|
-
caller.addEventListener("abort", onAbort, { once: true });
|
|
634
|
-
return {
|
|
635
|
-
signal: ctrl.signal,
|
|
636
|
-
dispose: () => {
|
|
637
|
-
deadline.removeEventListener("abort", onAbort);
|
|
638
|
-
caller.removeEventListener("abort", onAbort);
|
|
639
|
-
}
|
|
640
|
-
};
|
|
641
|
-
}
|
|
642
|
-
function delayAbortable(ms, timers, signal) {
|
|
643
|
-
if (ms <= 0 || signal?.aborted === true) return Promise.resolve();
|
|
644
|
-
return new Promise((resolve) => {
|
|
645
|
-
const handle = timers.setTimeout(() => {
|
|
646
|
-
signal?.removeEventListener("abort", onAbort);
|
|
647
|
-
resolve();
|
|
648
|
-
}, ms);
|
|
649
|
-
const onAbort = () => {
|
|
650
|
-
timers.clearTimeout(handle);
|
|
651
|
-
resolve();
|
|
652
|
-
};
|
|
653
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
654
|
-
});
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
// src/core/ports/TelemetryPort.ts
|
|
658
|
-
var noopTelemetry = Object.freeze({
|
|
659
|
-
track() {
|
|
660
|
-
},
|
|
661
|
-
trackExpectedFailure() {
|
|
662
|
-
}
|
|
663
|
-
});
|
|
664
|
-
function fileHandleFromMedia(source) {
|
|
665
|
-
const handle = {
|
|
666
|
-
uri: source.uri,
|
|
667
|
-
mimeType: source.mimeType
|
|
668
|
-
};
|
|
669
|
-
const size = mediaByteSize(source);
|
|
670
|
-
if (size !== null) handle.sizeBytes = size;
|
|
671
|
-
return Object.freeze(handle);
|
|
672
|
-
}
|
|
673
|
-
function mediaByteSize(source) {
|
|
674
|
-
const candidate = source.byteSize ?? source.sizeBytes;
|
|
675
|
-
return typeof candidate === "number" && Number.isFinite(candidate) && candidate >= 0 ? candidate : null;
|
|
676
|
-
}
|
|
677
|
-
function captureOptionsFor(mode, maxDurationSeconds) {
|
|
678
|
-
return maxDurationSeconds === void 0 ? Object.freeze({ mode }) : Object.freeze({ mode, maxDurationSeconds });
|
|
679
|
-
}
|
|
680
|
-
function isCaptureAvailable(camera) {
|
|
681
|
-
return camera != null && camera.isAvailable === true;
|
|
682
|
-
}
|
|
683
|
-
var defaultTimers = Object.freeze({
|
|
684
|
-
setTimeout: (handler, ms) => setTimeout(handler, ms),
|
|
685
|
-
clearTimeout: (handle) => clearTimeout(handle),
|
|
686
|
-
now: () => Date.now()
|
|
687
|
-
});
|
|
688
|
-
function delayAbortable2(ms, signal, timers) {
|
|
689
|
-
return new Promise((resolve) => {
|
|
690
|
-
if (signal.aborted) {
|
|
691
|
-
resolve();
|
|
692
|
-
return;
|
|
693
|
-
}
|
|
694
|
-
const holder = { handle: void 0 };
|
|
695
|
-
const done = () => {
|
|
696
|
-
timers.clearTimeout(holder.handle);
|
|
697
|
-
signal.removeEventListener("abort", done);
|
|
698
|
-
resolve();
|
|
699
|
-
};
|
|
700
|
-
holder.handle = timers.setTimeout(done, ms);
|
|
701
|
-
signal.addEventListener("abort", done, { once: true });
|
|
702
|
-
});
|
|
703
|
-
}
|
|
704
|
-
function mergeSignals2(a, b) {
|
|
705
|
-
if (!b) return a;
|
|
706
|
-
const ctrl = new AbortController();
|
|
707
|
-
if (a.aborted || b.aborted) {
|
|
708
|
-
ctrl.abort();
|
|
709
|
-
return ctrl.signal;
|
|
710
|
-
}
|
|
711
|
-
const onAbort = () => ctrl.abort();
|
|
712
|
-
a.addEventListener("abort", onAbort, { once: true });
|
|
713
|
-
b.addEventListener("abort", onAbort, { once: true });
|
|
714
|
-
return ctrl.signal;
|
|
715
|
-
}
|
|
716
|
-
var MediaStore = class {
|
|
717
|
-
constructor(initial) {
|
|
718
|
-
this.listeners = /* @__PURE__ */ new Set();
|
|
719
|
-
this.epoch = 0;
|
|
720
|
-
this.inFlight = null;
|
|
721
|
-
this.snapshot = Object.freeze({ ...initial });
|
|
722
|
-
}
|
|
723
|
-
subscribe(listener) {
|
|
724
|
-
this.listeners.add(listener);
|
|
725
|
-
return () => {
|
|
726
|
-
this.listeners.delete(listener);
|
|
727
|
-
};
|
|
728
|
-
}
|
|
729
|
-
getSnapshot() {
|
|
730
|
-
return this.snapshot;
|
|
731
|
-
}
|
|
732
|
-
patch(partial) {
|
|
733
|
-
this.snapshot = Object.freeze({ ...this.snapshot, ...partial });
|
|
734
|
-
for (const listener of this.listeners) listener();
|
|
735
|
-
}
|
|
736
|
-
beginOperation(external) {
|
|
737
|
-
this.inFlight?.abort();
|
|
738
|
-
const ctrl = new AbortController();
|
|
739
|
-
this.inFlight = ctrl;
|
|
740
|
-
const epoch = this.epoch;
|
|
741
|
-
const signal = external ? mergeSignals2(ctrl.signal, external) : ctrl.signal;
|
|
742
|
-
return {
|
|
743
|
-
signal,
|
|
744
|
-
epoch,
|
|
745
|
-
settle: () => {
|
|
746
|
-
if (this.inFlight === ctrl) this.inFlight = null;
|
|
747
|
-
}
|
|
748
|
-
};
|
|
749
|
-
}
|
|
750
|
-
alive(epoch) {
|
|
751
|
-
return epoch === this.epoch;
|
|
752
|
-
}
|
|
753
|
-
/** Re-scope on user/pet switch: aborts in-flight and bumps the epoch so a late write drops. */
|
|
754
|
-
rescope() {
|
|
755
|
-
this.epoch += 1;
|
|
756
|
-
this.inFlight?.abort();
|
|
757
|
-
this.inFlight = null;
|
|
758
|
-
}
|
|
759
|
-
/** Idempotent teardown: aborts in-flight, bumps the epoch, drops every listener. */
|
|
760
|
-
dispose() {
|
|
761
|
-
this.epoch += 1;
|
|
762
|
-
this.inFlight?.abort();
|
|
763
|
-
this.inFlight = null;
|
|
764
|
-
this.listeners.clear();
|
|
765
|
-
}
|
|
766
|
-
};
|
|
767
|
-
|
|
768
|
-
// src/client/text.ts
|
|
769
|
-
function hasLoneSurrogate(value) {
|
|
770
|
-
for (let i = 0; i < value.length; i += 1) {
|
|
771
|
-
const code = value.charCodeAt(i);
|
|
772
|
-
if (code >= 55296 && code <= 56319) {
|
|
773
|
-
const low = value.charCodeAt(i + 1);
|
|
774
|
-
if (!(low >= 56320 && low <= 57343)) return true;
|
|
775
|
-
i += 1;
|
|
776
|
-
} else if (code >= 56320 && code <= 57343) {
|
|
777
|
-
return true;
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
return false;
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
// src/core/media/projection.ts
|
|
784
|
-
var CONSUMER_SAFE_DETECTION_KEYS = Object.freeze([
|
|
785
|
-
"primary_label",
|
|
786
|
-
"primary_confidence",
|
|
787
|
-
"subtype_label",
|
|
788
|
-
"subtype_display",
|
|
789
|
-
"subtype_confidence",
|
|
790
|
-
"risk_tier",
|
|
791
|
-
"confidence_tier",
|
|
792
|
-
"copy_short",
|
|
793
|
-
"disease_name",
|
|
794
|
-
"disease_framing",
|
|
795
|
-
"model_version",
|
|
796
|
-
"skin_v2",
|
|
797
|
-
"eye_v2",
|
|
798
|
-
"llm_checkup"
|
|
799
|
-
]);
|
|
800
|
-
var OPERATOR_INTERNAL_KEYS = Object.freeze([
|
|
801
|
-
"decision_audit",
|
|
802
|
-
"decision_thresholds",
|
|
803
|
-
"per_class_auroc",
|
|
804
|
-
"ood_layers",
|
|
805
|
-
"gate1_score",
|
|
806
|
-
"possible_conditions",
|
|
807
|
-
"top_class"
|
|
808
|
-
]);
|
|
809
|
-
var BOOLEAN_KEYS = /* @__PURE__ */ new Set(["skin_v2", "eye_v2", "llm_checkup"]);
|
|
810
|
-
var CONFIDENCE_KEYS = /* @__PURE__ */ new Set(["primary_confidence", "subtype_confidence"]);
|
|
811
|
-
var RISK_TIER_VALUES = /* @__PURE__ */ new Set(["low_risk", "concern", "uncertain"]);
|
|
812
|
-
var CONFIDENCE_TIER_VALUES = /* @__PURE__ */ new Set(["possible", "probable"]);
|
|
813
|
-
var STRING_LIMITS = Object.freeze({
|
|
814
|
-
primary_label: 64,
|
|
815
|
-
subtype_label: 64,
|
|
816
|
-
subtype_display: 128,
|
|
817
|
-
copy_short: 512,
|
|
818
|
-
disease_name: 128,
|
|
819
|
-
disease_framing: 512,
|
|
820
|
-
model_version: 64
|
|
821
|
-
});
|
|
822
|
-
function projectDetectionMetadata(raw) {
|
|
823
|
-
const source = isRecord2(raw) ? raw : {};
|
|
824
|
-
const out = {};
|
|
825
|
-
for (const key of CONSUMER_SAFE_DETECTION_KEYS) {
|
|
826
|
-
if (!(key in source)) continue;
|
|
827
|
-
const value = source[key];
|
|
828
|
-
const accepted = acceptKey(key, value);
|
|
829
|
-
if (accepted !== DROP) out[key] = accepted;
|
|
830
|
-
}
|
|
831
|
-
let dropped = 0;
|
|
832
|
-
for (const key of OPERATOR_INTERNAL_KEYS) {
|
|
833
|
-
if (key in source) dropped += 1;
|
|
834
|
-
}
|
|
835
|
-
return Object.freeze({ metadata: Object.freeze(out), droppedOperatorKeys: dropped });
|
|
836
|
-
}
|
|
837
|
-
var DROP = /* @__PURE__ */ Symbol("drop");
|
|
838
|
-
function acceptKey(key, value) {
|
|
839
|
-
if (BOOLEAN_KEYS.has(key)) return typeof value === "boolean" ? value : DROP;
|
|
840
|
-
if (CONFIDENCE_KEYS.has(key)) return isUnitConfidence(value) ? value : DROP;
|
|
841
|
-
if (key === "risk_tier") return typeof value === "string" && RISK_TIER_VALUES.has(value) ? value : DROP;
|
|
842
|
-
if (key === "confidence_tier") {
|
|
843
|
-
return typeof value === "string" && CONFIDENCE_TIER_VALUES.has(value) ? value : DROP;
|
|
844
|
-
}
|
|
845
|
-
if (typeof value !== "string") return DROP;
|
|
846
|
-
const limit = STRING_LIMITS[key] ?? 256;
|
|
847
|
-
return value.length > limit ? value.slice(0, limit) : value;
|
|
848
|
-
}
|
|
849
|
-
function isUnitConfidence(value) {
|
|
850
|
-
return typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
|
|
851
|
-
}
|
|
852
|
-
function parseInterpretation(raw) {
|
|
853
|
-
if (!isRecord2(raw)) return null;
|
|
854
|
-
const summary = asString(raw["summary"]);
|
|
855
|
-
const disclaimer = asString(raw["disclaimer"]);
|
|
856
|
-
const recommendations = asStringList(raw["recommendations"] ?? raw["recommendation"]);
|
|
857
|
-
if (summary === null && disclaimer === null && recommendations.length === 0) return null;
|
|
858
|
-
return Object.freeze({
|
|
859
|
-
summary: summary ?? "",
|
|
860
|
-
recommendations: Object.freeze(recommendations),
|
|
861
|
-
disclaimer: disclaimer ?? ""
|
|
862
|
-
});
|
|
863
|
-
}
|
|
864
|
-
function locateDetectionMetadata(body) {
|
|
865
|
-
if (!isRecord2(body)) return {};
|
|
866
|
-
if (isRecord2(body["metadata"])) return body["metadata"];
|
|
867
|
-
const detections = body["detections"];
|
|
868
|
-
if (Array.isArray(detections) && detections.length > 0) {
|
|
869
|
-
const first = detections[0];
|
|
870
|
-
if (isRecord2(first) && isRecord2(first["metadata"])) return first["metadata"];
|
|
871
|
-
if (isRecord2(first)) return first;
|
|
872
|
-
}
|
|
873
|
-
return body;
|
|
874
|
-
}
|
|
875
|
-
function locateInterpretation(body) {
|
|
876
|
-
if (!isRecord2(body)) return null;
|
|
877
|
-
if (isRecord2(body["interpretation"])) return body["interpretation"];
|
|
878
|
-
return body;
|
|
879
|
-
}
|
|
880
|
-
function isRecord2(value) {
|
|
881
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
882
|
-
}
|
|
883
|
-
function asString(value) {
|
|
884
|
-
return typeof value === "string" && value.length > 0 ? value : null;
|
|
885
|
-
}
|
|
886
|
-
function asStringList(value) {
|
|
887
|
-
if (typeof value === "string") return value.length > 0 ? [value] : [];
|
|
888
|
-
if (!Array.isArray(value)) return [];
|
|
889
|
-
return value.filter((entry) => typeof entry === "string" && entry.length > 0);
|
|
890
|
-
}
|
|
891
|
-
function asUnitNumber(value) {
|
|
892
|
-
return isUnitConfidence(value) ? value : null;
|
|
893
|
-
}
|
|
894
|
-
function asFiniteNumber(value) {
|
|
895
|
-
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
// src/core/media/pipeline.ts
|
|
899
|
-
var ENDPOINTS = Object.freeze({
|
|
900
|
-
initiate: "/widget/v1/uploads/initiate",
|
|
901
|
-
precheck: "/widget/v1/photo/precheck",
|
|
902
|
-
photoAnalyze: "/widget/v1/photo/analyze",
|
|
903
|
-
videoAnalyze: "/widget/v1/video/analyze",
|
|
904
|
-
// `jobId` arrives from the WIRE, so it is not under this SDK's control, and `encodeURIComponent` throws
|
|
905
|
-
// URIError on a lone surrogate. Returning null lets the caller settle instead of throwing out of the
|
|
906
|
-
// poller. These routes are dark today, which is exactly why the guard goes in now: it costs one line and
|
|
907
|
-
// the gap re-opens silently the moment they land.
|
|
908
|
-
job: (jobId) => hasLoneSurrogate(jobId) ? null : `/widget/v1/analysis/jobs/${encodeURIComponent(jobId)}`
|
|
909
|
-
});
|
|
910
|
-
function brandJobId(raw) {
|
|
911
|
-
return raw;
|
|
912
|
-
}
|
|
913
|
-
async function initiateUpload(funnel, params) {
|
|
914
|
-
const body = { purpose: params.purpose, content_type: params.contentType };
|
|
915
|
-
if (params.byteSize !== null) body.byte_size = params.byteSize;
|
|
916
|
-
const req = {
|
|
917
|
-
method: "POST",
|
|
918
|
-
path: ENDPOINTS.initiate,
|
|
919
|
-
body,
|
|
920
|
-
...params.signal ? { signal: params.signal } : {}
|
|
921
|
-
};
|
|
922
|
-
const res = await funnel.send("media.initiateUpload", req);
|
|
923
|
-
if (!res.ok) return err(res.error);
|
|
924
|
-
const policy = parsePresigned(res.value);
|
|
925
|
-
if (policy === null) return err(makeEverfurError("mediaUploadFailed", { retryable: false }));
|
|
926
|
-
return ok(policy);
|
|
927
|
-
}
|
|
928
|
-
function parsePresigned(raw) {
|
|
929
|
-
if (!isRecord2(raw)) return null;
|
|
930
|
-
const url = asString(raw["url"]);
|
|
931
|
-
const s3Key = asString(raw["s3_key"]);
|
|
932
|
-
if (url === null || s3Key === null) return null;
|
|
933
|
-
const fieldsRaw = raw["fields"];
|
|
934
|
-
const fields = {};
|
|
935
|
-
if (isRecord2(fieldsRaw)) {
|
|
936
|
-
for (const [key, value] of Object.entries(fieldsRaw)) {
|
|
937
|
-
if (typeof value === "string") fields[key] = value;
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
return Object.freeze({
|
|
941
|
-
url,
|
|
942
|
-
fields: Object.freeze(fields),
|
|
943
|
-
s3Key,
|
|
944
|
-
expiresAt: asFiniteNumber(raw["expires_at"])
|
|
945
|
-
});
|
|
946
|
-
}
|
|
947
|
-
async function stageUpload(upload, source, policy, idempotencyKey, signal) {
|
|
948
|
-
try {
|
|
949
|
-
await upload.presignedPut(fileHandleFromMedia(source), {
|
|
950
|
-
target: policy,
|
|
951
|
-
idempotencyKey,
|
|
952
|
-
...signal ? { signal } : {}
|
|
953
|
-
});
|
|
954
|
-
return ok(void 0);
|
|
955
|
-
} catch {
|
|
956
|
-
return err(makeEverfurError("mediaUploadFailed", { retryable: false }));
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
async function runPrecheck(funnel, params) {
|
|
960
|
-
const req = {
|
|
961
|
-
method: "POST",
|
|
962
|
-
path: ENDPOINTS.precheck,
|
|
963
|
-
body: { s3_key: params.s3Key, type: params.type },
|
|
964
|
-
...params.signal ? { signal: params.signal } : {}
|
|
965
|
-
};
|
|
966
|
-
const res = await funnel.send("media.precheck", req);
|
|
967
|
-
if (!res.ok) return { verdict: "proceed", reasonCodes: [], precheckToken: null };
|
|
968
|
-
return parsePrecheck(res.value);
|
|
969
|
-
}
|
|
970
|
-
function parsePrecheck(raw) {
|
|
971
|
-
if (!isRecord2(raw)) return { verdict: "proceed", reasonCodes: [], precheckToken: null };
|
|
972
|
-
const verdictRaw = asString(raw["verdict"]);
|
|
973
|
-
const verdict = verdictRaw === "retake" || verdictRaw === "reject" ? verdictRaw : "proceed";
|
|
974
|
-
const reasonCodes = Array.isArray(raw["reason_codes"]) ? raw["reason_codes"].filter((c) => typeof c === "string") : [];
|
|
975
|
-
return Object.freeze({
|
|
976
|
-
verdict,
|
|
977
|
-
reasonCodes: Object.freeze(reasonCodes),
|
|
978
|
-
precheckToken: asString(raw["precheck_token"])
|
|
979
|
-
});
|
|
980
|
-
}
|
|
981
|
-
async function submitAnalyze(funnel, path, body, detectGate, signal) {
|
|
982
|
-
const req = {
|
|
983
|
-
method: "POST",
|
|
984
|
-
path,
|
|
985
|
-
body,
|
|
986
|
-
...signal ? { signal } : {}
|
|
987
|
-
};
|
|
988
|
-
const res = await funnel.send("media.analyze", req);
|
|
989
|
-
if (res.ok) {
|
|
990
|
-
const job = parseAsyncJob(res.value);
|
|
991
|
-
return job !== null ? { kind: "async", job } : { kind: "sync", body: res.value };
|
|
992
|
-
}
|
|
993
|
-
const gate = detectGate(res.error.httpStatus, res.error);
|
|
994
|
-
if (gate !== null) return { kind: "gate", gate };
|
|
995
|
-
return { kind: "error", error: res.error };
|
|
996
|
-
}
|
|
997
|
-
function parseAsyncJob(raw) {
|
|
998
|
-
if (!isRecord2(raw)) return null;
|
|
999
|
-
const jobId = asString(raw["job_id"]);
|
|
1000
|
-
if (jobId === null) return null;
|
|
1001
|
-
const status = asString(raw["status"]);
|
|
1002
|
-
if (status !== null && isTerminalStatus(status)) return null;
|
|
1003
|
-
return Object.freeze({
|
|
1004
|
-
jobId: brandJobId(jobId),
|
|
1005
|
-
estimatedSeconds: asFiniteNumber(raw["estimated_completion_seconds"])
|
|
1006
|
-
});
|
|
1007
|
-
}
|
|
1008
|
-
var POLL_FIRST_DELAY_MS = 2e3;
|
|
1009
|
-
var POLL_MAX_DELAY_MS = 5e3;
|
|
1010
|
-
var POLL_BUDGET_MS = 9e4;
|
|
1011
|
-
async function pollJob(funnel, jobId, deps) {
|
|
1012
|
-
const { timers, signal, alive } = deps;
|
|
1013
|
-
const start = timers.now();
|
|
1014
|
-
const budget = Math.max(POLL_BUDGET_MS, (deps.estimatedSeconds ?? 0) * 1e3);
|
|
1015
|
-
let delay = POLL_FIRST_DELAY_MS;
|
|
1016
|
-
for (; ; ) {
|
|
1017
|
-
if (signal.aborted || !alive()) return { kind: "aborted" };
|
|
1018
|
-
await delayAbortable2(delay, signal, timers);
|
|
1019
|
-
if (signal.aborted || !alive()) return { kind: "aborted" };
|
|
1020
|
-
if (timers.now() - start > budget) return { kind: "timeout" };
|
|
1021
|
-
const path = ENDPOINTS.job(jobId);
|
|
1022
|
-
if (path === null) return { kind: "notFound" };
|
|
1023
|
-
const res = await funnel.send(
|
|
1024
|
-
"media.pollJob",
|
|
1025
|
-
{ method: "GET", path, ...signal ? { signal } : {} },
|
|
1026
|
-
{ maxAttempts: 1 }
|
|
1027
|
-
);
|
|
1028
|
-
if (signal.aborted || !alive()) return { kind: "aborted" };
|
|
1029
|
-
if (!res.ok) {
|
|
1030
|
-
if (res.error.httpStatus === 404) return { kind: "notFound" };
|
|
1031
|
-
delay = Math.min(POLL_MAX_DELAY_MS, delay + 1e3);
|
|
1032
|
-
continue;
|
|
1033
|
-
}
|
|
1034
|
-
const status = jobStatusOf(res.value);
|
|
1035
|
-
if (status === "COMPLETED") return { kind: "complete", body: res.value };
|
|
1036
|
-
if (status === "FAILED") return { kind: "failed", error: jobFailureError(res.value) };
|
|
1037
|
-
delay = Math.min(POLL_MAX_DELAY_MS, delay + 1e3);
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
function jobStatusOf(body) {
|
|
1041
|
-
if (!isRecord2(body)) return "PROCESSING";
|
|
1042
|
-
return (asString(body["status"]) ?? "PROCESSING").toUpperCase();
|
|
1043
|
-
}
|
|
1044
|
-
function isTerminalStatus(status) {
|
|
1045
|
-
const upper = status.toUpperCase();
|
|
1046
|
-
return upper === "COMPLETED" || upper === "FAILED";
|
|
1047
|
-
}
|
|
1048
|
-
function jobFailureError(body) {
|
|
1049
|
-
const wire = isRecord2(body) ? asString(body["error"]) ?? asString(body["error_code"]) : null;
|
|
1050
|
-
return makeEverfurError("internalError", { retryable: false, ...wire ? { wireMessage: wire } : {} });
|
|
1051
|
-
}
|
|
1052
|
-
function detectPhotoGate(httpStatus, _error) {
|
|
1053
|
-
if (httpStatus === 422) return { kind: "species_unsupported", species: "cat" };
|
|
1054
|
-
if (httpStatus === 400) return { kind: "pet_context_required" };
|
|
1055
|
-
return null;
|
|
1056
|
-
}
|
|
1057
|
-
function analyzeBody(s3Key, extra) {
|
|
1058
|
-
const body = { s3_key: s3Key };
|
|
1059
|
-
if (extra.type !== void 0) body.type = extra.type;
|
|
1060
|
-
if (extra.petRef != null) body.pet_id = extra.petRef;
|
|
1061
|
-
if (extra.precheckToken != null) body.precheck_token = extra.precheckToken;
|
|
1062
|
-
return body;
|
|
1063
|
-
}
|
|
1064
|
-
|
|
1065
|
-
// src/core/photo/PhotoController.ts
|
|
1066
|
-
var ACCEPTED = Object.freeze({ kind: "accepted" });
|
|
1067
|
-
function createPhotoController(deps) {
|
|
1068
|
-
return new PhotoController(deps);
|
|
1069
|
-
}
|
|
1070
|
-
var PhotoController = class extends MediaStore {
|
|
1071
|
-
constructor(deps) {
|
|
1072
|
-
super({
|
|
1073
|
-
status: "idle",
|
|
1074
|
-
region: deps.region ?? "skin",
|
|
1075
|
-
result: null,
|
|
1076
|
-
gate: ACCEPTED,
|
|
1077
|
-
error: null,
|
|
1078
|
-
isCaptureAvailable: isCaptureAvailable(deps.camera)
|
|
1079
|
-
});
|
|
1080
|
-
this.funnel = deps.funnel ?? createRequestFunnel({
|
|
1081
|
-
auth: deps.auth,
|
|
1082
|
-
...deps.errorPolicy ? { errorPolicy: deps.errorPolicy } : {}
|
|
1083
|
-
});
|
|
1084
|
-
this.camera = deps.camera;
|
|
1085
|
-
this.upload = deps.upload;
|
|
1086
|
-
this.telemetry = deps.telemetry ?? noopTelemetry;
|
|
1087
|
-
this.timers = deps.timers ?? defaultTimers;
|
|
1088
|
-
this.makeIdempotencyKey = deps.makeIdempotencyKey ?? defaultIdempotencyFactory();
|
|
1089
|
-
}
|
|
1090
|
-
/** Open the CameraPort (photo), then analyze. No-op when capture is unavailable; a cancel returns to idle. */
|
|
1091
|
-
async capture(region, opts) {
|
|
1092
|
-
if (!isCaptureAvailable(this.camera)) {
|
|
1093
|
-
return err(makeEverfurError("unknown", { retryable: false }));
|
|
1094
|
-
}
|
|
1095
|
-
let source;
|
|
1096
|
-
try {
|
|
1097
|
-
source = await this.camera.capture(captureOptionsFor("photo"));
|
|
1098
|
-
} catch {
|
|
1099
|
-
if (this.snapshot.status !== "idle") this.patch({ status: "idle" });
|
|
1100
|
-
return err(makeEverfurError("unknown", { retryable: false }));
|
|
1101
|
-
}
|
|
1102
|
-
return this.analyze(source, region, opts);
|
|
1103
|
-
}
|
|
1104
|
-
/**
|
|
1105
|
-
* Server enforces the species gate (§6). The SDK NEVER runs a model and NEVER guesses species. `petRef` is
|
|
1106
|
-
* REQUIRED so a skin/eye request always carries pet context (§6.3 client invariant / PAR-07).
|
|
1107
|
-
*/
|
|
1108
|
-
async analyze(source, region, opts) {
|
|
1109
|
-
const op = this.beginOperation(opts.signal);
|
|
1110
|
-
const idempotencyKey = this.makeIdempotencyKey();
|
|
1111
|
-
const startedAt = this.timers.now();
|
|
1112
|
-
this.patch({ status: "capturing", region, error: null, result: null, gate: ACCEPTED });
|
|
1113
|
-
try {
|
|
1114
|
-
const initiated = await initiateUpload(this.funnel, {
|
|
1115
|
-
purpose: "skin_scan",
|
|
1116
|
-
contentType: source.mimeType,
|
|
1117
|
-
byteSize: mediaByteSize(source),
|
|
1118
|
-
signal: op.signal
|
|
1119
|
-
});
|
|
1120
|
-
if (op.signal.aborted || !this.alive(op.epoch)) return abortedResult();
|
|
1121
|
-
if (!initiated.ok) return this.fail(op, initiated.error);
|
|
1122
|
-
const staged = await stageUpload(this.upload, source, initiated.value, idempotencyKey, op.signal);
|
|
1123
|
-
if (op.signal.aborted || !this.alive(op.epoch)) return abortedResult();
|
|
1124
|
-
if (!staged.ok) return this.fail(op, staged.error);
|
|
1125
|
-
const precheck = await runPrecheck(this.funnel, {
|
|
1126
|
-
s3Key: initiated.value.s3Key,
|
|
1127
|
-
type: region,
|
|
1128
|
-
signal: op.signal
|
|
1129
|
-
});
|
|
1130
|
-
if (op.signal.aborted || !this.alive(op.epoch)) return abortedResult();
|
|
1131
|
-
if (precheck.verdict !== "proceed") {
|
|
1132
|
-
return this.gateRetake(op, region, precheck.reasonCodes);
|
|
1133
|
-
}
|
|
1134
|
-
this.patch({ status: "analyzing" });
|
|
1135
|
-
const outcome = await submitAnalyze(
|
|
1136
|
-
this.funnel,
|
|
1137
|
-
ENDPOINTS.photoAnalyze,
|
|
1138
|
-
analyzeBody(initiated.value.s3Key, {
|
|
1139
|
-
type: region,
|
|
1140
|
-
petRef: opts.petRef,
|
|
1141
|
-
precheckToken: precheck.precheckToken
|
|
1142
|
-
}),
|
|
1143
|
-
detectPhotoGate,
|
|
1144
|
-
op.signal
|
|
1145
|
-
);
|
|
1146
|
-
if (op.signal.aborted || !this.alive(op.epoch)) return abortedResult();
|
|
1147
|
-
switch (outcome.kind) {
|
|
1148
|
-
case "error":
|
|
1149
|
-
return this.fail(op, outcome.error);
|
|
1150
|
-
case "gate":
|
|
1151
|
-
return this.gateDomain(op, region, outcome.gate);
|
|
1152
|
-
case "sync":
|
|
1153
|
-
return this.complete(op, outcome.body, region, null, startedAt);
|
|
1154
|
-
case "async": {
|
|
1155
|
-
const poll = await pollJob(this.funnel, outcome.job.jobId, {
|
|
1156
|
-
timers: this.timers,
|
|
1157
|
-
signal: op.signal,
|
|
1158
|
-
alive: () => this.alive(op.epoch),
|
|
1159
|
-
estimatedSeconds: outcome.job.estimatedSeconds
|
|
1160
|
-
});
|
|
1161
|
-
if (op.signal.aborted || !this.alive(op.epoch)) return abortedResult();
|
|
1162
|
-
switch (poll.kind) {
|
|
1163
|
-
case "aborted":
|
|
1164
|
-
return abortedResult();
|
|
1165
|
-
case "complete":
|
|
1166
|
-
return this.complete(op, poll.body, region, outcome.job.jobId, startedAt);
|
|
1167
|
-
case "failed":
|
|
1168
|
-
return this.fail(op, poll.error);
|
|
1169
|
-
case "timeout":
|
|
1170
|
-
return this.fail(op, makeEverfurError("streamTimeout", { retryable: true }));
|
|
1171
|
-
case "notFound":
|
|
1172
|
-
return this.fail(op, makeEverfurError("conversationNotFound", { retryable: false, httpStatus: 404 }));
|
|
1173
|
-
default:
|
|
1174
|
-
return this.fail(op, makeEverfurError("internalError", { retryable: false }));
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
default:
|
|
1178
|
-
return this.fail(op, makeEverfurError("internalError", { retryable: false }));
|
|
1179
|
-
}
|
|
1180
|
-
} finally {
|
|
1181
|
-
op.settle();
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
/** Clear the gate/result back to idle for a retake. */
|
|
1185
|
-
reset() {
|
|
1186
|
-
this.inFlight?.abort();
|
|
1187
|
-
this.inFlight = null;
|
|
1188
|
-
this.patch({ status: "idle", result: null, gate: ACCEPTED, error: null });
|
|
1189
|
-
}
|
|
1190
|
-
// ---- terminal transitions ---------------------------------------------
|
|
1191
|
-
complete(op, body, region, jobId, startedAt) {
|
|
1192
|
-
if (!this.alive(op.epoch)) return abortedResult();
|
|
1193
|
-
const mapped = mapPhotoResult(body, region, jobId);
|
|
1194
|
-
if (mapped.droppedOperatorKeys > 0) {
|
|
1195
|
-
this.telemetry.track("analysis.metadata_leak_guard", { droppedKeyCount: mapped.droppedOperatorKeys });
|
|
1196
|
-
}
|
|
1197
|
-
this.patch({ status: "complete", result: mapped.result, gate: ACCEPTED, error: null });
|
|
1198
|
-
this.telemetry.track("analysis.species_gate", { outcome: "accepted", region });
|
|
1199
|
-
this.telemetry.track("analysis.result", {
|
|
1200
|
-
capability: "photo",
|
|
1201
|
-
region,
|
|
1202
|
-
riskTier: mapped.result.riskTier ?? "",
|
|
1203
|
-
confidenceTier: mapped.result.confidenceTier ?? "",
|
|
1204
|
-
modelVersion: mapped.result.modelVersion ?? "",
|
|
1205
|
-
timeToResultMs: this.timers.now() - startedAt
|
|
1206
|
-
});
|
|
1207
|
-
return ok(mapped.result);
|
|
1208
|
-
}
|
|
1209
|
-
gateDomain(op, region, gate) {
|
|
1210
|
-
if (!this.alive(op.epoch)) return abortedResult();
|
|
1211
|
-
const outcome = gate.kind === "species_unsupported" ? Object.freeze({ kind: "species_unsupported", species: "cat" }) : Object.freeze({ kind: "pet_context_required" });
|
|
1212
|
-
this.patch({ status: "idle", gate: outcome, error: null, result: null });
|
|
1213
|
-
this.telemetry.track("analysis.species_gate", {
|
|
1214
|
-
outcome: gate.kind,
|
|
1215
|
-
region,
|
|
1216
|
-
...gate.kind === "species_unsupported" ? { species: "cat" } : {}
|
|
1217
|
-
});
|
|
1218
|
-
return err(makeEverfurError("unknown", { retryable: false }));
|
|
1219
|
-
}
|
|
1220
|
-
gateRetake(op, region, reasons) {
|
|
1221
|
-
if (!this.alive(op.epoch)) return abortedResult();
|
|
1222
|
-
this.patch({
|
|
1223
|
-
status: "idle",
|
|
1224
|
-
gate: Object.freeze({ kind: "retake", reasons: Object.freeze([...reasons]) }),
|
|
1225
|
-
error: null,
|
|
1226
|
-
result: null
|
|
1227
|
-
});
|
|
1228
|
-
this.telemetry.track("analysis.quality_gate", { usable: false, reasons });
|
|
1229
|
-
const outcome = reasons.includes("wrong_species") ? "species_unsupported" : "retake";
|
|
1230
|
-
this.telemetry.track("analysis.species_gate", { outcome, region });
|
|
1231
|
-
return err(makeEverfurError("unknown", { retryable: false }));
|
|
1232
|
-
}
|
|
1233
|
-
fail(op, error) {
|
|
1234
|
-
if (!this.alive(op.epoch)) return abortedResult();
|
|
1235
|
-
this.patch({ status: "error", error, gate: ACCEPTED });
|
|
1236
|
-
this.telemetry.trackExpectedFailure("analysis.error", {
|
|
1237
|
-
code: error.code,
|
|
1238
|
-
...error.httpStatus !== void 0 ? { httpStatus: error.httpStatus } : {}
|
|
1239
|
-
});
|
|
1240
|
-
return err(error);
|
|
1241
|
-
}
|
|
1242
|
-
};
|
|
1243
|
-
function mapPhotoResult(body, region, jobId) {
|
|
1244
|
-
const projection = projectDetectionMetadata(locateDetectionMetadata(body));
|
|
1245
|
-
const meta = projection.metadata;
|
|
1246
|
-
const interpretation = parseInterpretation(locateInterpretation(body));
|
|
1247
|
-
const result = Object.freeze({
|
|
1248
|
-
jobId,
|
|
1249
|
-
analysisType: region,
|
|
1250
|
-
primaryLabel: asString(meta["primary_label"]) ?? "Uncertain",
|
|
1251
|
-
riskTier: asRiskTier(meta["risk_tier"]),
|
|
1252
|
-
confidence: asUnitNumber(meta["primary_confidence"]),
|
|
1253
|
-
subtypeDisplay: asString(meta["subtype_display"]),
|
|
1254
|
-
confidenceTier: asConfidenceTier(meta["confidence_tier"]),
|
|
1255
|
-
diseaseName: asString(meta["disease_name"]),
|
|
1256
|
-
diseaseFraming: asString(meta["disease_framing"]),
|
|
1257
|
-
copyShort: asString(meta["copy_short"]),
|
|
1258
|
-
interpretation,
|
|
1259
|
-
modelVersion: asString(meta["model_version"]) ?? (isRecord2(body) ? asString(body["model_version"]) : null)
|
|
1260
|
-
});
|
|
1261
|
-
return { result, droppedOperatorKeys: projection.droppedOperatorKeys };
|
|
1262
|
-
}
|
|
1263
|
-
function asRiskTier(value) {
|
|
1264
|
-
return value === "low_risk" || value === "concern" || value === "uncertain" ? value : null;
|
|
1265
|
-
}
|
|
1266
|
-
function asConfidenceTier(value) {
|
|
1267
|
-
return value === "possible" || value === "probable" ? value : null;
|
|
1268
|
-
}
|
|
1269
|
-
function abortedResult() {
|
|
1270
|
-
return err(makeEverfurError("unknown", { retryable: false }));
|
|
1271
|
-
}
|
|
1272
|
-
function defaultIdempotencyFactory() {
|
|
1273
|
-
let counter = 0;
|
|
1274
|
-
return () => `photo-analyze-${Date.now().toString(36)}-${(counter++).toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
// src/client/entitlements/resolve.ts
|
|
1278
|
-
var REQUIRES = Object.freeze({
|
|
1279
|
-
chat: Object.freeze(["chat.message.create"]),
|
|
1280
|
-
records: Object.freeze(["records.upload.create", "records.record.read"]),
|
|
1281
|
-
video: Object.freeze(["video.gait.execute"]),
|
|
1282
|
-
photo: Object.freeze(["photo.checkup.execute"]),
|
|
1283
|
-
animations: Object.freeze(["animations.state.read"])
|
|
1284
|
-
});
|
|
1285
|
-
function requires(name) {
|
|
1286
|
-
return REQUIRES[name];
|
|
1287
|
-
}
|
|
1288
|
-
function decideCapability(name, decisions, opts) {
|
|
1289
|
-
if (opts?.isPending === true) {
|
|
1290
|
-
return freezeDecision({ name, enabled: false, isPending: true, limits: {} });
|
|
1291
|
-
}
|
|
1292
|
-
const keys = requires(name);
|
|
1293
|
-
const byResource = /* @__PURE__ */ new Map();
|
|
1294
|
-
for (const decision of decisions) byResource.set(decision.resource, decision);
|
|
1295
|
-
const limits = mergeLimits(keys, byResource);
|
|
1296
|
-
const missing = keys.filter((key) => byResource.get(key)?.granted !== true);
|
|
1297
|
-
if (missing.length === 0) {
|
|
1298
|
-
return freezeDecision({ name, enabled: true, isPending: false, limits });
|
|
1299
|
-
}
|
|
1300
|
-
return freezeDecision({
|
|
1301
|
-
name,
|
|
1302
|
-
enabled: false,
|
|
1303
|
-
isPending: false,
|
|
1304
|
-
limits,
|
|
1305
|
-
disabled: disabledFor(keys, byResource)
|
|
1306
|
-
});
|
|
1307
|
-
}
|
|
1308
|
-
function mergeLimits(keys, byResource) {
|
|
1309
|
-
const out = {};
|
|
1310
|
-
for (const key of keys) {
|
|
1311
|
-
const decisionLimits = byResource.get(key)?.limits;
|
|
1312
|
-
if (!decisionLimits) continue;
|
|
1313
|
-
for (const [limitKey, limitValue] of Object.entries(decisionLimits)) {
|
|
1314
|
-
const existing = out[limitKey];
|
|
1315
|
-
out[limitKey] = existing === void 0 ? limitValue : Math.min(existing, limitValue);
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1318
|
-
return out;
|
|
1319
|
-
}
|
|
1320
|
-
function disabledFor(keys, byResource) {
|
|
1321
|
-
for (const key of keys) {
|
|
1322
|
-
const explicit = byResource.get(key)?.disabled;
|
|
1323
|
-
if (explicit) {
|
|
1324
|
-
return Object.freeze({
|
|
1325
|
-
reason: explicit.reason,
|
|
1326
|
-
level: explicit.level,
|
|
1327
|
-
recoverable: explicit.recoverable
|
|
1328
|
-
});
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
return Object.freeze({
|
|
1332
|
-
reason: userFacingError("accessDenied"),
|
|
1333
|
-
level: "user_entitlement",
|
|
1334
|
-
recoverable: false
|
|
1335
|
-
});
|
|
1336
|
-
}
|
|
1337
|
-
function freezeDecision(d) {
|
|
1338
|
-
const base = {
|
|
1339
|
-
name: d.name,
|
|
1340
|
-
enabled: d.enabled,
|
|
1341
|
-
isPending: d.isPending,
|
|
1342
|
-
limits: Object.freeze({ ...d.limits })
|
|
1343
|
-
};
|
|
1344
|
-
if (d.disabled !== void 0) base.disabled = d.disabled;
|
|
1345
|
-
return Object.freeze(base);
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
|
-
// src/client/theme/tokens.ts
|
|
1349
|
-
function deepFreeze(value) {
|
|
1350
|
-
if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
|
|
1351
|
-
Object.freeze(value);
|
|
1352
|
-
for (const key of Object.keys(value)) {
|
|
1353
|
-
deepFreeze(value[key]);
|
|
1354
|
-
}
|
|
1355
|
-
}
|
|
1356
|
-
return value;
|
|
1357
|
-
}
|
|
1358
|
-
var SPACE = { xs: 4, sm: 8, md: 12, lg: 16, xl: 24, xxl: 32 };
|
|
1359
|
-
var RADIUS = { tile: 5, xs: 6, sm: 10, md: 14, lg: 20, xl: 28, pill: 500 };
|
|
1360
|
-
var TYPE_SIZE = {
|
|
1361
|
-
nano: 8,
|
|
1362
|
-
small: 9,
|
|
1363
|
-
micro: 10,
|
|
1364
|
-
tiny: 11,
|
|
1365
|
-
caption: 12,
|
|
1366
|
-
bodyTight: 13,
|
|
1367
|
-
body: 14,
|
|
1368
|
-
subtitle: 16,
|
|
1369
|
-
heading: 18,
|
|
1370
|
-
verdict: 24
|
|
1371
|
-
};
|
|
1372
|
-
var LINE_HEIGHT = { tight: 1.15, snug: 1.3, normal: 1.4, relaxed: 1.55 };
|
|
1373
|
-
var WEIGHT = { regular: "400", medium: "500", bold: "700" };
|
|
1374
|
-
var LETTER_SPACING = {
|
|
1375
|
-
eyebrow: 1,
|
|
1376
|
-
monoWide: 0.9,
|
|
1377
|
-
headerBrand: -0.42,
|
|
1378
|
-
tight: -0.35
|
|
1379
|
-
};
|
|
1380
|
-
function baseType() {
|
|
1381
|
-
return {
|
|
1382
|
-
family: "System",
|
|
1383
|
-
mono: "monospace",
|
|
1384
|
-
fontUrl: null,
|
|
1385
|
-
size: TYPE_SIZE,
|
|
1386
|
-
lineHeight: LINE_HEIGHT,
|
|
1387
|
-
weight: WEIGHT,
|
|
1388
|
-
letterSpacing: LETTER_SPACING
|
|
1389
|
-
};
|
|
1390
|
-
}
|
|
1391
|
-
var MOTION = {
|
|
1392
|
-
duration: { instant: 80, fast: 140, normal: 220, slow: 320 },
|
|
1393
|
-
spring: { gentle: { damping: 18, stiffness: 200 } },
|
|
1394
|
-
easing: { standard: "cubic-bezier(0.4, 0, 0.2, 1)" },
|
|
1395
|
-
reducedMotion: false
|
|
1396
|
-
};
|
|
1397
|
-
var LIGHT_COLOR = {
|
|
1398
|
-
brand: "#10462E",
|
|
1399
|
-
brandSoft: "rgba(16,70,46,0.10)",
|
|
1400
|
-
brandSofter: "rgba(16,70,46,0.15)",
|
|
1401
|
-
brandBorder: "rgba(16,70,46,0.18)",
|
|
1402
|
-
borderHairlineGreen: "rgba(16,70,46,0.25)",
|
|
1403
|
-
surface: "#FFFFFF",
|
|
1404
|
-
surfaceCream: "#FDFBF8",
|
|
1405
|
-
surfaceSubtle: "#EFE9E3",
|
|
1406
|
-
background: "#FFFFFF",
|
|
1407
|
-
borderRingCream: "#D8D2C8",
|
|
1408
|
-
text: {
|
|
1409
|
-
primary: "#111111",
|
|
1410
|
-
secondary: "#0E462E",
|
|
1411
|
-
muted: "#6E6660",
|
|
1412
|
-
placeholder: "rgba(0,0,0,0.30)",
|
|
1413
|
-
onBrand: "#FFFFFF",
|
|
1414
|
-
metaCool: "#3D3D3D"
|
|
1415
|
-
},
|
|
1416
|
-
primary: "#10462E",
|
|
1417
|
-
onPrimary: "#FFFFFF",
|
|
1418
|
-
accent: "#10462E",
|
|
1419
|
-
onAccent: "#FFFFFF",
|
|
1420
|
-
accentEdge: "#07C168",
|
|
1421
|
-
success: "#07C168",
|
|
1422
|
-
danger: "#B91C1C",
|
|
1423
|
-
errorRed: "#FF1B00",
|
|
1424
|
-
warningStrong: "#A45610",
|
|
1425
|
-
cautionAccent: "#D9821D",
|
|
1426
|
-
riskConcern: "#EFA700",
|
|
1427
|
-
badgeBgPositive: "rgba(7,193,104,0.12)",
|
|
1428
|
-
badgeBgCaution: "rgba(217,130,29,0.14)",
|
|
1429
|
-
badgeBgCritical: "rgba(185,28,28,0.12)",
|
|
1430
|
-
badgeBgNeutral: "rgba(110,102,96,0.12)",
|
|
1431
|
-
bubbleUser: "#10462E",
|
|
1432
|
-
bubbleAssistant: "#FFFFFF",
|
|
1433
|
-
skeleton: "#EFE9E3",
|
|
1434
|
-
scanButtonPink: "#FFC3BC",
|
|
1435
|
-
checkupCardLowRiskBg: "rgba(7,193,104,0.10)",
|
|
1436
|
-
checkupCardConcernBg: "rgba(239,167,0,0.10)"
|
|
1437
|
-
};
|
|
1438
|
-
var EVERFUR_LIGHT = deepFreeze({
|
|
1439
|
-
brightness: "light",
|
|
1440
|
-
meta: { displayName: "Everfur", logoUrl: null, placeholder: null, hidePoweredBy: false },
|
|
1441
|
-
color: LIGHT_COLOR,
|
|
1442
|
-
space: SPACE,
|
|
1443
|
-
radius: RADIUS,
|
|
1444
|
-
type: baseType(),
|
|
1445
|
-
motion: MOTION
|
|
1446
|
-
});
|
|
1447
|
-
var DARK_COLOR = {
|
|
1448
|
-
brand: "#6BD39A",
|
|
1449
|
-
brandSoft: "rgba(107,211,154,0.12)",
|
|
1450
|
-
brandSofter: "rgba(107,211,154,0.18)",
|
|
1451
|
-
brandBorder: "rgba(107,211,154,0.22)",
|
|
1452
|
-
borderHairlineGreen: "rgba(107,211,154,0.28)",
|
|
1453
|
-
surface: "#1C1F23",
|
|
1454
|
-
surfaceCream: "#23272C",
|
|
1455
|
-
surfaceSubtle: "#23272C",
|
|
1456
|
-
background: "#15171A",
|
|
1457
|
-
borderRingCream: "#3A3F45",
|
|
1458
|
-
text: {
|
|
1459
|
-
primary: "#F5F3F0",
|
|
1460
|
-
secondary: "#A9E4C4",
|
|
1461
|
-
muted: "#9A938C",
|
|
1462
|
-
placeholder: "rgba(255,255,255,0.35)",
|
|
1463
|
-
onBrand: "#0E1F17",
|
|
1464
|
-
metaCool: "#C9C9C9"
|
|
1465
|
-
},
|
|
1466
|
-
primary: "#6BD39A",
|
|
1467
|
-
onPrimary: "#0E1F17",
|
|
1468
|
-
accent: "#6BD39A",
|
|
1469
|
-
onAccent: "#0E1F17",
|
|
1470
|
-
accentEdge: "#34D888",
|
|
1471
|
-
success: "#07C168",
|
|
1472
|
-
danger: "#B91C1C",
|
|
1473
|
-
errorRed: "#FF1B00",
|
|
1474
|
-
warningStrong: "#A45610",
|
|
1475
|
-
cautionAccent: "#D9821D",
|
|
1476
|
-
riskConcern: "#EFA700",
|
|
1477
|
-
badgeBgPositive: "rgba(7,193,104,0.18)",
|
|
1478
|
-
badgeBgCaution: "rgba(217,130,29,0.20)",
|
|
1479
|
-
badgeBgCritical: "rgba(185,28,28,0.22)",
|
|
1480
|
-
badgeBgNeutral: "rgba(255,255,255,0.10)",
|
|
1481
|
-
bubbleUser: "#6BD39A",
|
|
1482
|
-
bubbleAssistant: "#1C1F23",
|
|
1483
|
-
skeleton: "#23272C",
|
|
1484
|
-
scanButtonPink: "#FFC3BC",
|
|
1485
|
-
checkupCardLowRiskBg: "rgba(7,193,104,0.16)",
|
|
1486
|
-
checkupCardConcernBg: "rgba(239,167,0,0.16)"
|
|
1487
|
-
};
|
|
1488
|
-
deepFreeze({
|
|
1489
|
-
brightness: "dark",
|
|
1490
|
-
meta: { displayName: "Everfur", logoUrl: null, placeholder: null, hidePoweredBy: false },
|
|
1491
|
-
color: DARK_COLOR,
|
|
1492
|
-
space: SPACE,
|
|
1493
|
-
radius: RADIUS,
|
|
1494
|
-
type: baseType(),
|
|
1495
|
-
motion: MOTION
|
|
1496
|
-
});
|
|
1497
|
-
var EVERFUR_THEME_CONTEXT_SYMBOL = /* @__PURE__ */ Symbol.for("@everfur/sdk#EverfurThemeContext@v1");
|
|
1498
|
-
var globalThemeHost = globalThis;
|
|
1499
|
-
var EverfurThemeContext = globalThemeHost[EVERFUR_THEME_CONTEXT_SYMBOL] ?? (globalThemeHost[EVERFUR_THEME_CONTEXT_SYMBOL] = createContext(EVERFUR_LIGHT));
|
|
1500
|
-
EverfurThemeContext.displayName ??= "EverfurThemeContext";
|
|
1501
|
-
function useEverfurTheme() {
|
|
1502
|
-
return useContext(EverfurThemeContext);
|
|
1503
|
-
}
|
|
1504
|
-
|
|
1505
|
-
// src/rn/facade.ts
|
|
1506
|
-
var EVERFUR_CONTEXT_SYMBOL = /* @__PURE__ */ Symbol.for("@everfur/sdk#EverfurContext@v1");
|
|
1507
|
-
var globalContextHost = globalThis;
|
|
1508
|
-
var EverfurContext = globalContextHost[EVERFUR_CONTEXT_SYMBOL] ?? (globalContextHost[EVERFUR_CONTEXT_SYMBOL] = createContext(null));
|
|
1509
|
-
EverfurContext.displayName ??= "EverfurContext";
|
|
1510
|
-
function useEverfurContext() {
|
|
1511
|
-
const ctx = useContext(EverfurContext);
|
|
1512
|
-
if (ctx === null) {
|
|
1513
|
-
throw new EverfurConfigError(
|
|
1514
|
-
"not-initialized",
|
|
1515
|
-
"Everfur hooks must be called inside <EverfurProvider>. Wrap your tree once, high."
|
|
1516
|
-
);
|
|
1517
|
-
}
|
|
1518
|
-
return ctx;
|
|
1519
|
-
}
|
|
1520
|
-
|
|
1521
|
-
// src/rn/photo/usePhoto.ts
|
|
1522
|
-
var BOOTSTRAP_SNAPSHOT = Object.freeze({
|
|
1523
|
-
status: "idle",
|
|
1524
|
-
region: "skin",
|
|
1525
|
-
result: null,
|
|
1526
|
-
gate: Object.freeze({ kind: "accepted" }),
|
|
1527
|
-
error: null,
|
|
1528
|
-
isCaptureAvailable: false
|
|
1529
|
-
});
|
|
1530
|
-
var NOOP_UNSUBSCRIBE = () => {
|
|
1531
|
-
};
|
|
1532
|
-
function usePhotoCheckup(opts) {
|
|
1533
|
-
const { runtime, userRef, scopeEpoch } = useEverfurContext();
|
|
1534
|
-
const petRef = opts.petRef;
|
|
1535
|
-
const [controller, setController] = useState(null);
|
|
1536
|
-
useEffect(() => {
|
|
1537
|
-
const { services } = runtime.registry;
|
|
1538
|
-
const acquired = runtime.registry.acquire(
|
|
1539
|
-
"photo",
|
|
1540
|
-
`${userRef ?? ""}|${petRef}`,
|
|
1541
|
-
() => createPhotoController({
|
|
1542
|
-
...services.errorPolicy ? { errorPolicy: services.errorPolicy } : {},
|
|
1543
|
-
auth: services.authFor(userRef),
|
|
1544
|
-
camera: services.camera,
|
|
1545
|
-
upload: services.upload,
|
|
1546
|
-
telemetry: services.telemetry
|
|
1547
|
-
}),
|
|
1548
|
-
(controller2) => controller2.dispose()
|
|
1549
|
-
);
|
|
1550
|
-
setController(acquired);
|
|
1551
|
-
return () => {
|
|
1552
|
-
setController((current) => current === acquired ? null : current);
|
|
1553
|
-
runtime.registry.release(acquired);
|
|
1554
|
-
};
|
|
1555
|
-
}, [runtime, userRef, petRef, scopeEpoch]);
|
|
1556
|
-
const subscribe = useCallback(
|
|
1557
|
-
(onChange) => controller ? controller.subscribe(onChange) : NOOP_UNSUBSCRIBE,
|
|
1558
|
-
[controller]
|
|
1559
|
-
);
|
|
1560
|
-
const getSnapshot = useCallback(
|
|
1561
|
-
() => controller ? controller.getSnapshot() : BOOTSTRAP_SNAPSHOT,
|
|
1562
|
-
[controller]
|
|
1563
|
-
);
|
|
1564
|
-
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
1565
|
-
const analyze = useCallback(
|
|
1566
|
-
(source, region) => controller ? controller.analyze(source, region, { petRef }) : Promise.resolve(notReadyError()),
|
|
1567
|
-
[controller, petRef]
|
|
1568
|
-
);
|
|
1569
|
-
const capture = useCallback(
|
|
1570
|
-
(region) => controller ? controller.capture(region, { petRef }) : Promise.resolve(notReadyError()),
|
|
1571
|
-
[controller, petRef]
|
|
1572
|
-
);
|
|
1573
|
-
const reset = useCallback(() => {
|
|
1574
|
-
controller?.reset();
|
|
1575
|
-
}, [controller]);
|
|
1576
|
-
return useMemo(
|
|
1577
|
-
() => ({
|
|
1578
|
-
status: snapshot.status,
|
|
1579
|
-
region: snapshot.region,
|
|
1580
|
-
result: snapshot.result,
|
|
1581
|
-
gate: snapshot.gate,
|
|
1582
|
-
error: snapshot.error,
|
|
1583
|
-
isCaptureAvailable: snapshot.isCaptureAvailable,
|
|
1584
|
-
isBootstrapping: controller === null,
|
|
1585
|
-
analyze,
|
|
1586
|
-
capture,
|
|
1587
|
-
reset
|
|
1588
|
-
}),
|
|
1589
|
-
[snapshot, controller, analyze, capture, reset]
|
|
1590
|
-
);
|
|
1591
|
-
}
|
|
1592
|
-
function notReadyError() {
|
|
1593
|
-
return err(
|
|
1594
|
-
makeEverfurError("internalError", { retryable: true })
|
|
1595
|
-
);
|
|
1596
|
-
}
|
|
1597
|
-
function useCapability(name) {
|
|
1598
|
-
const { runtime } = useEverfurContext();
|
|
1599
|
-
const store = runtime.entitlements;
|
|
1600
|
-
const snapshot = useSyncExternalStore(
|
|
1601
|
-
store.subscribe,
|
|
1602
|
-
store.getSnapshot,
|
|
1603
|
-
store.getSnapshot
|
|
1604
|
-
);
|
|
1605
|
-
return useMemo(
|
|
1606
|
-
() => decideCapability(name, snapshot.decisions, { isPending: snapshot.isPending }),
|
|
1607
|
-
[name, snapshot]
|
|
1608
|
-
);
|
|
1609
|
-
}
|
|
1610
|
-
var MIN_OFFSTATE_HEIGHT = 48;
|
|
1611
|
-
function selectView(decision) {
|
|
1612
|
-
if (decision.isPending) return "pending";
|
|
1613
|
-
return decision.enabled ? "enabled" : "disabled";
|
|
1614
|
-
}
|
|
1615
|
-
function CapabilityGate(props) {
|
|
1616
|
-
const { capability, children, renderDisabled, renderPending, style } = props;
|
|
1617
|
-
const decision = useCapability(capability);
|
|
1618
|
-
const view = useMemo(() => selectView(decision), [decision]);
|
|
1619
|
-
switch (view) {
|
|
1620
|
-
case "enabled":
|
|
1621
|
-
return children;
|
|
1622
|
-
case "pending":
|
|
1623
|
-
return renderPending ? renderPending() : /* @__PURE__ */ jsx(PendingSkeleton, { capability, style });
|
|
1624
|
-
case "disabled":
|
|
1625
|
-
return renderDisabled ? renderDisabled(decision) : /* @__PURE__ */ jsx(OffState, { decision, style });
|
|
1626
|
-
default:
|
|
1627
|
-
return assertNever(view);
|
|
1628
|
-
}
|
|
1629
|
-
}
|
|
1630
|
-
function PendingSkeleton({
|
|
1631
|
-
capability,
|
|
1632
|
-
style
|
|
1633
|
-
}) {
|
|
1634
|
-
const theme = useEverfurTheme();
|
|
1635
|
-
const styles = useMemo(() => makeStyles(theme), [theme]);
|
|
1636
|
-
return /* @__PURE__ */ jsx(
|
|
1637
|
-
View,
|
|
1638
|
-
{
|
|
1639
|
-
style: [styles.offstate, style],
|
|
1640
|
-
accessible: true,
|
|
1641
|
-
accessibilityState: { busy: true },
|
|
1642
|
-
accessibilityLabel: `Loading ${capability}`,
|
|
1643
|
-
children: /* @__PURE__ */ jsx(View, { style: styles.skeletonBar })
|
|
1644
|
-
}
|
|
1645
|
-
);
|
|
1646
|
-
}
|
|
1647
|
-
function OffState({
|
|
1648
|
-
decision,
|
|
1649
|
-
style
|
|
1650
|
-
}) {
|
|
1651
|
-
const theme = useEverfurTheme();
|
|
1652
|
-
const styles = useMemo(() => makeStyles(theme), [theme]);
|
|
1653
|
-
const message = decision.disabled?.reason ?? "";
|
|
1654
|
-
return /* @__PURE__ */ jsx(
|
|
1655
|
-
View,
|
|
1656
|
-
{
|
|
1657
|
-
style: [styles.offstate, style],
|
|
1658
|
-
accessible: true,
|
|
1659
|
-
accessibilityRole: "text",
|
|
1660
|
-
accessibilityLabel: message,
|
|
1661
|
-
children: message.length > 0 ? /* @__PURE__ */ jsx(Text, { style: styles.offstateText, children: message }) : null
|
|
1662
|
-
}
|
|
1663
|
-
);
|
|
1664
|
-
}
|
|
1665
|
-
function makeStyles(theme) {
|
|
1666
|
-
const { color, space, radius, type } = theme;
|
|
1667
|
-
return StyleSheet.create({
|
|
1668
|
-
// A deliberate cream card with a soft brand hairline, never a bare grey slab.
|
|
1669
|
-
offstate: {
|
|
1670
|
-
minHeight: MIN_OFFSTATE_HEIGHT,
|
|
1671
|
-
justifyContent: "center",
|
|
1672
|
-
paddingHorizontal: space.lg,
|
|
1673
|
-
paddingVertical: space.md,
|
|
1674
|
-
backgroundColor: color.surfaceCream,
|
|
1675
|
-
borderRadius: radius.md,
|
|
1676
|
-
borderWidth: StyleSheet.hairlineWidth,
|
|
1677
|
-
borderColor: color.brandBorder
|
|
1678
|
-
},
|
|
1679
|
-
offstateText: {
|
|
1680
|
-
fontFamily: type.family,
|
|
1681
|
-
fontSize: type.size.body,
|
|
1682
|
-
lineHeight: Math.round(type.size.body * type.lineHeight.normal),
|
|
1683
|
-
color: color.text.muted
|
|
1684
|
-
},
|
|
1685
|
-
// A soft green shimmer bar (brand wash) that stays legible on the cream card in both light and dark,
|
|
1686
|
-
// where the neutral `skeleton` token would collide with the dark cream surface.
|
|
1687
|
-
skeletonBar: {
|
|
1688
|
-
height: space.md,
|
|
1689
|
-
borderRadius: radius.xs,
|
|
1690
|
-
backgroundColor: color.brandSofter
|
|
1691
|
-
}
|
|
1692
|
-
});
|
|
1693
|
-
}
|
|
1694
|
-
var CLINICAL_DISCLAIMER = Object.freeze({
|
|
1695
|
-
title: "For educational use only",
|
|
1696
|
-
body: "Everfur is for educational purposes only. It helps you track visible changes through AI checkups. It does not diagnose, treat, prescribe, or replace veterinary care. For urgent symptoms or emergencies, contact a veterinarian immediately."
|
|
1697
|
-
});
|
|
1698
|
-
function ClinicalDisclaimer(props) {
|
|
1699
|
-
const theme = useEverfurTheme();
|
|
1700
|
-
const styles = useMemo(() => makeStyles2(theme), [theme]);
|
|
1701
|
-
return /* @__PURE__ */ jsxs(
|
|
1702
|
-
View,
|
|
1703
|
-
{
|
|
1704
|
-
style: [styles.container, props.style],
|
|
1705
|
-
accessible: true,
|
|
1706
|
-
accessibilityRole: "text",
|
|
1707
|
-
accessibilityLabel: `${CLINICAL_DISCLAIMER.title}. ${CLINICAL_DISCLAIMER.body}`,
|
|
1708
|
-
children: [
|
|
1709
|
-
/* @__PURE__ */ jsx(Text, { style: styles.title, children: CLINICAL_DISCLAIMER.title }),
|
|
1710
|
-
/* @__PURE__ */ jsx(Text, { style: styles.body, children: CLINICAL_DISCLAIMER.body })
|
|
1711
|
-
]
|
|
1712
|
-
}
|
|
1713
|
-
);
|
|
1714
|
-
}
|
|
1715
|
-
function makeStyles2(theme) {
|
|
1716
|
-
const { color, space, type } = theme;
|
|
1717
|
-
return StyleSheet.create({
|
|
1718
|
-
container: {
|
|
1719
|
-
paddingHorizontal: space.lg,
|
|
1720
|
-
paddingVertical: space.md,
|
|
1721
|
-
borderTopWidth: StyleSheet.hairlineWidth,
|
|
1722
|
-
borderTopColor: color.borderHairlineGreen,
|
|
1723
|
-
backgroundColor: color.surfaceCream,
|
|
1724
|
-
gap: space.xs
|
|
1725
|
-
},
|
|
1726
|
-
title: {
|
|
1727
|
-
fontFamily: type.family,
|
|
1728
|
-
fontSize: type.size.caption,
|
|
1729
|
-
lineHeight: Math.round(type.size.caption * type.lineHeight.snug),
|
|
1730
|
-
color: color.text.secondary,
|
|
1731
|
-
fontWeight: type.weight.bold,
|
|
1732
|
-
letterSpacing: type.letterSpacing.eyebrow
|
|
1733
|
-
},
|
|
1734
|
-
body: {
|
|
1735
|
-
fontFamily: type.family,
|
|
1736
|
-
fontSize: type.size.caption,
|
|
1737
|
-
lineHeight: Math.round(type.size.caption * type.lineHeight.snug),
|
|
1738
|
-
color: color.text.muted,
|
|
1739
|
-
fontWeight: type.weight.regular
|
|
1740
|
-
}
|
|
1741
|
-
});
|
|
1742
|
-
}
|
|
1743
|
-
var ROTATION_PERIOD_MS = 1400;
|
|
1744
|
-
var REVEAL_DELAY_MS = 300;
|
|
1745
|
-
function AnalyzingIndicator() {
|
|
1746
|
-
const theme = useEverfurTheme();
|
|
1747
|
-
const styles = useMemo(() => makeStyles3(theme), [theme]);
|
|
1748
|
-
const reduced = theme.motion.reducedMotion;
|
|
1749
|
-
const [visible, setVisible] = useState(false);
|
|
1750
|
-
useEffect(() => {
|
|
1751
|
-
const id = setTimeout(() => setVisible(true), REVEAL_DELAY_MS);
|
|
1752
|
-
return () => clearTimeout(id);
|
|
1753
|
-
}, []);
|
|
1754
|
-
const spin = useRef(new Animated.Value(0)).current;
|
|
1755
|
-
useEffect(() => {
|
|
1756
|
-
if (reduced || !visible) return;
|
|
1757
|
-
const loop = Animated.loop(
|
|
1758
|
-
Animated.timing(spin, {
|
|
1759
|
-
toValue: 1,
|
|
1760
|
-
duration: ROTATION_PERIOD_MS,
|
|
1761
|
-
easing: Easing.linear,
|
|
1762
|
-
useNativeDriver: true
|
|
1763
|
-
})
|
|
1764
|
-
);
|
|
1765
|
-
loop.start();
|
|
1766
|
-
return () => loop.stop();
|
|
1767
|
-
}, [reduced, visible, spin]);
|
|
1768
|
-
const arcMotionStyle = reduced ? null : { transform: [{ rotate: spin.interpolate({ inputRange: [0, 1], outputRange: ["0deg", "360deg"] }) }] };
|
|
1769
|
-
return /* @__PURE__ */ jsx(
|
|
1770
|
-
View,
|
|
1771
|
-
{
|
|
1772
|
-
style: styles.wrap,
|
|
1773
|
-
accessible: true,
|
|
1774
|
-
accessibilityState: { busy: true },
|
|
1775
|
-
accessibilityLabel: "Analyzing photo",
|
|
1776
|
-
children: visible ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1777
|
-
/* @__PURE__ */ jsx(Animated.View, { style: [styles.ring, arcMotionStyle] }),
|
|
1778
|
-
/* @__PURE__ */ jsx(Text, { style: styles.statusLine, children: "Analyzing" })
|
|
1779
|
-
] }) : null
|
|
1780
|
-
}
|
|
1781
|
-
);
|
|
1782
|
-
}
|
|
1783
|
-
function makeStyles3(theme) {
|
|
1784
|
-
const { color, space, radius, type } = theme;
|
|
1785
|
-
const medium = type.weight.medium;
|
|
1786
|
-
const ringSize = space.xxl * 2;
|
|
1787
|
-
const ringThickness = space.xs;
|
|
1788
|
-
return StyleSheet.create({
|
|
1789
|
-
wrap: {
|
|
1790
|
-
flexGrow: 1,
|
|
1791
|
-
alignItems: "center",
|
|
1792
|
-
justifyContent: "center",
|
|
1793
|
-
padding: space.xl,
|
|
1794
|
-
gap: space.md
|
|
1795
|
-
},
|
|
1796
|
-
// One rotating ring: success on the top/right, cream on the remainder. Only the success sweep is perceptible
|
|
1797
|
-
// (the cream half is uniform), so this reads as a cream ring with a rotating success arc - the mobile look.
|
|
1798
|
-
ring: {
|
|
1799
|
-
width: ringSize,
|
|
1800
|
-
height: ringSize,
|
|
1801
|
-
borderRadius: radius.pill,
|
|
1802
|
-
borderWidth: ringThickness,
|
|
1803
|
-
borderTopColor: color.success,
|
|
1804
|
-
borderRightColor: color.success,
|
|
1805
|
-
borderBottomColor: color.borderRingCream,
|
|
1806
|
-
borderLeftColor: color.borderRingCream
|
|
1807
|
-
},
|
|
1808
|
-
// Neutral SDK-owned structural status word (not clinical / product copy), on the brand-green secondary ramp
|
|
1809
|
-
// so it sits in the same family as the verdict hero. Legible in both themes via the token.
|
|
1810
|
-
statusLine: {
|
|
1811
|
-
color: color.text.secondary,
|
|
1812
|
-
fontFamily: type.family,
|
|
1813
|
-
fontSize: type.size.subtitle,
|
|
1814
|
-
lineHeight: type.size.subtitle * type.lineHeight.snug,
|
|
1815
|
-
fontWeight: medium,
|
|
1816
|
-
letterSpacing: type.letterSpacing.tight,
|
|
1817
|
-
textAlign: "center"
|
|
1818
|
-
}
|
|
1819
|
-
});
|
|
1820
|
-
}
|
|
1821
|
-
var PULSE_PERIOD_MS = 1100;
|
|
1822
|
-
var PULSE_MAX_SCALE = 1.6;
|
|
1823
|
-
var PULSE_START_OPACITY = 0.35;
|
|
1824
|
-
function toneSpec(theme, tier) {
|
|
1825
|
-
const { color } = theme;
|
|
1826
|
-
switch (tier) {
|
|
1827
|
-
case "low_risk":
|
|
1828
|
-
return { accent: color.success, wash: color.badgeBgPositive, label: "Low risk", pulse: false };
|
|
1829
|
-
case "concern":
|
|
1830
|
-
return { accent: color.cautionAccent, wash: color.badgeBgCaution, label: "Concern", pulse: true };
|
|
1831
|
-
case "uncertain":
|
|
1832
|
-
return { accent: color.text.muted, wash: color.badgeBgNeutral, label: "Uncertain", pulse: false };
|
|
1833
|
-
}
|
|
1834
|
-
}
|
|
1835
|
-
function SeverityBadge({ riskTier }) {
|
|
1836
|
-
const theme = useEverfurTheme();
|
|
1837
|
-
const styles = useMemo(() => makeStyles4(theme), [theme]);
|
|
1838
|
-
const pulse = useRef(new Animated.Value(0)).current;
|
|
1839
|
-
const spec = riskTier === null ? null : toneSpec(theme, riskTier);
|
|
1840
|
-
const shouldPulse = spec !== null && spec.pulse && !theme.motion.reducedMotion;
|
|
1841
|
-
useEffect(() => {
|
|
1842
|
-
if (!shouldPulse) return;
|
|
1843
|
-
const loop = Animated.loop(
|
|
1844
|
-
Animated.timing(pulse, {
|
|
1845
|
-
toValue: 1,
|
|
1846
|
-
duration: PULSE_PERIOD_MS,
|
|
1847
|
-
easing: Easing.out(Easing.quad),
|
|
1848
|
-
useNativeDriver: true
|
|
1849
|
-
})
|
|
1850
|
-
);
|
|
1851
|
-
loop.start();
|
|
1852
|
-
return () => loop.stop();
|
|
1853
|
-
}, [shouldPulse, pulse]);
|
|
1854
|
-
if (spec === null) return null;
|
|
1855
|
-
return /* @__PURE__ */ jsxs(
|
|
1856
|
-
View,
|
|
1857
|
-
{
|
|
1858
|
-
style: [styles.pill, { backgroundColor: spec.wash }],
|
|
1859
|
-
accessible: true,
|
|
1860
|
-
accessibilityRole: "text",
|
|
1861
|
-
accessibilityLabel: spec.label,
|
|
1862
|
-
children: [
|
|
1863
|
-
/* @__PURE__ */ jsxs(View, { style: styles.dotWrap, children: [
|
|
1864
|
-
shouldPulse ? /* @__PURE__ */ jsx(
|
|
1865
|
-
Animated.View,
|
|
1866
|
-
{
|
|
1867
|
-
pointerEvents: "none",
|
|
1868
|
-
style: [
|
|
1869
|
-
styles.dotRing,
|
|
1870
|
-
{
|
|
1871
|
-
backgroundColor: spec.accent,
|
|
1872
|
-
opacity: pulse.interpolate({ inputRange: [0, 1], outputRange: [PULSE_START_OPACITY, 0] }),
|
|
1873
|
-
transform: [{ scale: pulse.interpolate({ inputRange: [0, 1], outputRange: [1, PULSE_MAX_SCALE] }) }]
|
|
1874
|
-
}
|
|
1875
|
-
]
|
|
1876
|
-
}
|
|
1877
|
-
) : null,
|
|
1878
|
-
/* @__PURE__ */ jsx(View, { style: [styles.dot, { backgroundColor: spec.accent }] })
|
|
1879
|
-
] }),
|
|
1880
|
-
/* @__PURE__ */ jsx(Text, { style: [styles.label, { color: spec.accent }], children: spec.label })
|
|
1881
|
-
]
|
|
1882
|
-
}
|
|
1883
|
-
);
|
|
1884
|
-
}
|
|
1885
|
-
function makeStyles4(theme) {
|
|
1886
|
-
const { space, radius, type } = theme;
|
|
1887
|
-
const bold = type.weight.bold;
|
|
1888
|
-
const dotSize = space.sm;
|
|
1889
|
-
return StyleSheet.create({
|
|
1890
|
-
pill: {
|
|
1891
|
-
alignSelf: "flex-start",
|
|
1892
|
-
flexDirection: "row",
|
|
1893
|
-
alignItems: "center",
|
|
1894
|
-
gap: space.sm,
|
|
1895
|
-
paddingHorizontal: space.md,
|
|
1896
|
-
paddingVertical: space.xs,
|
|
1897
|
-
borderRadius: radius.pill
|
|
1898
|
-
},
|
|
1899
|
-
dotWrap: {
|
|
1900
|
-
width: dotSize,
|
|
1901
|
-
height: dotSize,
|
|
1902
|
-
alignItems: "center",
|
|
1903
|
-
justifyContent: "center"
|
|
1904
|
-
},
|
|
1905
|
-
dot: {
|
|
1906
|
-
width: dotSize,
|
|
1907
|
-
height: dotSize,
|
|
1908
|
-
borderRadius: radius.pill
|
|
1909
|
-
},
|
|
1910
|
-
// The pulsing attention ring behind the dot (concern only). backgroundColor + animated opacity/scale set inline.
|
|
1911
|
-
dotRing: {
|
|
1912
|
-
position: "absolute",
|
|
1913
|
-
width: dotSize,
|
|
1914
|
-
height: dotSize,
|
|
1915
|
-
borderRadius: radius.pill
|
|
1916
|
-
},
|
|
1917
|
-
label: {
|
|
1918
|
-
fontFamily: type.family,
|
|
1919
|
-
fontSize: type.size.caption,
|
|
1920
|
-
lineHeight: type.size.caption * type.lineHeight.snug,
|
|
1921
|
-
fontWeight: bold,
|
|
1922
|
-
letterSpacing: type.letterSpacing.eyebrow
|
|
1923
|
-
}
|
|
1924
|
-
});
|
|
1925
|
-
}
|
|
1926
|
-
var MIN_SURFACE_HEIGHT = 120;
|
|
1927
|
-
var MIN_TOUCH_TARGET = 44;
|
|
1928
|
-
function PhotoCheckup(props) {
|
|
1929
|
-
const { petRef, region, style } = props;
|
|
1930
|
-
return /* @__PURE__ */ jsx(CapabilityGate, { capability: "photo", style, children: /* @__PURE__ */ jsx(PhotoBody, { petRef, region, style }) });
|
|
1931
|
-
}
|
|
1932
|
-
function selectView2(state) {
|
|
1933
|
-
if (state.isBootstrapping) return "loading";
|
|
1934
|
-
if (state.status === "capturing" || state.status === "analyzing") return "loading";
|
|
1935
|
-
if (state.status === "error" && state.error) return "error";
|
|
1936
|
-
if (state.status === "complete" && state.result) return "populated";
|
|
1937
|
-
if (state.gate.kind !== "accepted") return "gate";
|
|
1938
|
-
if (!state.isCaptureAvailable) return "native";
|
|
1939
|
-
return "empty";
|
|
1940
|
-
}
|
|
1941
|
-
function PhotoBody({
|
|
1942
|
-
petRef,
|
|
1943
|
-
region,
|
|
1944
|
-
style
|
|
1945
|
-
}) {
|
|
1946
|
-
const photo = usePhotoCheckup({ petRef });
|
|
1947
|
-
const activeRegion = region ?? "skin";
|
|
1948
|
-
const view = selectView2(photo);
|
|
1949
|
-
const capture = useMemo(() => () => void photo.capture(activeRegion), [photo, activeRegion]);
|
|
1950
|
-
const theme = useEverfurTheme();
|
|
1951
|
-
const styles = useMemo(() => makeStyles5(theme), [theme]);
|
|
1952
|
-
return /* @__PURE__ */ jsxs(View, { style: [styles.container, style], children: [
|
|
1953
|
-
/* @__PURE__ */ jsx(View, { style: styles.body, children: renderView(view, photo, capture) }),
|
|
1954
|
-
/* @__PURE__ */ jsx(ClinicalDisclaimer, {})
|
|
1955
|
-
] });
|
|
1956
|
-
}
|
|
1957
|
-
function renderView(view, photo, capture) {
|
|
1958
|
-
switch (view) {
|
|
1959
|
-
case "loading":
|
|
1960
|
-
return /* @__PURE__ */ jsx(AnalyzingIndicator, {});
|
|
1961
|
-
case "native":
|
|
1962
|
-
return /* @__PURE__ */ jsx(NativeUnavailableState, {});
|
|
1963
|
-
case "empty":
|
|
1964
|
-
return /* @__PURE__ */ jsx(EmptyState, { onCapture: capture });
|
|
1965
|
-
case "error":
|
|
1966
|
-
return /* @__PURE__ */ jsx(ErrorState, { error: photo.error, onRetry: capture });
|
|
1967
|
-
case "gate":
|
|
1968
|
-
return /* @__PURE__ */ jsx(GateState, { gate: photo.gate, onRetry: photo.reset });
|
|
1969
|
-
case "populated":
|
|
1970
|
-
return /* @__PURE__ */ jsx(ResultState, { result: photo.result });
|
|
1971
|
-
default:
|
|
1972
|
-
return assertNever(view);
|
|
1973
|
-
}
|
|
1974
|
-
}
|
|
1975
|
-
function NativeUnavailableState() {
|
|
1976
|
-
const theme = useEverfurTheme();
|
|
1977
|
-
const styles = useMemo(() => makeStyles5(theme), [theme]);
|
|
1978
|
-
return /* @__PURE__ */ jsx(View, { style: styles.offstate, accessible: true, accessibilityRole: "text", accessibilityLabel: "Camera is unavailable", children: /* @__PURE__ */ jsx(View, { style: styles.emptyGlyph }) });
|
|
1979
|
-
}
|
|
1980
|
-
function EmptyState({ onCapture }) {
|
|
1981
|
-
const theme = useEverfurTheme();
|
|
1982
|
-
const styles = useMemo(() => makeStyles5(theme), [theme]);
|
|
1983
|
-
return /* @__PURE__ */ jsx(View, { style: styles.centered, accessible: true, accessibilityRole: "summary", accessibilityLabel: "Take a photo to check", children: /* @__PURE__ */ jsx(Pressable, { onPress: onCapture, accessibilityRole: "button", accessibilityLabel: "Take photo", style: styles.scanButton, children: /* @__PURE__ */ jsx(Text, { style: styles.scanLabel, children: "Take photo" }) }) });
|
|
1984
|
-
}
|
|
1985
|
-
function ErrorState({ error, onRetry }) {
|
|
1986
|
-
const theme = useEverfurTheme();
|
|
1987
|
-
const styles = useMemo(() => makeStyles5(theme), [theme]);
|
|
1988
|
-
return /* @__PURE__ */ jsxs(View, { style: styles.centered, accessible: true, accessibilityRole: "alert", children: [
|
|
1989
|
-
/* @__PURE__ */ jsx(Text, { style: styles.errorText, children: error.displayMessage }),
|
|
1990
|
-
error.retryable ? /* @__PURE__ */ jsx(Pressable, { onPress: onRetry, accessibilityRole: "button", accessibilityLabel: "Try again", style: styles.primaryButton, children: /* @__PURE__ */ jsx(Text, { style: styles.primaryLabel, children: "Try again" }) }) : null
|
|
1991
|
-
] });
|
|
1992
|
-
}
|
|
1993
|
-
function GateState({ gate, onRetry }) {
|
|
1994
|
-
const theme = useEverfurTheme();
|
|
1995
|
-
const styles = useMemo(() => makeStyles5(theme), [theme]);
|
|
1996
|
-
const message = gateMessage(gate);
|
|
1997
|
-
return /* @__PURE__ */ jsxs(View, { style: styles.centered, accessible: true, accessibilityRole: "summary", accessibilityLabel: message, children: [
|
|
1998
|
-
/* @__PURE__ */ jsx(Text, { style: styles.guidanceText, children: message }),
|
|
1999
|
-
gate.kind !== "species_unsupported" ? /* @__PURE__ */ jsx(Pressable, { onPress: onRetry, accessibilityRole: "button", accessibilityLabel: "Try again", style: styles.primaryButton, children: /* @__PURE__ */ jsx(Text, { style: styles.primaryLabel, children: "Try again" }) }) : null
|
|
2000
|
-
] });
|
|
2001
|
-
}
|
|
2002
|
-
function gateMessage(gate) {
|
|
2003
|
-
switch (gate.kind) {
|
|
2004
|
-
case "species_unsupported":
|
|
2005
|
-
return "Photo checks are coming soon for this pet.";
|
|
2006
|
-
case "pet_context_required":
|
|
2007
|
-
return "Choose a pet first, then take the photo.";
|
|
2008
|
-
case "retake":
|
|
2009
|
-
return "That photo was hard to read. Please take another.";
|
|
2010
|
-
case "accepted":
|
|
2011
|
-
return "";
|
|
2012
|
-
default:
|
|
2013
|
-
return assertNever(gate);
|
|
2014
|
-
}
|
|
2015
|
-
}
|
|
2016
|
-
function ResultState({ result }) {
|
|
2017
|
-
const theme = useEverfurTheme();
|
|
2018
|
-
const styles = useMemo(() => makeStyles5(theme), [theme]);
|
|
2019
|
-
const headline = result.subtypeDisplay ?? result.diseaseName ?? result.primaryLabel;
|
|
2020
|
-
const cardTint = resultCardTint(theme, result.riskTier);
|
|
2021
|
-
return /* @__PURE__ */ jsxs(
|
|
2022
|
-
ScrollView,
|
|
2023
|
-
{
|
|
2024
|
-
style: styles.list,
|
|
2025
|
-
contentContainerStyle: [styles.listContent, { backgroundColor: cardTint }],
|
|
2026
|
-
accessibilityLabel: "Photo checkup result",
|
|
2027
|
-
children: [
|
|
2028
|
-
/* @__PURE__ */ jsx(Text, { style: styles.resultHeadline, children: headline }),
|
|
2029
|
-
/* @__PURE__ */ jsx(SeverityBadge, { riskTier: result.riskTier }),
|
|
2030
|
-
result.diseaseFraming ? /* @__PURE__ */ jsx(Text, { style: styles.summaryText, children: result.diseaseFraming }) : null,
|
|
2031
|
-
result.copyShort ? /* @__PURE__ */ jsx(Text, { style: styles.summaryText, children: result.copyShort }) : null,
|
|
2032
|
-
result.interpretation ? /* @__PURE__ */ jsxs(View, { style: styles.interpretationBlock, children: [
|
|
2033
|
-
/* @__PURE__ */ jsx(Text, { style: styles.summaryText, children: result.interpretation.summary }),
|
|
2034
|
-
/* @__PURE__ */ jsx(Text, { style: styles.disclaimerText, children: result.interpretation.disclaimer })
|
|
2035
|
-
] }) : null
|
|
2036
|
-
]
|
|
2037
|
-
}
|
|
2038
|
-
);
|
|
2039
|
-
}
|
|
2040
|
-
function resultCardTint(theme, riskTier) {
|
|
2041
|
-
switch (riskTier) {
|
|
2042
|
-
case "low_risk":
|
|
2043
|
-
return theme.color.checkupCardLowRiskBg;
|
|
2044
|
-
case "concern":
|
|
2045
|
-
return theme.color.checkupCardConcernBg;
|
|
2046
|
-
default:
|
|
2047
|
-
return theme.color.surfaceCream;
|
|
2048
|
-
}
|
|
2049
|
-
}
|
|
2050
|
-
function makeStyles5(theme) {
|
|
2051
|
-
const { color, space, radius, type } = theme;
|
|
2052
|
-
const bold = type.weight.bold;
|
|
2053
|
-
const medium = type.weight.medium;
|
|
2054
|
-
const regular = type.weight.regular;
|
|
2055
|
-
const scanInk = theme.brightness === "dark" ? color.onAccent : color.brand;
|
|
2056
|
-
return StyleSheet.create({
|
|
2057
|
-
container: { flex: 1, minHeight: MIN_SURFACE_HEIGHT, backgroundColor: color.background },
|
|
2058
|
-
body: { flex: 1 },
|
|
2059
|
-
centered: {
|
|
2060
|
-
minHeight: MIN_SURFACE_HEIGHT,
|
|
2061
|
-
flexGrow: 1,
|
|
2062
|
-
alignItems: "center",
|
|
2063
|
-
justifyContent: "center",
|
|
2064
|
-
padding: space.xl
|
|
2065
|
-
},
|
|
2066
|
-
offstate: {
|
|
2067
|
-
minHeight: MIN_SURFACE_HEIGHT,
|
|
2068
|
-
alignItems: "center",
|
|
2069
|
-
justifyContent: "center",
|
|
2070
|
-
padding: space.xl
|
|
2071
|
-
},
|
|
2072
|
-
emptyGlyph: {
|
|
2073
|
-
width: space.xxl,
|
|
2074
|
-
height: space.xxl,
|
|
2075
|
-
borderRadius: radius.pill,
|
|
2076
|
-
backgroundColor: color.surfaceSubtle,
|
|
2077
|
-
borderWidth: StyleSheet.hairlineWidth,
|
|
2078
|
-
borderColor: color.brandBorder
|
|
2079
|
-
},
|
|
2080
|
-
// Capture CTA — the Everfur pink scan pill (brief: scanButtonPink, radius pill, subtle depth).
|
|
2081
|
-
scanButton: {
|
|
2082
|
-
minHeight: MIN_TOUCH_TARGET,
|
|
2083
|
-
minWidth: MIN_TOUCH_TARGET,
|
|
2084
|
-
alignItems: "center",
|
|
2085
|
-
justifyContent: "center",
|
|
2086
|
-
paddingHorizontal: space.xl,
|
|
2087
|
-
paddingVertical: space.md,
|
|
2088
|
-
borderRadius: radius.pill,
|
|
2089
|
-
backgroundColor: color.scanButtonPink,
|
|
2090
|
-
shadowColor: color.brand,
|
|
2091
|
-
shadowOpacity: 0.18,
|
|
2092
|
-
shadowRadius: radius.xs,
|
|
2093
|
-
shadowOffset: { width: 0, height: space.xs },
|
|
2094
|
-
elevation: 3
|
|
2095
|
-
},
|
|
2096
|
-
scanLabel: {
|
|
2097
|
-
color: scanInk,
|
|
2098
|
-
fontFamily: type.family,
|
|
2099
|
-
fontSize: type.size.subtitle,
|
|
2100
|
-
lineHeight: type.size.subtitle * type.lineHeight.snug,
|
|
2101
|
-
fontWeight: bold,
|
|
2102
|
-
letterSpacing: type.letterSpacing.tight
|
|
2103
|
-
},
|
|
2104
|
-
// Secondary CTA — retry / try-again, brand fill with WCAG-legible onPrimary.
|
|
2105
|
-
primaryButton: {
|
|
2106
|
-
minHeight: MIN_TOUCH_TARGET,
|
|
2107
|
-
minWidth: MIN_TOUCH_TARGET,
|
|
2108
|
-
alignItems: "center",
|
|
2109
|
-
justifyContent: "center",
|
|
2110
|
-
paddingHorizontal: space.xl,
|
|
2111
|
-
paddingVertical: space.sm,
|
|
2112
|
-
borderRadius: radius.pill,
|
|
2113
|
-
backgroundColor: color.brand
|
|
2114
|
-
},
|
|
2115
|
-
primaryLabel: {
|
|
2116
|
-
color: color.onPrimary,
|
|
2117
|
-
fontFamily: type.family,
|
|
2118
|
-
fontSize: type.size.body,
|
|
2119
|
-
lineHeight: type.size.body * type.lineHeight.snug,
|
|
2120
|
-
fontWeight: medium
|
|
2121
|
-
},
|
|
2122
|
-
errorText: {
|
|
2123
|
-
color: color.danger,
|
|
2124
|
-
fontFamily: type.family,
|
|
2125
|
-
fontSize: type.size.body,
|
|
2126
|
-
lineHeight: type.size.body * type.lineHeight.relaxed,
|
|
2127
|
-
textAlign: "center",
|
|
2128
|
-
marginBottom: space.md
|
|
2129
|
-
},
|
|
2130
|
-
guidanceText: {
|
|
2131
|
-
color: color.text.muted,
|
|
2132
|
-
fontFamily: type.family,
|
|
2133
|
-
fontSize: type.size.body,
|
|
2134
|
-
lineHeight: type.size.body * type.lineHeight.relaxed,
|
|
2135
|
-
textAlign: "center",
|
|
2136
|
-
marginBottom: space.md
|
|
2137
|
-
},
|
|
2138
|
-
list: { flex: 1 },
|
|
2139
|
-
// The checkup card — cream/risk-washed surface (backgroundColor supplied inline from resultCardTint), a
|
|
2140
|
-
// brand hairline border, and a subtle brand-tinted shadow for depth.
|
|
2141
|
-
listContent: {
|
|
2142
|
-
padding: space.lg,
|
|
2143
|
-
gap: space.sm,
|
|
2144
|
-
borderRadius: radius.lg,
|
|
2145
|
-
borderWidth: StyleSheet.hairlineWidth,
|
|
2146
|
-
borderColor: color.brandBorder,
|
|
2147
|
-
shadowColor: color.brand,
|
|
2148
|
-
shadowOpacity: 0.08,
|
|
2149
|
-
shadowRadius: radius.xs,
|
|
2150
|
-
shadowOffset: { width: 0, height: space.xs },
|
|
2151
|
-
elevation: 2
|
|
2152
|
-
},
|
|
2153
|
-
// Verdict hero (brief: type.verdict 24) in brand green for character.
|
|
2154
|
-
resultHeadline: {
|
|
2155
|
-
color: color.text.secondary,
|
|
2156
|
-
fontFamily: type.family,
|
|
2157
|
-
fontSize: type.size.verdict,
|
|
2158
|
-
lineHeight: type.size.verdict * type.lineHeight.tight,
|
|
2159
|
-
fontWeight: bold,
|
|
2160
|
-
letterSpacing: type.letterSpacing.tight
|
|
2161
|
-
},
|
|
2162
|
-
summaryText: {
|
|
2163
|
-
color: color.text.primary,
|
|
2164
|
-
fontFamily: type.family,
|
|
2165
|
-
fontSize: type.size.body,
|
|
2166
|
-
lineHeight: type.size.body * type.lineHeight.relaxed,
|
|
2167
|
-
fontWeight: regular
|
|
2168
|
-
},
|
|
2169
|
-
interpretationBlock: {
|
|
2170
|
-
marginTop: space.sm,
|
|
2171
|
-
paddingTop: space.sm,
|
|
2172
|
-
gap: space.xs,
|
|
2173
|
-
borderTopWidth: StyleSheet.hairlineWidth,
|
|
2174
|
-
borderTopColor: color.borderHairlineGreen
|
|
2175
|
-
},
|
|
2176
|
-
disclaimerText: {
|
|
2177
|
-
color: color.text.muted,
|
|
2178
|
-
fontFamily: type.family,
|
|
2179
|
-
fontSize: type.size.caption,
|
|
2180
|
-
lineHeight: type.size.caption * type.lineHeight.normal,
|
|
2181
|
-
fontWeight: regular
|
|
2182
|
-
}
|
|
2183
|
-
});
|
|
2184
|
-
}
|
|
2185
|
-
|
|
2186
|
-
export { PhotoCheckup, usePhotoCheckup };
|
|
1
|
+
import {createContext,forwardRef,useState,useEffect,useCallback,useSyncExternalStore,useMemo,createElement,useContext,Component,useRef}from'react';import {TextInput,View,Pressable,ScrollView,StyleSheet,Text,Animated,Easing}from'react-native';import {jsx,jsxs,Fragment}from'react/jsx-runtime';var $t=Object.defineProperty;var i=(e,t)=>$t(e,"name",{value:t,configurable:true});var oe=Object.freeze([Object.freeze({wireCode:"AccessDeniedException",envelope:"http_envelope",httpStatus:403,retryable:false}),Object.freeze({wireCode:"AuthorizationException",envelope:"http_envelope",httpStatus:401,retryable:false}),Object.freeze({wireCode:"ConflictException",envelope:"http_envelope",httpStatus:409,retryable:true}),Object.freeze({wireCode:"DAILY_QUOTA_EXCEEDED",envelope:"http_envelope",httpStatus:429,retryable:true}),Object.freeze({wireCode:"FILE_TOO_LARGE",envelope:"http_envelope",httpStatus:400,retryable:false}),Object.freeze({wireCode:"IDEMPOTENCY_KEY_IN_FLIGHT",envelope:"http_envelope",httpStatus:409,retryable:true}),Object.freeze({wireCode:"IDEMPOTENCY_KEY_REUSED_DIFFERENT_REQUEST",envelope:"http_envelope",httpStatus:409,retryable:false}),Object.freeze({wireCode:"INVALID_CONTENT_TYPE",envelope:"http_envelope",httpStatus:400,retryable:false}),Object.freeze({wireCode:"InternalFailureException",envelope:"http_envelope",httpStatus:500,retryable:true}),Object.freeze({wireCode:"MethodNotAllowedException",envelope:"http_envelope",httpStatus:405,retryable:false}),Object.freeze({wireCode:"NotImplementedException",envelope:"http_envelope",httpStatus:501,retryable:false}),Object.freeze({wireCode:"PaymentRequiredException",envelope:"http_envelope",httpStatus:402,retryable:false}),Object.freeze({wireCode:"ResourceNotFoundException",envelope:"http_envelope",httpStatus:404,retryable:false}),Object.freeze({wireCode:"ServiceUnavailableException",envelope:"http_envelope",httpStatus:503,retryable:true}),Object.freeze({wireCode:"TIER_UPGRADE_REQUIRED",envelope:"http_envelope",httpStatus:402,retryable:false}),Object.freeze({wireCode:"ThrottlingException",envelope:"http_envelope",httpStatus:429,retryable:true}),Object.freeze({wireCode:"ValidationException",envelope:"http_envelope",httpStatus:400,retryable:false}),Object.freeze({wireCode:"accessDenied",envelope:"http_envelope",httpStatus:403,retryable:false}),Object.freeze({wireCode:"access_denied",envelope:"oauth_error",httpStatus:302,retryable:false}),Object.freeze({wireCode:"attachment_unreadable",envelope:"http_envelope",httpStatus:400,retryable:false}),Object.freeze({wireCode:"authExpired",envelope:"http_envelope",httpStatus:401,retryable:true}),Object.freeze({wireCode:"authRejected",envelope:"http_envelope",httpStatus:401,retryable:false}),Object.freeze({wireCode:"consent_invalid",envelope:"http_envelope",httpStatus:422,retryable:false}),Object.freeze({wireCode:"consent_required",envelope:"http_envelope",httpStatus:422,retryable:false}),Object.freeze({wireCode:"consent_scope_wrong_route",envelope:"http_envelope",httpStatus:400,retryable:false}),Object.freeze({wireCode:"device_cipher_unavailable",envelope:"http_envelope",httpStatus:503,retryable:true}),Object.freeze({wireCode:"email_invalid",envelope:"http_envelope",httpStatus:422,retryable:false}),Object.freeze({wireCode:"event_cursor_invalid",envelope:"http_envelope",httpStatus:400,retryable:false}),Object.freeze({wireCode:"event_filter_invalid",envelope:"http_envelope",httpStatus:400,retryable:false}),Object.freeze({wireCode:"event_payload_invalid",envelope:"http_envelope",httpStatus:400,retryable:false}),Object.freeze({wireCode:"event_type_not_available",envelope:"http_envelope",httpStatus:400,retryable:false}),Object.freeze({wireCode:"event_type_unknown",envelope:"http_envelope",httpStatus:400,retryable:false}),Object.freeze({wireCode:"idempotency_key_invalid",envelope:"http_envelope",httpStatus:400,retryable:false}),Object.freeze({wireCode:"idempotency_key_required",envelope:"http_envelope",httpStatus:400,retryable:false}),Object.freeze({wireCode:"idempotency_key_reused",envelope:"http_envelope",httpStatus:409,retryable:false}),Object.freeze({wireCode:"insufficientCapability",envelope:"http_envelope",httpStatus:403,retryable:false}),Object.freeze({wireCode:"insufficient_scope",envelope:"http_envelope",httpStatus:403,retryable:false}),Object.freeze({wireCode:"internal_error",envelope:"sse_frame",httpStatus:200,retryable:false}),Object.freeze({wireCode:"invalidRequest",envelope:"http_envelope",httpStatus:400,retryable:false}),Object.freeze({wireCode:"invalid_client",envelope:"oauth_error",httpStatus:401,retryable:false}),Object.freeze({wireCode:"invalid_client_metadata",envelope:"oauth_error",httpStatus:400,retryable:false}),Object.freeze({wireCode:"invalid_grant",envelope:"oauth_error",httpStatus:400,retryable:false}),Object.freeze({wireCode:"invalid_redirect_uri",envelope:"oauth_error",httpStatus:400,retryable:false}),Object.freeze({wireCode:"invalid_request",envelope:"oauth_error",httpStatus:400,retryable:false}),Object.freeze({wireCode:"invalid_scope",envelope:"oauth_error",httpStatus:400,retryable:false}),Object.freeze({wireCode:"invalid_target",envelope:"oauth_error",httpStatus:400,retryable:false}),Object.freeze({wireCode:"mcp_connection_not_found",envelope:"http_envelope",httpStatus:404,retryable:false}),Object.freeze({wireCode:"mcp_request_conflict",envelope:"http_envelope",httpStatus:409,retryable:false}),Object.freeze({wireCode:"mcp_request_gone",envelope:"http_envelope",httpStatus:410,retryable:false}),Object.freeze({wireCode:"mcp_request_not_found",envelope:"http_envelope",httpStatus:404,retryable:false}),Object.freeze({wireCode:"not_found",envelope:"sse_frame",httpStatus:200,retryable:false}),Object.freeze({wireCode:"partner_visit_link_conflict",envelope:"http_envelope",httpStatus:409,retryable:false}),Object.freeze({wireCode:"partner_visit_owner_only",envelope:"http_envelope",httpStatus:403,retryable:false}),Object.freeze({wireCode:"partner_visit_pet_already_mapped",envelope:"http_envelope",httpStatus:409,retryable:false}),Object.freeze({wireCode:"payload_too_large",envelope:"http_envelope",httpStatus:413,retryable:false}),Object.freeze({wireCode:"petRefInUse",envelope:"http_envelope",httpStatus:409,retryable:false}),Object.freeze({wireCode:"pet_health_list_removals_unconfirmed",envelope:"http_envelope",httpStatus:422,retryable:false}),Object.freeze({wireCode:"pet_health_species_unsupported",envelope:"http_envelope",httpStatus:422,retryable:false}),Object.freeze({wireCode:"pet_not_registered",envelope:"http_envelope",httpStatus:404,retryable:false}),Object.freeze({wireCode:"provider_unsupported",envelope:"http_envelope",httpStatus:422,retryable:false}),Object.freeze({wireCode:"rateLimited",envelope:"http_envelope",httpStatus:429,retryable:true}),Object.freeze({wireCode:"record_species_unsupported",envelope:"http_envelope",httpStatus:422,retryable:false}),Object.freeze({wireCode:"record_subject_not_found",envelope:"http_envelope",httpStatus:404,retryable:false}),Object.freeze({wireCode:"record_upload_invalid",envelope:"http_envelope",httpStatus:400,retryable:false}),Object.freeze({wireCode:"records_consent_required",envelope:"http_envelope",httpStatus:409,retryable:false}),Object.freeze({wireCode:"sandbox_only",envelope:"http_envelope",httpStatus:403,retryable:false}),Object.freeze({wireCode:"session_required",envelope:"http_envelope",httpStatus:403,retryable:false}),Object.freeze({wireCode:"share_limit_reached",envelope:"http_envelope",httpStatus:429,retryable:false}),Object.freeze({wireCode:"slow_down",envelope:"oauth_error",httpStatus:429,retryable:true}),Object.freeze({wireCode:"species_unsupported",envelope:"http_envelope",httpStatus:422,retryable:false}),Object.freeze({wireCode:"tenant_filter_invalid",envelope:"http_envelope",httpStatus:400,retryable:false}),Object.freeze({wireCode:"tenant_has_no_capabilities",envelope:"http_envelope",httpStatus:409,retryable:false}),Object.freeze({wireCode:"tenant_resource_not_found",envelope:"http_envelope",httpStatus:404,retryable:false}),Object.freeze({wireCode:"tenant_surface_not_enabled",envelope:"http_envelope",httpStatus:409,retryable:false}),Object.freeze({wireCode:"timeout",envelope:"sse_frame",httpStatus:200,retryable:true}),Object.freeze({wireCode:"token_invalid",envelope:"http_envelope",httpStatus:422,retryable:false}),Object.freeze({wireCode:"unavailable",envelope:"sse_frame",httpStatus:200,retryable:true}),Object.freeze({wireCode:"unavailable",envelope:"http_envelope",httpStatus:503,retryable:true}),Object.freeze({wireCode:"unsupported_grant_type",envelope:"oauth_error",httpStatus:400,retryable:false}),Object.freeze({wireCode:"unsupported_response_type",envelope:"oauth_error",httpStatus:302,retryable:false}),Object.freeze({wireCode:"verify_mint_rate_limited",envelope:"http_envelope",httpStatus:429,retryable:true}),Object.freeze({wireCode:"verify_mint_refused",envelope:"http_envelope",httpStatus:403,retryable:false}),Object.freeze({wireCode:"verify_mint_unavailable",envelope:"http_envelope",httpStatus:503,retryable:true}),Object.freeze({wireCode:"verify_user_ref_invalid",envelope:"http_envelope",httpStatus:422,retryable:false}),Object.freeze({wireCode:"webhook_endpoint_update_empty",envelope:"http_envelope",httpStatus:400,retryable:false}),Object.freeze({wireCode:"webhook_endpoint_update_invalid",envelope:"http_envelope",httpStatus:400,retryable:false}),Object.freeze({wireCode:"webhook_url_rejected",envelope:"http_envelope",httpStatus:400,retryable:false})]);Object.freeze(oe.map(e=>e.wireCode));var Jt=Object.freeze({ValidationException:"validationFailed",AuthorizationException:"authRejected",PaymentRequiredException:"paymentRequired",AccessDeniedException:"accessDenied",ResourceNotFoundException:"resourceNotFound",ConflictException:"conflict",ThrottlingException:"rateLimited",InternalFailureException:"internalError",NotImplementedException:"notImplemented",ServiceUnavailableException:"serviceUnavailable",MethodNotAllowedException:"validationFailed",authExpired:"authExpired",unavailable:"serviceUnavailable",session_required:"sessionRequired",accessDenied:"accessDenied",invalidRequest:"sdkUpdateRequired",authRejected:"authRejected",rateLimited:"rateLimited",insufficientCapability:"accessDenied",event_cursor_invalid:"validationFailed",event_filter_invalid:"validationFailed",event_payload_invalid:"validationFailed",event_type_unknown:"validationFailed",idempotency_key_invalid:"validationFailed",idempotency_key_required:"validationFailed",idempotency_key_reused:"idempotencyConflict",payload_too_large:"validationFailed",webhook_url_rejected:"validationFailed",webhook_endpoint_update_empty:"validationFailed",webhook_endpoint_update_invalid:"validationFailed",event_type_not_available:"conflict",record_subject_not_found:"resourceNotFound",records_consent_required:"conflict",record_species_unsupported:"validationFailed",record_upload_invalid:"validationFailed",FILE_TOO_LARGE:"validationFailed",INVALID_CONTENT_TYPE:"validationFailed",attachment_unreadable:"validationFailed",provider_unsupported:"validationFailed",token_invalid:"validationFailed",device_cipher_unavailable:"serviceUnavailable",email_invalid:"validationFailed",consent_required:"validationFailed",consent_invalid:"validationFailed",consent_scope_wrong_route:"validationFailed",IDEMPOTENCY_KEY_IN_FLIGHT:"idempotencyInFlight",IDEMPOTENCY_KEY_REUSED_DIFFERENT_REQUEST:"idempotencyConflict",DAILY_QUOTA_EXCEEDED:"quotaExceeded",TIER_UPGRADE_REQUIRED:"paymentRequired",not_found:"conversationNotFound",timeout:"streamTimeout",internal_error:"internalError",invalid_request:"validationFailed",invalid_client:"authRejected",invalid_grant:"authRejected",unsupported_grant_type:"validationFailed",invalid_scope:"validationFailed",invalid_target:"validationFailed",invalid_redirect_uri:"validationFailed",invalid_client_metadata:"validationFailed",unsupported_response_type:"validationFailed",access_denied:"accessDenied",slow_down:"rateLimited",mcp_request_not_found:"resourceNotFound",mcp_request_gone:"resourceNotFound",mcp_connection_not_found:"resourceNotFound",mcp_request_conflict:"conflict",sandbox_only:"accessDenied",tenant_has_no_capabilities:"conflict",verify_mint_refused:"accessDenied",verify_mint_rate_limited:"rateLimited",verify_mint_unavailable:"serviceUnavailable",verify_user_ref_invalid:"validationFailed",insufficient_scope:"accessDenied",tenant_filter_invalid:"validationFailed",tenant_resource_not_found:"resourceNotFound",tenant_surface_not_enabled:"conflict",pet_health_species_unsupported:"validationFailed",pet_health_list_removals_unconfirmed:"validationFailed",petRefInUse:"conflict",share_limit_reached:"conflict",pet_not_registered:"resourceNotFound",species_unsupported:"validationFailed",partner_visit_owner_only:"accessDenied",partner_visit_link_conflict:"conflict",partner_visit_pet_already_mapped:"conflict"}),je=Object.freeze(oe.map(e=>Object.freeze({wireCode:e.wireCode,envelope:e.envelope,retryable:e.retryable,normalized:Jt[e.wireCode]}))),Qt=new Map(je.map(e=>[e.envelope+e.wireCode.toUpperCase(),e])),Xt=Object.freeze(["http_envelope",...[...new Set(je.map(e=>e.envelope))].filter(e=>e!=="http_envelope").sort()]);function V(e,t){if(typeof e!="string")return;let r=e.trim().toUpperCase();if(r.length!==0)for(let n of [t??"http_envelope",...Xt]){let o=Qt.get(n+r);if(o)return o}}i(V,"contractErrorFor");function Zt(e){return e.envelope==="sse_frame"?`/errors#sse-${e.wireCode}`:e.envelope==="oauth_error"?`/errors#oauth-${e.wireCode}`:`/errors#${e.wireCode}`}i(Zt,"contractDocUrl");function De(e){return null}i(De,"publishedDocUrl");var er=Object.freeze({authExpired:"authentication",authRejected:"authentication",sessionRequired:"authentication",accessDenied:"authorization",paymentRequired:"entitlement",quotaExceeded:"rateLimit",rateLimited:"rateLimit",validationFailed:"invalidRequest",sdkUpdateRequired:"invalidRequest",conversationNotFound:"notFound",petNotFound:"notFound",resourceNotFound:"notFound",conflict:"conflict",idempotencyConflict:"conflict",idempotencyInFlight:"conflict",streamTimeout:"connection",streamIncomplete:"connection",upstreamLlmTimeout:"connection",serviceUnavailable:"connection",internalError:"server",notImplemented:"server",mediaUploadFailed:"media",moderationBlocked:"policy",unknown:"unknown"});function Ne(e){return er[e]}i(Ne,"categoryFor");function Le(e){let t=V(e);if(t)return t.normalized;let r=(e??"").trim().toUpperCase(),n=r.replace(/[^A-Z0-9]/g,"");if(n==="AUTHREJECTED")return "authRejected";if(r.startsWith("AUTH"))return "authExpired";switch(n){case "DAILYQUOTAEXCEEDED":case "QUOTAEXCEEDED":return "quotaExceeded";case "RATELIMITED":case "TOOMANYREQUESTS":return "rateLimited";case "IDEMPOTENCYKEYCONFLICT":case "IDEMPOTENCYCONFLICT":return "idempotencyConflict";case "ACCESSDENIED":case "FORBIDDEN":return "accessDenied";case "NOTFOUND":case "CONVERSATIONNOTFOUND":return "conversationNotFound";case "PETNOTFOUND":return "petNotFound";case "TIMEOUT":case "STREAMTIMEOUT":return "streamTimeout";case "MEDIAUPLOADFAILED":case "UNRECOGNIZEDVIDEOFORMAT":case "FILETOOSMALL":return "mediaUploadFailed";case "MODERATIONBLOCKED":return "moderationBlocked";case "UPSTREAMLLMTIMEOUT":return "upstreamLlmTimeout";case "INTERNALERROR":case "INTERNALSERVERERROR":return "internalError";case "STREAMINCOMPLETE":case "CHATSTREAMINCOMPLETE":return "streamIncomplete";case "INVALIDANALYSISTYPE":case "INVALIDUPLOADPURPOSE":return "unknown";default:return "unknown"}}i(Le,"fromWire");var tr=new Set(["streamTimeout","rateLimited","internalError","upstreamLlmTimeout","streamIncomplete"]);function ie(e){return tr.has(e)}i(ie,"isAutoRetryable");var rr=new Set(["serviceUnavailable","conflict","idempotencyInFlight"]);function Me(e){return ie(e)||rr.has(e)}i(Me,"isRetryableCode");var nr=500,ae=8e3,Ue=4;function Be(e,t=Math.random){let r=Math.max(0,e),n=Math.min(nr*2**r,ae);return Math.floor(t()*n)}i(Be,"backoffDelayMs");var or=Object.freeze({authExpired:"Please sign in again to continue.",authRejected:"Please sign in again to continue.",sessionRequired:"This app needs to be updated to keep using Everfur.",accessDenied:"This feature isn't available on your account right now.",paymentRequired:"This is part of a paid plan.",quotaExceeded:"You have used today's messages. This resets tomorrow.",rateLimited:"You are sending messages quickly. Try again in a few seconds.",validationFailed:"Something in that request was not right. Check the details and try again.",sdkUpdateRequired:"This app needs to be updated to keep using Everfur.",conflict:"That is already running. Wait for it to finish, then try again.",idempotencyConflict:"That message was already delivered.",idempotencyInFlight:"That is still processing. Give it a moment.",conversationNotFound:"This conversation is no longer available.",petNotFound:"We could not find that pet. Please pick a pet and try again.",resourceNotFound:"We could not find that.",streamTimeout:"The response timed out. Tap retry to try again.",mediaUploadFailed:"We could not upload that photo. Please attach it again.",moderationBlocked:"This request cannot be answered here.",upstreamLlmTimeout:"The response timed out. Tap retry to try again.",internalError:"We hit a snag on our end. Please try again.",notImplemented:"That is not available yet.",serviceUnavailable:"That service is briefly unavailable. Try again shortly.",streamIncomplete:"The connection dropped mid-answer. Tap retry to continue.",unknown:"Something went wrong. Please try again."});function se(e){return or[e]}i(se,"userFacingError");function qe(e,t,r){Object.defineProperty(e,t,{value:r,enumerable:false,writable:false,configurable:false});}i(qe,"defineHidden");var ir=/^[a-z][a-z0-9_]{0,63}$/;function f(e,t){let r=V(t?.wireCode,t?.wireEnvelope),n={code:e,category:Ne(e),retryable:t?.retryable??Me(e),displayMessage:se(e)};if(t?.httpStatus!==void 0&&(n.httpStatus=t.httpStatus),t?.requestId&&(n.requestId=t.requestId),typeof t?.reasonCode=="string"&&ir.test(t.reasonCode)&&(n.reasonCode=t.reasonCode),r);return t?.wireMessage&&qe(n,"wireMessage",t.wireMessage),t?.raw!==void 0&&t.raw!==null&&qe(n,"raw",t.raw),Object.freeze(n)}i(f,"makeEverfurError");function z(e,t){return Object.freeze(t===void 0?{ok:true,value:e}:{ok:true,value:e,warning:t})}i(z,"ok");function y(e){return Object.freeze({ok:false,error:e})}i(y,"err");var K=class K extends Error{constructor(t,r){super(r),this.name="EverfurConfigError",this.kind=t,Object.setPrototypeOf(this,K.prototype);}};i(K,"EverfurConfigError");var I=K;function j(e){throw new Error(`assertNever: unhandled discriminated-union member ${JSON.stringify(e)}`)}i(j,"assertNever");function He(e){return Object.freeze({kind:"error",terminal:true,code:e.code,retryable:e.retryable,wireMessage:e.wireMessage,synthesized:e.synthesized,lastEventId:e.lastEventId,serverRequestId:e.serverRequestId??null})}i(He,"errorFrame");function lr(e){let t={};return e.mode==="personalized"?t.authorization=`Bearer ${e.token}`:t["x-everfur-partner-key"]=e.publishableKey,e.userRef&&(t["x-everfur-user-ref"]=e.userRef),e.sessionId&&(t["x-everfur-session-id"]=e.sessionId),t}i(lr,"authHeaders");function D(e,t){return {...e,headers:{...dr(e.headers),...lr(t)}}}i(D,"withAuthHeaders");function dr(e){if(!e)return {};let t={};for(let[r,n]of Object.entries(e)){let o=r.toLowerCase();o==="authorization"||o==="x-everfur-partner-key"||(t[r]=n);}return t}i(dr,"stripAuthHeaders");async function Ve(e,t){if(t.mode==="anonymous")return t.transport.request(D(e,{mode:"anonymous",publishableKey:t.publishableKey,userRef:t.userRef,...t.sessionId?{sessionId:t.sessionId}:{}}));let r;try{r=await t.identity.currentToken();}catch{return y(le())}let n=await t.transport.request(D(e,{mode:"personalized",token:r,userRef:t.userRef,...t.sessionId?{sessionId:t.sessionId}:{}}));if(n.ok||n.error.httpStatus!==401)return n;let o;try{o=await t.identity.currentToken({forceRefresh:!0});}catch{return y(le())}let a=await t.transport.request(D(e,{mode:"personalized",token:o,userRef:t.userRef,...t.sessionId?{sessionId:t.sessionId}:{}}));return !a.ok&&a.error.httpStatus===401?y(le(a.error.httpStatus)):a}i(Ve,"authedSend");function le(e){return f("authRejected",{retryable:false,...e!==void 0?{httpStatus:e}:{}})}i(le,"authRejectedError");function Ke(e){return f("internalError",{retryable:true,wireMessage:ur(e)})}i(Ke,"networkError");function ur(e){return e instanceof Error?e.message:typeof e=="string"?e:"network request failed"}i(ur,"describeCause");var cr=3e4,pr=ae*2,yr="idempotency-key",fr=new Set(["POST","PUT","PATCH","DELETE"]),mr=Object.freeze({onEvent(){}}),br=Object.freeze({setTimeout:i((e,t)=>setTimeout(e,t),"setTimeout"),clearTimeout:i(e=>clearTimeout(e),"clearTimeout"),now:i(()=>Date.now(),"now")});function gr(e){let t=e.trim().replace(/[^A-Za-z0-9_.:-]/g,"-");return t.length===0?null:`efs_${t}`.slice(0,128)}i(gr,"seededIdempotencyKey");function We(e){let t=e.observer??mr,r=e.timers??br,n=e.rng??Math.random,o=e.sleep??((E,u)=>Fr(E,r,u)),a=e.maxAttempts??Ue,s=e.unaryTimeoutMs??cr,l=e.newIdempotencyKey??vr(),d=i(E=>{try{t.onEvent(E);}catch{}},"emit");async function p(E,u,_){let k=_?.maxAttempts,Kt=Math.max(1,k??a),Gt=_?.idempotent??fr.has(u.method),Wt=_?.idempotencyKey===void 0?null:gr(_.idempotencyKey),ee=Gt?Wt??l():null,Yt=hr(u,ee),te=0;for(;;){let O=i(ne=>Object.freeze({call:E,method:u.method,path:u.path,attempt:te,...ee?{idempotencyKey:ee}:{},...ne}),"stamp");d(O({phase:"attempt"}));let Ae=wr(s,r),Ie=Pr(Ae.signal,u.signal),A;try{A=await Ve({...Yt,signal:Ie.signal},e.auth);}catch(ne){A=y(Ke(ne));}finally{Ie.dispose(),Ae.cancel();}if(A.ok)return d(O({phase:"settled",ok:true,reason:"ok"})),A;let S=A.error,H=_r(S,te,{maxAttempts:Kt,rng:n,now:r.now(),aborted:u.signal?.aborted===true,...k!==void 0?{perCallMaxAttempts:k}:{},...e.errorPolicy!==void 0?{policy:e.errorPolicy}:{}}),re={ok:false,code:S.code,reason:H.reason,...S.httpStatus!==void 0?{httpStatus:S.httpStatus}:{},...S.requestId!==void 0?{requestId:S.requestId}:{}};if(!H.retry)return d(O({phase:"settled",...re})),y(S);if(d(O({phase:"retry",...re,delayMs:H.delayMs})),await o(H.delayMs,u.signal),u.signal?.aborted===true)return d(O({phase:"settled",...re,reason:"aborted"})),y(S);te+=1;}}i(p,"send");async function h(E){return D(E,await xr(e.auth))}i(h,"authorize");async function*R(E,u,_){d(Object.freeze({call:E,phase:"stream.open",method:u.method,path:u.path,attempt:0}));try{yield*e.auth.transport.stream(u,_);}catch{yield He({code:"internal_error",retryable:true,wireMessage:null,synthesized:true,lastEventId:null,serverRequestId:null});}}return i(R,"stream"),Object.freeze({send:p,authorize:h,stream:R})}i(We,"createRequestFunnel");function hr(e,t){let r={};for(let[n,o]of Object.entries(e.headers??{}))n.toLowerCase()!==yr&&(r[n]=o);return t!==null&&(r["Idempotency-Key"]=t),{...e,headers:r}}i(hr,"assembleHeaders");function vr(){let e=0;return ()=>`efk_${Date.now().toString(36)}_${(e++).toString(36)}_${Math.random().toString(36).slice(2,12)}`}i(vr,"defaultIdempotencyKeyFactory");var Er=10;function _r(e,t,r){if(r.aborted)return {retry:false,reason:"aborted"};let n=Rr(e);if(n==="quota")return {retry:false,reason:"daily-quota-exhausted"};if(n!=="burst"&&!ie(e.code))return {retry:false,reason:"terminal-code"};let o=n==="burst"?"burst-throttle":"retryable-code";if(i(()=>{if(r.policy===void 0)return false;try{return !r.policy.shouldRetry(e.code,t)}catch{return false}},"policyVetoedRetry")())return {retry:false,reason:"terminal-code"};let s=i(()=>{if(r.policy===void 0)return null;let p;try{p=r.policy.maxAttempts(e.code);}catch{return null}return typeof p!="number"||!Number.isFinite(p)?null:Math.max(1,Math.min(Er,Math.floor(p)))},"policyCeiling"),l=r.perCallMaxAttempts??s()??r.maxAttempts;if(t+1>=l)return {retry:false,reason:"attempts-exhausted"};let d=Tr(e,r.now);return d!==null?d>pr?{retry:false,reason:"server-reset-beyond-budget"}:{retry:true,reason:o,delayMs:d}:{retry:true,reason:o,delayMs:Be(t,r.rng)}}i(_r,"decideRetry");function Rr(e){let t=e.code==="unknown"?Le(Sr(e.raw)):e.code;return t==="quotaExceeded"?"quota":t==="rateLimited"||e.httpStatus===429?"burst":null}i(Rr,"throttleKind");function Tr(e,t){let r=Cr(e.raw);if(r===null)return null;let n=r-t;return n>0?n:0}i(Tr,"retryOverrideMs");function Cr(e){for(let t of Ye(e)){let r=t.resets_at,n=kr(r);if(n!==null)return n}return null}i(Cr,"findResetsAt");function Ye(e){if(!Ge(e))return [];let t=[e];for(let r of ["error","detail"]){let n=e[r];Ge(n)&&t.push(n);}return t}i(Ye,"candidateScopes");function kr(e){if(typeof e=="number"&&Number.isFinite(e))return e<1e11?Math.round(e*1e3):Math.round(e);if(typeof e=="string"&&e.trim().length>0){let t=Date.parse(e);return Number.isFinite(t)?t:null}return null}i(kr,"parseInstantMs");function Sr(e){for(let t of Ye(e)){let r=t.code;if(typeof r=="string"&&r.length>0)return r}return null}i(Sr,"rawWireCode");function Ge(e){return typeof e=="object"&&e!==null}i(Ge,"isRecord");async function xr(e){return e.mode==="anonymous"?{mode:"anonymous",publishableKey:e.publishableKey,userRef:e.userRef,...e.sessionId?{sessionId:e.sessionId}:{}}:{mode:"personalized",token:await e.identity.currentToken(),userRef:e.userRef,...e.sessionId?{sessionId:e.sessionId}:{}}}i(xr,"resolveDeviceAuth");function wr(e,t){let r=new AbortController,n=t.setTimeout(()=>r.abort(),e);return {signal:r.signal,cancel:i(()=>t.clearTimeout(n),"cancel")}}i(wr,"boundedSignal");function Pr(e,t){let r=i(()=>{},"noop");if(!t)return {signal:e,dispose:r};let n=new AbortController;if(e.aborted||t.aborted)return n.abort(),{signal:n.signal,dispose:r};let o=i(()=>n.abort(),"onAbort");return e.addEventListener("abort",o,{once:true}),t.addEventListener("abort",o,{once:true}),{signal:n.signal,dispose:i(()=>{e.removeEventListener("abort",o),t.removeEventListener("abort",o);},"dispose")}}i(Pr,"mergeSignals");function Fr(e,t,r){return e<=0||r?.aborted===true?Promise.resolve():new Promise(n=>{let o=t.setTimeout(()=>{r?.removeEventListener("abort",a),n();},e),a=i(()=>{t.clearTimeout(o),n();},"onAbort");r?.addEventListener("abort",a,{once:true});})}i(Fr,"delayAbortable");var $e=Object.freeze({track(){},trackExpectedFailure(){}});function Je(e){let t={uri:e.uri,mimeType:e.mimeType},r=de(e);return r!==null&&(t.sizeBytes=r),Object.freeze(t)}i(Je,"fileHandleFromMedia");function de(e){let t=e.byteSize??e.sizeBytes;return typeof t=="number"&&Number.isFinite(t)&&t>=0?t:null}i(de,"mediaByteSize");function Qe(e,t){return Object.freeze(t===void 0?{mode:e}:{mode:e,maxDurationSeconds:t})}i(Qe,"captureOptionsFor");function ue(e){return e!=null&&e.isAvailable===true}i(ue,"isCaptureAvailable");var Xe=Object.freeze({setTimeout:i((e,t)=>setTimeout(e,t),"setTimeout"),clearTimeout:i(e=>clearTimeout(e),"clearTimeout"),now:i(()=>Date.now(),"now")});function Ze(e,t,r){return new Promise(n=>{if(t.aborted){n();return}let o={handle:void 0},a=i(()=>{r.clearTimeout(o.handle),t.removeEventListener("abort",a),n();},"done");o.handle=r.setTimeout(a,e),t.addEventListener("abort",a,{once:true});})}i(Ze,"delayAbortable");function Or(e,t){if(!t)return e;let r=new AbortController;if(e.aborted||t.aborted)return r.abort(),r.signal;let n=i(()=>r.abort(),"onAbort");return e.addEventListener("abort",n,{once:true}),t.addEventListener("abort",n,{once:true}),r.signal}i(Or,"mergeSignals");var ce=class ce{constructor(t){this.listeners=new Set;this.epoch=0;this.inFlight=null;this.snapshot=Object.freeze({...t});}subscribe(t){return this.listeners.add(t),()=>{this.listeners.delete(t);}}getSnapshot(){return this.snapshot}patch(t){this.snapshot=Object.freeze({...this.snapshot,...t});for(let r of this.listeners)r();}beginOperation(t){this.inFlight?.abort();let r=new AbortController;this.inFlight=r;let n=this.epoch;return {signal:t?Or(r.signal,t):r.signal,epoch:n,settle:i(()=>{this.inFlight===r&&(this.inFlight=null);},"settle")}}alive(t){return t===this.epoch}rescope(){this.epoch+=1,this.inFlight?.abort(),this.inFlight=null;}dispose(){this.epoch+=1,this.inFlight?.abort(),this.inFlight=null,this.listeners.clear();}};i(ce,"MediaStore");var G=ce;function et(e){for(let t=0;t<e.length;t+=1){let r=e.charCodeAt(t);if(r>=55296&&r<=56319){let n=e.charCodeAt(t+1);if(!(n>=56320&&n<=57343))return true;t+=1;}else if(r>=56320&&r<=57343)return true}return false}i(et,"hasLoneSurrogate");var Ar=Object.freeze(["primary_label","primary_confidence","subtype_label","subtype_display","subtype_confidence","risk_tier","confidence_tier","copy_short","disease_name","disease_framing","model_version","skin_v2","eye_v2","llm_checkup"]),Ir=Object.freeze(["decision_audit","decision_thresholds","per_class_auroc","ood_layers","gate1_score","possible_conditions","top_class"]),zr=new Set(["skin_v2","eye_v2","llm_checkup"]),jr=new Set(["primary_confidence","subtype_confidence"]),Dr=new Set(["low_risk","concern","uncertain"]),Nr=new Set(["possible","probable"]),Lr=Object.freeze({primary_label:64,subtype_label:64,subtype_display:128,copy_short:512,disease_name:128,disease_framing:512,model_version:64});function tt(e){let t=v(e)?e:{},r={};for(let o of Ar){if(!(o in t))continue;let a=t[o],s=Mr(o,a);s!==P&&(r[o]=s);}let n=0;for(let o of Ir)o in t&&(n+=1);return Object.freeze({metadata:Object.freeze(r),droppedOperatorKeys:n})}i(tt,"projectDetectionMetadata");var P=Symbol("drop");function Mr(e,t){if(zr.has(e))return typeof t=="boolean"?t:P;if(jr.has(e))return rt(t)?t:P;if(e==="risk_tier")return typeof t=="string"&&Dr.has(t)?t:P;if(e==="confidence_tier")return typeof t=="string"&&Nr.has(t)?t:P;if(typeof t!="string")return P;let r=Lr[e]??256;return t.length>r?t.slice(0,r):t}i(Mr,"acceptKey");function rt(e){return typeof e=="number"&&Number.isFinite(e)&&e>=0&&e<=1}i(rt,"isUnitConfidence");function nt(e){if(!v(e))return null;let t=m(e.summary),r=m(e.disclaimer),n=Ur(e.recommendations??e.recommendation);return t===null&&r===null&&n.length===0?null:Object.freeze({summary:t??"",recommendations:Object.freeze(n),disclaimer:r??""})}i(nt,"parseInterpretation");function ot(e){if(!v(e))return {};if(v(e.metadata))return e.metadata;let t=e.detections;if(Array.isArray(t)&&t.length>0){let r=t[0];if(v(r)&&v(r.metadata))return r.metadata;if(v(r))return r}return e}i(ot,"locateDetectionMetadata");function it(e){return v(e)?v(e.interpretation)?e.interpretation:e:null}i(it,"locateInterpretation");function v(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}i(v,"isRecord");function m(e){return typeof e=="string"&&e.length>0?e:null}i(m,"asString");function Ur(e){return typeof e=="string"?e.length>0?[e]:[]:Array.isArray(e)?e.filter(t=>typeof t=="string"&&t.length>0):[]}i(Ur,"asStringList");function at(e){return rt(e)?e:null}i(at,"asUnitNumber");function pe(e){return typeof e=="number"&&Number.isFinite(e)?e:null}i(pe,"asFiniteNumber");var N=Object.freeze({initiate:"/widget/v1/uploads/initiate",precheck:"/widget/v1/photo/precheck",photoAnalyze:"/widget/v1/photo/analyze",videoAnalyze:"/widget/v1/video/analyze",job:i(e=>et(e)?null:`/widget/v1/analysis/jobs/${encodeURIComponent(e)}`,"job")});async function lt(e,t){let r={purpose:t.purpose,content_type:t.contentType};t.byteSize!==null&&(r.byte_size=t.byteSize);let n={method:"POST",path:N.initiate,body:r,...t.signal?{signal:t.signal}:{}},o=await e.send("media.initiateUpload",n);if(!o.ok)return y(o.error);let a=Br(o.value);return a===null?y(f("mediaUploadFailed",{retryable:false})):z(a)}i(lt,"initiateUpload");function Br(e){if(!v(e))return null;let t=m(e.url),r=m(e.s3_key);if(t===null||r===null)return null;let n=e.fields,o={};if(v(n))for(let[a,s]of Object.entries(n))typeof s=="string"&&(o[a]=s);return Object.freeze({url:t,fields:Object.freeze(o),s3Key:r,expiresAt:pe(e.expires_at)})}i(Br,"parsePresigned");async function dt(e,t,r,n,o){try{return await e.presignedPut(Je(t),{target:r,idempotencyKey:n,...o?{signal:o}:{}}),z(void 0)}catch{return y(f("mediaUploadFailed",{retryable:false}))}}i(dt,"stageUpload");async function ut(e,t){let r={method:"POST",path:N.precheck,body:{s3_key:t.s3Key,type:t.type},...t.signal?{signal:t.signal}:{}},n=await e.send("media.precheck",r);return n.ok?qr(n.value):{verdict:"proceed",reasonCodes:[],precheckToken:null}}i(ut,"runPrecheck");function qr(e){if(!v(e))return {verdict:"proceed",reasonCodes:[],precheckToken:null};let t=m(e.verdict),r=t==="retake"||t==="reject"?t:"proceed",n=Array.isArray(e.reason_codes)?e.reason_codes.filter(o=>typeof o=="string"):[];return Object.freeze({verdict:r,reasonCodes:Object.freeze(n),precheckToken:m(e.precheck_token)})}i(qr,"parsePrecheck");async function ct(e,t,r,n,o){let a={method:"POST",path:t,body:r,...o?{signal:o}:{}},s=await e.send("media.analyze",a);if(s.ok){let d=Hr(s.value);return d!==null?{kind:"async",job:d}:{kind:"sync",body:s.value}}let l=n(s.error.httpStatus,s.error);return l!==null?{kind:"gate",gate:l}:{kind:"error",error:s.error}}i(ct,"submitAnalyze");function Hr(e){if(!v(e))return null;let t=m(e.job_id);if(t===null)return null;let r=m(e.status);return r!==null&&Wr(r)?null:Object.freeze({jobId:t,estimatedSeconds:pe(e.estimated_completion_seconds)})}i(Hr,"parseAsyncJob");var Vr=2e3,st=5e3,Kr=9e4;async function pt(e,t,r){let{timers:n,signal:o,alive:a}=r,s=n.now(),l=Math.max(Kr,(r.estimatedSeconds??0)*1e3),d=Vr;for(;;){if(o.aborted||!a())return {kind:"aborted"};if(await Ze(d,o,n),o.aborted||!a())return {kind:"aborted"};if(n.now()-s>l)return {kind:"timeout"};let p=N.job(t);if(p===null)return {kind:"notFound"};let h=await e.send("media.pollJob",{method:"GET",path:p,...o?{signal:o}:{}},{maxAttempts:1});if(o.aborted||!a())return {kind:"aborted"};if(!h.ok){if(h.error.httpStatus===404)return {kind:"notFound"};d=Math.min(st,d+1e3);continue}let R=Gr(h.value);if(R==="COMPLETED")return {kind:"complete",body:h.value};if(R==="FAILED")return {kind:"failed",error:Yr(h.value)};d=Math.min(st,d+1e3);}}i(pt,"pollJob");function Gr(e){return v(e)?(m(e.status)??"PROCESSING").toUpperCase():"PROCESSING"}i(Gr,"jobStatusOf");function Wr(e){let t=e.toUpperCase();return t==="COMPLETED"||t==="FAILED"}i(Wr,"isTerminalStatus");function Yr(e){let t=v(e)?m(e.error)??m(e.error_code):null;return f("internalError",{retryable:false,...t?{wireMessage:t}:{}})}i(Yr,"jobFailureError");function yt(e,t){return e===422?{kind:"species_unsupported",species:"cat"}:e===400?{kind:"pet_context_required"}:null}i(yt,"detectPhotoGate");function ft(e,t){let r={s3_key:e};return t.type!==void 0&&(r.type=t.type),t.petRef!=null&&(r.pet_id=t.petRef),t.precheckToken!=null&&(r.precheck_token=t.precheckToken),r}i(ft,"analyzeBody");var L=Object.freeze({kind:"accepted"});function mt(e){return new ye(e)}i(mt,"createPhotoController");var fe=class fe extends G{constructor(t){super({status:"idle",region:t.region??"skin",result:null,gate:L,error:null,isCaptureAvailable:ue(t.camera)}),this.funnel=t.funnel??We({auth:t.auth,...t.errorPolicy?{errorPolicy:t.errorPolicy}:{}}),this.camera=t.camera,this.upload=t.upload,this.telemetry=t.telemetry??$e,this.timers=t.timers??Xe,this.makeIdempotencyKey=t.makeIdempotencyKey??Xr();}async capture(t,r){if(!ue(this.camera))return y(f("unknown",{retryable:false}));let n;try{n=await this.camera.capture(Qe("photo"));}catch{return this.snapshot.status!=="idle"&&this.patch({status:"idle"}),y(f("unknown",{retryable:false}))}return this.analyze(n,t,r)}async analyze(t,r,n){let o=this.beginOperation(n.signal),a=this.makeIdempotencyKey(),s=this.timers.now();this.patch({status:"capturing",region:r,error:null,result:null,gate:L});try{let l=await lt(this.funnel,{purpose:"skin_scan",contentType:t.mimeType,byteSize:de(t),signal:o.signal});if(o.signal.aborted||!this.alive(o.epoch))return T();if(!l.ok)return this.fail(o,l.error);let d=await dt(this.upload,t,l.value,a,o.signal);if(o.signal.aborted||!this.alive(o.epoch))return T();if(!d.ok)return this.fail(o,d.error);let p=await ut(this.funnel,{s3Key:l.value.s3Key,type:r,signal:o.signal});if(o.signal.aborted||!this.alive(o.epoch))return T();if(p.verdict!=="proceed")return this.gateRetake(o,r,p.reasonCodes);this.patch({status:"analyzing"});let h=await ct(this.funnel,N.photoAnalyze,ft(l.value.s3Key,{type:r,petRef:n.petRef,precheckToken:p.precheckToken}),yt,o.signal);if(o.signal.aborted||!this.alive(o.epoch))return T();switch(h.kind){case "error":return this.fail(o,h.error);case "gate":return this.gateDomain(o,r,h.gate);case "sync":return this.complete(o,h.body,r,null,s);case "async":{let R=await pt(this.funnel,h.job.jobId,{timers:this.timers,signal:o.signal,alive:i(()=>this.alive(o.epoch),"alive"),estimatedSeconds:h.job.estimatedSeconds});if(o.signal.aborted||!this.alive(o.epoch))return T();switch(R.kind){case "aborted":return T();case "complete":return this.complete(o,R.body,r,h.job.jobId,s);case "failed":return this.fail(o,R.error);case "timeout":return this.fail(o,f("streamTimeout",{retryable:!0}));case "notFound":return this.fail(o,f("conversationNotFound",{retryable:!1,httpStatus:404}));default:return this.fail(o,f("internalError",{retryable:!1}))}}default:return this.fail(o,f("internalError",{retryable:!1}))}}finally{o.settle();}}reset(){this.inFlight?.abort(),this.inFlight=null,this.patch({status:"idle",result:null,gate:L,error:null});}complete(t,r,n,o,a){if(!this.alive(t.epoch))return T();let s=$r(r,n,o);return s.droppedOperatorKeys>0&&this.telemetry.track("analysis.metadata_leak_guard",{droppedKeyCount:s.droppedOperatorKeys}),this.patch({status:"complete",result:s.result,gate:L,error:null}),this.telemetry.track("analysis.species_gate",{outcome:"accepted",region:n}),this.telemetry.track("analysis.result",{capability:"photo",region:n,riskTier:s.result.riskTier??"",confidenceTier:s.result.confidenceTier??"",modelVersion:s.result.modelVersion??"",timeToResultMs:this.timers.now()-a}),z(s.result)}gateDomain(t,r,n){if(!this.alive(t.epoch))return T();let o=n.kind==="species_unsupported"?Object.freeze({kind:"species_unsupported",species:"cat"}):Object.freeze({kind:"pet_context_required"});return this.patch({status:"idle",gate:o,error:null,result:null}),this.telemetry.track("analysis.species_gate",{outcome:n.kind,region:r,...n.kind==="species_unsupported"?{species:"cat"}:{}}),y(f("unknown",{retryable:false}))}gateRetake(t,r,n){if(!this.alive(t.epoch))return T();this.patch({status:"idle",gate:Object.freeze({kind:"retake",reasons:Object.freeze([...n])}),error:null,result:null}),this.telemetry.track("analysis.quality_gate",{usable:false,reasons:n});let o=n.includes("wrong_species")?"species_unsupported":"retake";return this.telemetry.track("analysis.species_gate",{outcome:o,region:r}),y(f("unknown",{retryable:false}))}fail(t,r){return this.alive(t.epoch)?(this.patch({status:"error",error:r,gate:L}),this.telemetry.trackExpectedFailure("analysis.error",{code:r.code,...r.httpStatus!==void 0?{httpStatus:r.httpStatus}:{}}),y(r)):T()}};i(fe,"PhotoController");var ye=fe;function $r(e,t,r){let n=tt(ot(e)),o=n.metadata,a=nt(it(e));return {result:Object.freeze({jobId:r,analysisType:t,primaryLabel:m(o.primary_label)??"Uncertain",riskTier:Jr(o.risk_tier),confidence:at(o.primary_confidence),subtypeDisplay:m(o.subtype_display),confidenceTier:Qr(o.confidence_tier),diseaseName:m(o.disease_name),diseaseFraming:m(o.disease_framing),copyShort:m(o.copy_short),interpretation:a,modelVersion:m(o.model_version)??(v(e)?m(e.model_version):null)}),droppedOperatorKeys:n.droppedOperatorKeys}}i($r,"mapPhotoResult");function Jr(e){return e==="low_risk"||e==="concern"||e==="uncertain"?e:null}i(Jr,"asRiskTier");function Qr(e){return e==="possible"||e==="probable"?e:null}i(Qr,"asConfidenceTier");function T(){return y(f("unknown",{retryable:false}))}i(T,"abortedResult");function Xr(){let e=0;return ()=>`photo-analyze-${Date.now().toString(36)}-${(e++).toString(36)}-${Math.random().toString(36).slice(2,10)}`}i(Xr,"defaultIdempotencyFactory");var bt=Symbol.for("@everfur/sdk#EverfurContext@v1"),gt=globalThis,ht=gt[bt]??(gt[bt]=createContext(null));ht.displayName??="EverfurContext";function W(){let e=useContext(ht);if(e===null)throw new I("not-initialized","Everfur hooks must be called inside <EverfurProvider>. Wrap your tree once, high.");return e}i(W,"useEverfurContext");var an=Object.freeze({status:"idle",region:"skin",result:null,gate:Object.freeze({kind:"accepted"}),error:null,isCaptureAvailable:false}),sn=i(()=>{},"NOOP_UNSUBSCRIBE");function me(e){let{runtime:t,userRef:r,scopeEpoch:n}=W(),o=e.petRef,[a,s]=useState(null);useEffect(()=>{let{services:u}=t.registry,_=t.registry.acquire("photo",`${r??""}|${o}`,()=>mt({...u.errorPolicy?{errorPolicy:u.errorPolicy}:{},auth:u.authFor(r),camera:u.camera,upload:u.upload,telemetry:u.telemetry}),k=>k.dispose());return s(_),()=>{s(k=>k===_?null:k),t.registry.release(_);}},[t,r,o,n]);let l=useCallback(u=>a?a.subscribe(u):sn,[a]),d=useCallback(()=>a?a.getSnapshot():an,[a]),p=useSyncExternalStore(l,d,d),h=useCallback((u,_)=>a?a.analyze(u,_,{petRef:o}):Promise.resolve(vt()),[a,o]),R=useCallback(u=>a?a.capture(u,{petRef:o}):Promise.resolve(vt()),[a,o]),E=useCallback(()=>{a?.reset();},[a]);return useMemo(()=>({status:p.status,region:p.region,result:p.result,gate:p.gate,error:p.error,isCaptureAvailable:p.isCaptureAvailable,isBootstrapping:a===null,analyze:h,capture:R,reset:E}),[p,a,h,R,E])}i(me,"usePhotoCheckup");function vt(){return y(f("internalError",{retryable:true}))}i(vt,"notReadyError");function he(e){if(e!==null&&typeof e=="object"&&!Object.isFrozen(e)){Object.freeze(e);for(let t of Object.keys(e))he(e[t]);}return e}i(he,"deepFreeze");var ln=Object.freeze({content:0,heading:2,control:1.8,compact:1.5,badge:1.3}),_t={xs:4,sm:8,md:12,lg:16,xl:24,xxl:32},Rt={tile:5,xs:6,sm:10,md:14,lg:20,xl:28,pill:500},Et={nano:8,small:9,micro:10,tiny:11,caption:12,bodyTight:13,body:14,subtitle:16,heading:18,verdict:24},dn={tight:1.15,snug:1.3,normal:1.4,relaxed:1.55},un={regular:"400",medium:"500",bold:"700"},cn={eyebrow:1,monoWide:.9,headerBrand:-0.42,tight:-0.35},pn=0,Y="System",$="monospace",yn=["content","heading","control","compact","badge"];function fn(e){let t={};for(let r of yn)t[r]=e;return t}i(fn,"authoredTierSizes");function Tt(){return {family:Y,mono:$,fontUrl:null,families:{regular:Y,medium:Y,bold:Y,mono:$,monoMedium:$,monoBold:$},namedFaces:{regular:false,medium:false,bold:false,mono:false,monoMedium:false,monoBold:false},fontUrls:{},size:Et,tierSizes:fn(Et),lineHeight:dn,weight:un,letterSpacing:cn,scale:1,maxScale:pn,scaleCaps:ln}}i(Tt,"baseType");var Ct={},be=Object.freeze({standard:Object.freeze([.2,0,0,1]),emphasized:Object.freeze([.2,.8,.2,1]),exit:Object.freeze([.4,0,1,1])});function ge(e){return `cubic-bezier(${e.join(", ")})`}i(ge,"cssCubicBezier");var kt={duration:{instant:80,fast:140,normal:220,slow:320},spring:{gentle:{damping:18,stiffness:200}},easing:{standard:ge(be.standard),emphasized:ge(be.emphasized),exit:ge(be.exit)},reducedMotion:false},mn={brand:"#10462E",brandSoft:"rgba(16,70,46,0.10)",brandSofter:"rgba(16,70,46,0.15)",brandBorder:"rgba(16,70,46,0.18)",borderHairlineGreen:"rgba(16,70,46,0.25)",brandBorderStrong:"rgba(16,70,46,0.55)",brandInk:"#10462E",linkInk:"#10462E",surface:"#FFFFFF",surfaceCream:"#FDFBF8",surfaceSubtle:"#EFE9E3",background:"#FFFFFF",borderRingCream:"#D8D2C8",chatGround:"#FFFFFF",composerFill:"#FDFBF8",composerBorder:"rgba(16,70,46,0.75)",shadow:"#0B3421",text:{primary:"#111111",secondary:"#0E462E",muted:"#6E6660",placeholder:"rgba(61,61,61,0.75)",onBrand:"#FFFFFF",metaCool:"#3D3D3D",timestamp:"rgba(61,61,61,0.75)"},primary:"#10462E",onPrimary:"#FFFFFF",accent:"#10462E",onAccent:"#FFFFFF",accentEdge:"#07C168",success:"#07C168",danger:"#B91C1C",errorRed:"#FF1B00",warningStrong:"#A45610",cautionAccent:"#D9821D",riskConcern:"#EFA700",sentiment:{up:"#07C168",neutral:"#FBC558",down:"#FF1B00"},badgeBgPositive:"rgba(7,193,104,0.12)",badgeBgCaution:"rgba(217,130,29,0.14)",badgeBgCritical:"rgba(185,28,28,0.12)",badgeBgNeutral:"rgba(110,102,96,0.12)",bubbleUser:"#10462E",bubbleAssistant:"#FFFFFF",skeleton:"#EFE9E3",scanButtonPink:"#FFC3BC",checkupCardLowRiskBg:"rgba(7,193,104,0.10)",checkupCardConcernBg:"rgba(239,167,0,0.10)"},St=he({brightness:"light",highContrast:false,meta:{displayName:"Everfur",logoUrl:null,placeholder:null,hidePoweredBy:false},color:mn,space:_t,radius:Rt,type:Tt(),motion:kt,slots:Ct}),bn={brand:"#6BD39A",brandSoft:"rgba(107,211,154,0.12)",brandSofter:"rgba(107,211,154,0.18)",brandBorder:"rgba(107,211,154,0.22)",borderHairlineGreen:"rgba(107,211,154,0.28)",brandBorderStrong:"rgba(107,211,154,0.55)",brandInk:"#6BD39A",linkInk:"#6BD39A",surface:"#1C1F23",surfaceCream:"#23272C",surfaceSubtle:"#23272C",background:"#15171A",borderRingCream:"#3A3F45",chatGround:"#1C1F23",composerFill:"#23272C",composerBorder:"rgba(107,211,154,0.75)",shadow:"#000000",text:{primary:"#F5F3F0",secondary:"#A9E4C4",muted:"#9A938C",placeholder:"rgba(255,255,255,0.55)",onBrand:"#0E1F17",metaCool:"#C9C9C9",timestamp:"rgba(245,243,240,0.7)"},primary:"#6BD39A",onPrimary:"#0E1F17",accent:"#6BD39A",onAccent:"#0E1F17",accentEdge:"#34D888",success:"#07C168",danger:"#F87171",errorRed:"#FF6B5B",warningStrong:"#F0A35E",cautionAccent:"#D9821D",riskConcern:"#EFA700",sentiment:{up:"#07C168",neutral:"#FBC558",down:"#FF1B00"},badgeBgPositive:"rgba(7,193,104,0.18)",badgeBgCaution:"rgba(217,130,29,0.20)",badgeBgCritical:"rgba(185,28,28,0.22)",badgeBgNeutral:"rgba(255,255,255,0.10)",bubbleUser:"#6BD39A",bubbleAssistant:"#1C1F23",skeleton:"#23272C",scanButtonPink:"#FFC3BC",checkupCardLowRiskBg:"rgba(7,193,104,0.16)",checkupCardConcernBg:"rgba(239,167,0,0.16)"};he({brightness:"dark",highContrast:false,meta:{displayName:"Everfur",logoUrl:null,placeholder:null,hidePoweredBy:false},color:bn,space:_t,radius:Rt,type:Tt(),motion:kt,slots:Ct});var xt=Symbol.for("@everfur/sdk#EverfurThemeContext@v1"),wt=globalThis,ve=wt[xt]??(wt[xt]=createContext(St));ve.displayName??="EverfurThemeContext";function g(){return useContext(ve)}i(g,"useEverfurTheme");function Rn(e,t){let r=e.scaleCaps[t];return r>0?r:void 0}i(Rn,"tierMultiplier");function b({tier:e="content",...t}){let{type:r}=g(),n=Rn(r,e);return createElement(Text,n===void 0?t:{maxFontSizeMultiplier:n,...t})}i(b,"Text");forwardRef(i(function(t,r){return createElement(TextInput,{...t,...r===null?{}:{ref:r}})},"TextInput"));var Tn=Object.freeze({regular:"regular",medium:"medium",bold:"bold",mono:"regular",monoMedium:"medium",monoBold:"bold"});function U(e,t){let r=e.type,n=r.families[t];return t==="mono"||r.namedFaces[t]?{fontFamily:n}:{fontFamily:n,fontWeight:r.weight[Tn[t]]}}i(U,"face");var Re=class Re extends Component{constructor(){super(...arguments);this.state={failed:false,key:this.props.resetKey};}static getDerivedStateFromError(){return {failed:true}}static getDerivedStateFromProps(r,n){return Object.is(r.resetKey,n.key)?null:{failed:false,key:r.resetKey}}render(){return this.state.failed?this.props.fallback:this.props.children}};i(Re,"HostRenderBoundary");var Ee=Re;function kn({render:e}){return e()}i(kn,"RenderHost");function _e({render:e,fallback:t,resetKey:r}){return createElement(Ee,{fallback:t,resetKey:r,children:createElement(kn,{render:e})})}i(_e,"HostRender");var Sn=Object.freeze({chat:Object.freeze(["chat.message.create"]),records:Object.freeze(["records.upload.create","records.record.read"]),recordsClinic:Object.freeze(["records.clinic.create"]),video:Object.freeze(["video.gait.execute"]),photo:Object.freeze(["photo.checkup.execute"]),animations:Object.freeze(["animations.state.read"]),televet:Object.freeze(["televet.visit.create"])});function xn(e){return Sn[e]}i(xn,"requires");function Ot(e,t,r){let n=xn(e),o=new Map;for(let l of t)o.set(l.resource,l);if(r?.isPending===true&&n.some(l=>!o.has(l)))return Te({name:e,enabled:false,isPending:true,limits:{}});let a=wn(n,o);return n.filter(l=>o.get(l)?.granted!==true).length===0?Te({name:e,enabled:true,isPending:false,limits:a}):Te({name:e,enabled:false,isPending:false,limits:a,disabled:Pn(n,o)})}i(Ot,"decideCapability");function wn(e,t){let r={};for(let n of e){let o=t.get(n)?.limits;if(o)for(let[a,s]of Object.entries(o)){let l=r[a];r[a]=l===void 0?s:Math.min(l,s);}}return r}i(wn,"mergeLimits");function Pn(e,t){for(let r of e){let n=t.get(r)?.disabled;if(n)return Object.freeze({reason:n.reason,level:n.level,recoverable:n.recoverable})}return Object.freeze({reason:se("accessDenied"),level:"user_entitlement",recoverable:false})}i(Pn,"disabledFor");function Te(e){let t={name:e.name,enabled:e.enabled,isPending:e.isPending,limits:Object.freeze({...e.limits})};return e.disabled!==void 0&&(t.disabled=e.disabled),Object.freeze(t)}i(Te,"freezeDecision");function Ce(e){let{runtime:t}=W(),r=t.entitlements,n=useSyncExternalStore(r.subscribe,r.getSnapshot,r.getSnapshot);return useMemo(()=>Ot(e,n.decisions,{isPending:n.isPending}),[e,n])}i(Ce,"useCapability");var An=48;function In(e){return e.isPending?"pending":e.enabled?"enabled":"disabled"}i(In,"selectView");function jt(e){let{capability:t,children:r,renderDisabled:n,renderPending:o,style:a}=e,s=Ce(t),l=useMemo(()=>In(s),[s]);switch(l){case "enabled":return r;case "pending":return o?jsx(_e,{render:o,fallback:jsx(It,{capability:t,style:a})}):jsx(It,{capability:t,style:a});case "disabled":return n?jsx(_e,{render:()=>n(s),fallback:jsx(zt,{decision:s,style:a}),resetKey:s}):jsx(zt,{decision:s,style:a});default:return j(l)}}i(jt,"CapabilityGate");function It({capability:e,style:t}){let r=g(),n=useMemo(()=>Dt(r),[r]);return jsx(View,{style:[n.offstate,t],accessible:true,accessibilityState:{busy:true},accessibilityLabel:`Loading ${e}`,children:jsx(View,{style:n.skeletonBar})})}i(It,"PendingSkeleton");function zt({decision:e,style:t}){let r=g(),n=useMemo(()=>Dt(r),[r]),o=e.disabled?.reason??"";return jsx(View,{style:[n.offstate,t],accessible:true,accessibilityRole:"text",accessibilityLabel:o,children:o.length>0?jsx(b,{style:n.offstateText,children:o}):null})}i(zt,"OffState");function Dt(e){let{color:t,space:r,radius:n,type:o}=e;return StyleSheet.create({offstate:{minHeight:An,justifyContent:"center",paddingHorizontal:r.lg,paddingVertical:r.md,backgroundColor:t.surfaceCream,borderRadius:n.md,borderWidth:StyleSheet.hairlineWidth,borderColor:t.brandBorder},offstateText:{...U(e,"regular"),fontSize:o.size.body,lineHeight:Math.round(o.size.body*o.lineHeight.normal),color:t.text.muted},skeletonBar:{height:r.md,borderRadius:n.xs,backgroundColor:t.brandSofter}})}i(Dt,"makeStyles");var B=Object.freeze({title:"For educational use only",body:"Everfur is for educational purposes only. It helps you track visible changes through AI checkups. It does not diagnose, treat, prescribe, or replace veterinary care. For urgent symptoms or emergencies, contact a veterinarian immediately."});function Mt(e){let t=g(),r=useMemo(()=>Dn(t),[t]);return jsxs(View,{style:[r.container,e.style],accessible:true,accessibilityRole:"text",accessibilityLabel:`${B.title}. ${B.body}`,children:[jsx(b,{style:r.title,children:B.title}),jsx(b,{style:r.body,children:B.body})]})}i(Mt,"ClinicalDisclaimer");function Dn(e){let{color:t,space:r,type:n}=e;return StyleSheet.create({container:{paddingHorizontal:r.lg,paddingVertical:r.md,borderTopWidth:StyleSheet.hairlineWidth,borderTopColor:t.borderHairlineGreen,backgroundColor:t.surfaceCream,gap:r.xs},title:{...U(e,"bold"),fontSize:n.size.caption,lineHeight:Math.round(n.size.caption*n.lineHeight.snug),color:t.text.secondary,letterSpacing:n.letterSpacing.eyebrow},body:{...U(e,"regular"),fontSize:n.size.caption,lineHeight:Math.round(n.size.caption*n.lineHeight.snug),color:t.text.muted}})}i(Dn,"makeStyles");var Vn=1400,Kn=300;function Bt(){let e=g(),t=useMemo(()=>Gn(e),[e]),r=e.motion.reducedMotion,[n,o]=useState(false);useEffect(()=>{let l=setTimeout(()=>o(true),Kn);return ()=>clearTimeout(l)},[]);let a=useRef(new Animated.Value(0)).current;useEffect(()=>{if(r||!n)return;let l=Animated.loop(Animated.timing(a,{toValue:1,duration:Vn,easing:Easing.linear,useNativeDriver:true}));return l.start(),()=>l.stop()},[r,n,a]);let s=r?null:{transform:[{rotate:a.interpolate({inputRange:[0,1],outputRange:["0deg","360deg"]})}]};return jsx(View,{style:t.wrap,accessible:true,accessibilityState:{busy:true},accessibilityLabel:"Analyzing photo",children:n?jsxs(Fragment,{children:[jsx(Animated.View,{style:[t.ring,s]}),jsx(b,{style:t.statusLine,children:"Analyzing"})]}):null})}i(Bt,"AnalyzingIndicator");function Gn(e){let{color:t,space:r,radius:n,type:o}=e,a=o.weight.medium,s=r.xxl*2,l=r.xs;return StyleSheet.create({wrap:{flexGrow:1,alignItems:"center",justifyContent:"center",padding:r.xl,gap:r.md},ring:{width:s,height:s,borderRadius:n.pill,borderWidth:l,borderTopColor:t.success,borderRightColor:t.success,borderBottomColor:t.borderRingCream,borderLeftColor:t.borderRingCream},statusLine:{color:t.text.secondary,fontFamily:o.family,fontSize:o.size.subtitle,lineHeight:o.size.subtitle*o.lineHeight.snug,fontWeight:a,letterSpacing:o.letterSpacing.tight,textAlign:"center"}})}i(Gn,"makeStyles");var Zn=1100,eo=1.6,to=.35;function ro(e,t){let{color:r}=e;switch(t){case "low_risk":return {accent:r.success,wash:r.badgeBgPositive,label:"Low risk",pulse:false};case "concern":return {accent:r.cautionAccent,wash:r.badgeBgCaution,label:"Concern",pulse:true};case "uncertain":return {accent:r.text.muted,wash:r.badgeBgNeutral,label:"Uncertain",pulse:false}}}i(ro,"toneSpec");function Vt({riskTier:e}){let t=g(),r=useMemo(()=>no(t),[t]),n=useRef(new Animated.Value(0)).current,o=e===null?null:ro(t,e),a=o!==null&&o.pulse&&!t.motion.reducedMotion;return useEffect(()=>{if(!a)return;let s=Animated.loop(Animated.timing(n,{toValue:1,duration:Zn,easing:Easing.out(Easing.quad),useNativeDriver:true}));return s.start(),()=>s.stop()},[a,n]),o===null?null:jsxs(View,{style:[r.pill,{backgroundColor:o.wash}],accessible:true,accessibilityRole:"text",accessibilityLabel:o.label,children:[jsxs(View,{style:r.dotWrap,children:[a?jsx(Animated.View,{pointerEvents:"none",style:[r.dotRing,{backgroundColor:o.accent,opacity:n.interpolate({inputRange:[0,1],outputRange:[to,0]}),transform:[{scale:n.interpolate({inputRange:[0,1],outputRange:[1,eo]})}]}]}):null,jsx(View,{style:[r.dot,{backgroundColor:o.accent}]})]}),jsx(b,{style:[r.label,{color:o.accent}],children:o.label})]})}i(Vt,"SeverityBadge");function no(e){let{space:t,radius:r,type:n}=e,o=n.weight.bold,a=t.sm;return StyleSheet.create({pill:{alignSelf:"flex-start",flexDirection:"row",alignItems:"center",gap:t.sm,paddingHorizontal:t.md,paddingVertical:t.xs,borderRadius:r.pill},dotWrap:{width:a,height:a,alignItems:"center",justifyContent:"center"},dot:{width:a,height:a,borderRadius:r.pill},dotRing:{position:"absolute",width:a,height:a,borderRadius:r.pill},label:{fontFamily:n.family,fontSize:n.size.caption,lineHeight:n.size.caption*n.lineHeight.snug,fontWeight:o,letterSpacing:n.letterSpacing.eyebrow}})}i(no,"makeStyles");var Fe=120,Z=44;function io(e){let{petRef:t,region:r,style:n}=e;return jsx(jt,{capability:"photo",style:n,children:jsx(so,{petRef:t,region:r,style:n})})}i(io,"PhotoCheckup");function ao(e){return e.isBootstrapping||e.status==="capturing"||e.status==="analyzing"?"loading":e.status==="error"&&e.error?"error":e.status==="complete"&&e.result?"populated":e.gate.kind!=="accepted"?"gate":e.isCaptureAvailable?"empty":"native"}i(ao,"selectView");function so({petRef:e,region:t,style:r}){let n=me({petRef:e}),o=t??"skin",a=ao(n),s=useMemo(()=>()=>{n.capture(o);},[n,o]),l=g(),d=useMemo(()=>F(l),[l]);return jsxs(View,{style:[d.container,r],children:[jsx(View,{style:d.body,children:lo(a,n,s)}),jsx(Mt,{})]})}i(so,"PhotoBody");function lo(e,t,r){switch(e){case "loading":return jsx(Bt,{});case "native":return jsx(uo,{});case "empty":return jsx(co,{onCapture:r});case "error":return jsx(po,{error:t.error,onRetry:r});case "gate":return jsx(yo,{gate:t.gate,onRetry:t.reset});case "populated":return jsx(mo,{result:t.result});default:return j(e)}}i(lo,"renderView");function uo(){let e=g(),t=useMemo(()=>F(e),[e]);return jsx(View,{style:t.offstate,accessible:true,accessibilityRole:"text",accessibilityLabel:"Camera is unavailable",children:jsx(View,{style:t.emptyGlyph})})}i(uo,"NativeUnavailableState");function co({onCapture:e}){let t=g(),r=useMemo(()=>F(t),[t]);return jsx(View,{style:r.centered,accessible:true,accessibilityRole:"summary",accessibilityLabel:"Take a photo to check",children:jsx(Pressable,{onPress:e,accessibilityRole:"button",accessibilityLabel:"Take photo",style:r.scanButton,children:jsx(b,{style:r.scanLabel,children:"Take photo"})})})}i(co,"EmptyState");function po({error:e,onRetry:t}){let r=g(),n=useMemo(()=>F(r),[r]);return jsxs(View,{style:n.centered,accessible:true,accessibilityRole:"alert",children:[jsx(b,{style:n.errorText,children:e.displayMessage}),e.retryable?jsx(Pressable,{onPress:t,accessibilityRole:"button",accessibilityLabel:"Try again",style:n.primaryButton,children:jsx(b,{style:n.primaryLabel,children:"Try again"})}):null]})}i(po,"ErrorState");function yo({gate:e,onRetry:t}){let r=g(),n=useMemo(()=>F(r),[r]),o=fo(e);return jsxs(View,{style:n.centered,accessible:true,accessibilityRole:"summary",accessibilityLabel:o,children:[jsx(b,{style:n.guidanceText,children:o}),e.kind!=="species_unsupported"?jsx(Pressable,{onPress:t,accessibilityRole:"button",accessibilityLabel:"Try again",style:n.primaryButton,children:jsx(b,{style:n.primaryLabel,children:"Try again"})}):null]})}i(yo,"GateState");function fo(e){switch(e.kind){case "species_unsupported":return "Photo checks are coming soon for this pet.";case "pet_context_required":return "Choose a pet first, then take the photo.";case "retake":return "That photo was hard to read. Please take another.";case "accepted":return "";default:return j(e)}}i(fo,"gateMessage");function mo({result:e}){let t=g(),r=useMemo(()=>F(t),[t]),n=e.subtypeDisplay??e.diseaseName??e.primaryLabel,o=bo(t,e.riskTier);return jsxs(ScrollView,{style:r.list,contentContainerStyle:[r.listContent,{backgroundColor:o}],accessibilityLabel:"Photo checkup result",children:[jsx(b,{style:r.resultHeadline,children:n}),jsx(Vt,{riskTier:e.riskTier}),e.diseaseFraming?jsx(b,{style:r.summaryText,children:e.diseaseFraming}):null,e.copyShort?jsx(b,{style:r.summaryText,children:e.copyShort}):null,e.interpretation?jsxs(View,{style:r.interpretationBlock,children:[jsx(b,{style:r.summaryText,children:e.interpretation.summary}),jsx(b,{style:r.disclaimerText,children:e.interpretation.disclaimer})]}):null]})}i(mo,"ResultState");function bo(e,t){switch(t){case "low_risk":return e.color.checkupCardLowRiskBg;case "concern":return e.color.checkupCardConcernBg;default:return e.color.surfaceCream}}i(bo,"resultCardTint");function F(e){let{color:t,space:r,radius:n,type:o}=e,a=o.weight.bold,s=o.weight.medium,l=o.weight.regular,d=e.brightness==="dark"?t.onAccent:t.brand;return StyleSheet.create({container:{flex:1,minHeight:Fe,backgroundColor:t.background},body:{flex:1},centered:{minHeight:Fe,flexGrow:1,alignItems:"center",justifyContent:"center",padding:r.xl},offstate:{minHeight:Fe,alignItems:"center",justifyContent:"center",padding:r.xl},emptyGlyph:{width:r.xxl,height:r.xxl,borderRadius:n.pill,backgroundColor:t.surfaceSubtle,borderWidth:StyleSheet.hairlineWidth,borderColor:t.brandBorder},scanButton:{minHeight:Z,minWidth:Z,alignItems:"center",justifyContent:"center",paddingHorizontal:r.xl,paddingVertical:r.md,borderRadius:n.pill,backgroundColor:t.scanButtonPink,shadowColor:t.brand,shadowOpacity:.18,shadowRadius:n.xs,shadowOffset:{width:0,height:r.xs},elevation:3},scanLabel:{color:d,fontFamily:o.family,fontSize:o.size.subtitle,lineHeight:o.size.subtitle*o.lineHeight.snug,fontWeight:a,letterSpacing:o.letterSpacing.tight},primaryButton:{minHeight:Z,minWidth:Z,alignItems:"center",justifyContent:"center",paddingHorizontal:r.xl,paddingVertical:r.sm,borderRadius:n.pill,backgroundColor:t.brand},primaryLabel:{color:t.onPrimary,fontFamily:o.family,fontSize:o.size.body,lineHeight:o.size.body*o.lineHeight.snug,fontWeight:s},errorText:{color:t.danger,fontFamily:o.family,fontSize:o.size.body,lineHeight:o.size.body*o.lineHeight.relaxed,textAlign:"center",marginBottom:r.md},guidanceText:{color:t.text.muted,fontFamily:o.family,fontSize:o.size.body,lineHeight:o.size.body*o.lineHeight.relaxed,textAlign:"center",marginBottom:r.md},list:{flex:1},listContent:{padding:r.lg,gap:r.sm,borderRadius:n.lg,borderWidth:StyleSheet.hairlineWidth,borderColor:t.brandBorder,shadowColor:t.brand,shadowOpacity:.08,shadowRadius:n.xs,shadowOffset:{width:0,height:r.xs},elevation:2},resultHeadline:{color:t.text.secondary,fontFamily:o.family,fontSize:o.size.verdict,lineHeight:o.size.verdict*o.lineHeight.tight,fontWeight:a,letterSpacing:o.letterSpacing.tight},summaryText:{color:t.text.primary,fontFamily:o.family,fontSize:o.size.body,lineHeight:o.size.body*o.lineHeight.relaxed,fontWeight:l},interpretationBlock:{marginTop:r.sm,paddingTop:r.sm,gap:r.xs,borderTopWidth:StyleSheet.hairlineWidth,borderTopColor:t.borderHairlineGreen},disclaimerText:{color:t.text.muted,fontFamily:o.family,fontSize:o.size.caption,lineHeight:o.size.caption*o.lineHeight.normal,fontWeight:l}})}i(F,"makeStyles");export{io as PhotoCheckup,me as usePhotoCheckup};
|