@absolutejs/voice 0.0.22-beta.400 → 0.0.22-beta.402
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/callDebugger.d.ts +58 -0
- package/dist/client/index.js +25 -11
- package/dist/client/sessionSnapshotWidget.d.ts +5 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +12606 -12482
- package/dist/react/index.js +23 -9
- package/dist/sessionSnapshot.d.ts +11 -0
- package/dist/svelte/index.js +23 -9
- package/dist/vue/index.js +23 -9
- package/package.json +1 -1
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Elysia } from 'elysia';
|
|
2
|
+
import { type VoiceFailureReplayReport, type VoiceOperationsRecord, type VoiceOperationsRecordOptions } from './operationsRecord';
|
|
3
|
+
import { type VoiceSessionSnapshot, type VoiceSessionSnapshotRouteSource } from './sessionSnapshot';
|
|
4
|
+
export type VoiceCallDebuggerReport = {
|
|
5
|
+
checkedAt: number;
|
|
6
|
+
failureReplay: VoiceFailureReplayReport;
|
|
7
|
+
incidentMarkdown: string;
|
|
8
|
+
operationsRecord: VoiceOperationsRecord;
|
|
9
|
+
sessionId: string;
|
|
10
|
+
snapshot: VoiceSessionSnapshot;
|
|
11
|
+
status: 'failed' | 'healthy' | 'warning';
|
|
12
|
+
};
|
|
13
|
+
export type VoiceCallDebuggerRoutesOptions = Omit<VoiceOperationsRecordOptions, 'sessionId'> & {
|
|
14
|
+
headers?: HeadersInit;
|
|
15
|
+
htmlPath?: false | string;
|
|
16
|
+
incidentPath?: false | string;
|
|
17
|
+
name?: string;
|
|
18
|
+
operationsRecordHref?: string | ((input: {
|
|
19
|
+
sessionId: string;
|
|
20
|
+
}) => string | undefined);
|
|
21
|
+
path?: string;
|
|
22
|
+
render?: (report: VoiceCallDebuggerReport) => string | Promise<string>;
|
|
23
|
+
snapshot?: VoiceSessionSnapshotRouteSource;
|
|
24
|
+
title?: string;
|
|
25
|
+
};
|
|
26
|
+
export declare const buildVoiceCallDebuggerReport: (options: VoiceCallDebuggerRoutesOptions, input: {
|
|
27
|
+
request: Request;
|
|
28
|
+
sessionId: string;
|
|
29
|
+
}) => Promise<VoiceCallDebuggerReport>;
|
|
30
|
+
export declare const renderVoiceCallDebuggerHTML: (report: VoiceCallDebuggerReport, options?: Pick<VoiceCallDebuggerRoutesOptions, "title">) => string;
|
|
31
|
+
export declare const createVoiceCallDebuggerRoutes: (options: VoiceCallDebuggerRoutesOptions) => Elysia<"", {
|
|
32
|
+
decorator: {};
|
|
33
|
+
store: {};
|
|
34
|
+
derive: {};
|
|
35
|
+
resolve: {};
|
|
36
|
+
}, {
|
|
37
|
+
typebox: {};
|
|
38
|
+
error: {};
|
|
39
|
+
}, {
|
|
40
|
+
schema: {};
|
|
41
|
+
standaloneSchema: {};
|
|
42
|
+
macro: {};
|
|
43
|
+
macroFn: {};
|
|
44
|
+
parser: {};
|
|
45
|
+
response: {};
|
|
46
|
+
}, {}, {
|
|
47
|
+
derive: {};
|
|
48
|
+
resolve: {};
|
|
49
|
+
schema: {};
|
|
50
|
+
standaloneSchema: {};
|
|
51
|
+
response: {};
|
|
52
|
+
}, {
|
|
53
|
+
derive: {};
|
|
54
|
+
resolve: {};
|
|
55
|
+
schema: {};
|
|
56
|
+
standaloneSchema: {};
|
|
57
|
+
response: {};
|
|
58
|
+
}>;
|
package/dist/client/index.js
CHANGED
|
@@ -3071,6 +3071,7 @@ var DEFAULT_TITLE = "Session Snapshot";
|
|
|
3071
3071
|
var DEFAULT_DESCRIPTION = "Portable call artifact with media graph, provider routing, proof, quality, and telephony evidence.";
|
|
3072
3072
|
var DEFAULT_DOWNLOAD_LABEL = "Download snapshot";
|
|
3073
3073
|
var escapeHtml = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
3074
|
+
var formatStatus = (status) => status ?? "n/a";
|
|
3074
3075
|
var createVoiceSessionSnapshotViewModel = (state, options = {}) => {
|
|
3075
3076
|
const snapshot = state.snapshot;
|
|
3076
3077
|
const failedProofs = snapshot?.proofSummary.failed ?? 0;
|
|
@@ -3078,7 +3079,13 @@ var createVoiceSessionSnapshotViewModel = (state, options = {}) => {
|
|
|
3078
3079
|
const timingWarnings = snapshot?.media.reduce((total, media) => total + media.report.timing.overBudgetFrames, 0) ?? 0;
|
|
3079
3080
|
const backpressureDrops = snapshot?.media.reduce((total, media) => total + media.report.backpressure.droppedFrames, 0) ?? 0;
|
|
3080
3081
|
const qualityWarnings = snapshot?.quality.filter((quality) => quality.status !== "pass").length ?? 0;
|
|
3082
|
+
const artifactWarnings = snapshot?.artifacts.filter((artifact) => artifact.status !== undefined && artifact.status !== "pass").length ?? 0;
|
|
3081
3083
|
return {
|
|
3084
|
+
artifacts: snapshot?.artifacts.map((artifact) => ({
|
|
3085
|
+
href: artifact.href,
|
|
3086
|
+
label: artifact.label,
|
|
3087
|
+
status: formatStatus(artifact.status)
|
|
3088
|
+
})) ?? [],
|
|
3082
3089
|
description: options.description ?? DEFAULT_DESCRIPTION,
|
|
3083
3090
|
error: state.error,
|
|
3084
3091
|
isLoading: state.isLoading,
|
|
@@ -3090,6 +3097,8 @@ var createVoiceSessionSnapshotViewModel = (state, options = {}) => {
|
|
|
3090
3097
|
{ label: "Backpressure drops", value: String(backpressureDrops) },
|
|
3091
3098
|
{ label: "Proof failures", value: String(failedProofs) },
|
|
3092
3099
|
{ label: "Quality warnings", value: String(qualityWarnings) },
|
|
3100
|
+
{ label: "Debug artifacts", value: String(snapshot.artifacts.length) },
|
|
3101
|
+
{ label: "Artifact warnings", value: String(artifactWarnings) },
|
|
3093
3102
|
{
|
|
3094
3103
|
label: "Provider routing",
|
|
3095
3104
|
value: String(snapshot.providerRoutingEvents.length)
|
|
@@ -3111,6 +3120,10 @@ var renderVoiceSessionSnapshotHTML = (state, options = {}) => {
|
|
|
3111
3120
|
<dt>${escapeHtml(row.label)}</dt>
|
|
3112
3121
|
<dd>${escapeHtml(row.value)}</dd>
|
|
3113
3122
|
</div>`).join("")}</dl>` : '<p class="absolute-voice-session-snapshot__empty">Load a session snapshot to see support diagnostics.</p>';
|
|
3123
|
+
const artifacts = model.artifacts.length ? `<div class="absolute-voice-session-snapshot__artifacts">${model.artifacts.map((artifact) => {
|
|
3124
|
+
const body = `<strong>${escapeHtml(artifact.label)}</strong><span>${escapeHtml(artifact.status)}</span>`;
|
|
3125
|
+
return artifact.href ? `<a href="${escapeHtml(artifact.href)}">${body}</a>` : `<div>${body}</div>`;
|
|
3126
|
+
}).join("")}</div>` : "";
|
|
3114
3127
|
return `<section class="absolute-voice-session-snapshot absolute-voice-session-snapshot--${escapeHtml(model.status)}">
|
|
3115
3128
|
<header class="absolute-voice-session-snapshot__header">
|
|
3116
3129
|
<span class="absolute-voice-session-snapshot__eyebrow">${escapeHtml(model.title)}</span>
|
|
@@ -3119,6 +3132,7 @@ var renderVoiceSessionSnapshotHTML = (state, options = {}) => {
|
|
|
3119
3132
|
<p class="absolute-voice-session-snapshot__description">${escapeHtml(model.description)}</p>
|
|
3120
3133
|
${model.showDownload ? `<button class="absolute-voice-session-snapshot__download" data-absolute-voice-session-snapshot-download type="button">${escapeHtml(options.downloadLabel ?? DEFAULT_DOWNLOAD_LABEL)}</button>` : ""}
|
|
3121
3134
|
${rows}
|
|
3135
|
+
${artifacts}
|
|
3122
3136
|
${model.error ? `<p class="absolute-voice-session-snapshot__error">${escapeHtml(model.error)}</p>` : ""}
|
|
3123
3137
|
</section>`;
|
|
3124
3138
|
};
|
|
@@ -4020,7 +4034,7 @@ var DEFAULT_LINKS = [
|
|
|
4020
4034
|
{ href: "/api/voice/vapi-coverage", label: "Coverage JSON" }
|
|
4021
4035
|
];
|
|
4022
4036
|
var escapeHtml3 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
4023
|
-
var
|
|
4037
|
+
var formatStatus2 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
|
|
4024
4038
|
var surfaceDetail2 = (surface) => {
|
|
4025
4039
|
if (surface.status === "pass") {
|
|
4026
4040
|
return surface.replacement;
|
|
@@ -4059,7 +4073,7 @@ var renderVoicePlatformCoverageHTML = (snapshot, options = {}) => {
|
|
|
4059
4073
|
const surfaces = model.surfaces.length ? `<div class="absolute-voice-platform-coverage__surfaces">${model.surfaces.map((surface) => `<article class="absolute-voice-platform-coverage__surface absolute-voice-platform-coverage__surface--${escapeHtml3(surface.status)}">
|
|
4060
4074
|
<header>
|
|
4061
4075
|
<strong>${escapeHtml3(surface.label)}</strong>
|
|
4062
|
-
<span>${escapeHtml3(
|
|
4076
|
+
<span>${escapeHtml3(formatStatus2(surface.status))}</span>
|
|
4063
4077
|
</header>
|
|
4064
4078
|
<p>${escapeHtml3(surface.detail)}</p>
|
|
4065
4079
|
<small>${surface.evidence.filter((item) => item.ok).length}/${surface.evidence.length} evidence checks passing</small>
|
|
@@ -10303,7 +10317,7 @@ var DEFAULT_TITLE11 = "Voice Providers";
|
|
|
10303
10317
|
var DEFAULT_DESCRIPTION11 = "Live provider health, fallback counts, latency, and suppression state from your self-hosted trace store.";
|
|
10304
10318
|
var escapeHtml19 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
10305
10319
|
var formatProvider = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
|
|
10306
|
-
var
|
|
10320
|
+
var formatStatus3 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
|
|
10307
10321
|
var formatLatency = (value) => typeof value === "number" ? `${value}ms` : "No samples";
|
|
10308
10322
|
var formatSuppression = (value) => typeof value === "number" ? `${Math.ceil(value / 1000)}s` : "None";
|
|
10309
10323
|
var getProviderDetail = (provider) => {
|
|
@@ -10360,7 +10374,7 @@ var renderVoiceProviderStatusHTML = (snapshot, options = {}) => {
|
|
|
10360
10374
|
const providers = model.providers.length ? `<div class="absolute-voice-provider-status__providers">${model.providers.map((provider) => `<article class="absolute-voice-provider-status__provider absolute-voice-provider-status__provider--${escapeHtml19(provider.status)}">
|
|
10361
10375
|
<header>
|
|
10362
10376
|
<strong>${escapeHtml19(provider.label)}</strong>
|
|
10363
|
-
<span>${escapeHtml19(
|
|
10377
|
+
<span>${escapeHtml19(formatStatus3(provider.status))}</span>
|
|
10364
10378
|
</header>
|
|
10365
10379
|
<p>${escapeHtml19(provider.detail)}</p>
|
|
10366
10380
|
<dl>${provider.rows.map((row) => `<div>
|
|
@@ -10421,7 +10435,7 @@ var DEFAULT_DESCRIPTION12 = "Configured, selected, and healthy voice providers f
|
|
|
10421
10435
|
var escapeHtml20 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
10422
10436
|
var formatProvider2 = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
|
|
10423
10437
|
var formatKind2 = (kind) => kind.toUpperCase();
|
|
10424
|
-
var
|
|
10438
|
+
var formatStatus4 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
|
|
10425
10439
|
var getCapabilityDetail = (capability) => {
|
|
10426
10440
|
if (!capability.configured) {
|
|
10427
10441
|
return "Not configured in this deployment.";
|
|
@@ -10447,7 +10461,7 @@ var createVoiceProviderCapabilitiesViewModel = (snapshot, options = {}) => {
|
|
|
10447
10461
|
detail: getCapabilityDetail(capability),
|
|
10448
10462
|
label: `${formatProvider2(capability.provider)} ${formatKind2(capability.kind)}`,
|
|
10449
10463
|
rows: [
|
|
10450
|
-
{ label: "Status", value:
|
|
10464
|
+
{ label: "Status", value: formatStatus4(capability.status) },
|
|
10451
10465
|
{ label: "Selected", value: capability.selected ? "Yes" : "No" },
|
|
10452
10466
|
{ label: "Model", value: capability.model ?? "Default" },
|
|
10453
10467
|
{
|
|
@@ -10476,7 +10490,7 @@ var renderVoiceProviderCapabilitiesHTML = (snapshot, options = {}) => {
|
|
|
10476
10490
|
const capabilities = model.capabilities.length ? `<div class="absolute-voice-provider-capabilities__providers">${model.capabilities.map((capability) => `<article class="absolute-voice-provider-capabilities__provider absolute-voice-provider-capabilities__provider--${escapeHtml20(capability.status)}">
|
|
10477
10491
|
<header>
|
|
10478
10492
|
<strong>${escapeHtml20(capability.label)}</strong>
|
|
10479
|
-
<span>${escapeHtml20(
|
|
10493
|
+
<span>${escapeHtml20(formatStatus4(capability.status))}</span>
|
|
10480
10494
|
</header>
|
|
10481
10495
|
<p>${escapeHtml20(capability.detail)}</p>
|
|
10482
10496
|
<dl>${capability.rows.map((row) => `<div>
|
|
@@ -10536,7 +10550,7 @@ var DEFAULT_TITLE13 = "Provider Contracts";
|
|
|
10536
10550
|
var DEFAULT_DESCRIPTION13 = "Production contract coverage for provider env, latency, fallback, streaming, and capabilities.";
|
|
10537
10551
|
var escapeHtml21 = (value) => value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
10538
10552
|
var formatProvider3 = (provider) => provider.split(/[-_\s]+/).filter(Boolean).map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ") || provider;
|
|
10539
|
-
var
|
|
10553
|
+
var formatStatus5 = (status) => status.split("-").map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`).join(" ");
|
|
10540
10554
|
var contractDetail = (row) => {
|
|
10541
10555
|
const failing = row.checks.filter((check) => check.status !== "pass");
|
|
10542
10556
|
if (failing.length === 0) {
|
|
@@ -10555,12 +10569,12 @@ var createVoiceProviderContractsViewModel = (snapshot, options = {}) => {
|
|
|
10555
10569
|
label: check.remediation?.label ?? check.label
|
|
10556
10570
|
})),
|
|
10557
10571
|
rows: [
|
|
10558
|
-
{ label: "Status", value:
|
|
10572
|
+
{ label: "Status", value: formatStatus5(row.status) },
|
|
10559
10573
|
{ label: "Selected", value: row.selected ? "Yes" : "No" },
|
|
10560
10574
|
{ label: "Configured", value: row.configured ? "Yes" : "No" },
|
|
10561
10575
|
{
|
|
10562
10576
|
label: "Checks",
|
|
10563
|
-
value: row.checks.map((check) => `${check.label}: ${
|
|
10577
|
+
value: row.checks.map((check) => `${check.label}: ${formatStatus5(check.status)}`).join(", ")
|
|
10564
10578
|
}
|
|
10565
10579
|
]
|
|
10566
10580
|
}));
|
|
@@ -10581,7 +10595,7 @@ var renderVoiceProviderContractsHTML = (snapshot, options = {}) => {
|
|
|
10581
10595
|
const rows = model.rows.length ? `<div class="absolute-voice-provider-contracts__rows">${model.rows.map((row) => `<article class="absolute-voice-provider-contracts__row absolute-voice-provider-contracts__row--${escapeHtml21(row.status)}">
|
|
10582
10596
|
<header>
|
|
10583
10597
|
<strong>${escapeHtml21(row.label)}</strong>
|
|
10584
|
-
<span>${escapeHtml21(
|
|
10598
|
+
<span>${escapeHtml21(formatStatus5(row.status))}</span>
|
|
10585
10599
|
</header>
|
|
10586
10600
|
<p>${escapeHtml21(row.detail)}</p>
|
|
10587
10601
|
${row.remediations.length ? `<ul class="absolute-voice-provider-contracts__remediations">${row.remediations.map((remediation) => `<li>${remediation.href ? `<a href="${escapeHtml21(remediation.href)}">${escapeHtml21(remediation.label)}</a>` : `<strong>${escapeHtml21(remediation.label)}</strong>`}<span>${escapeHtml21(remediation.detail)}</span></li>`).join("")}</ul>` : ""}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { type VoiceSessionSnapshotClientOptions, type VoiceSessionSnapshotClientState } from './sessionSnapshot';
|
|
2
2
|
export type VoiceSessionSnapshotViewModel = {
|
|
3
|
+
artifacts: Array<{
|
|
4
|
+
href?: string;
|
|
5
|
+
label: string;
|
|
6
|
+
status: string;
|
|
7
|
+
}>;
|
|
3
8
|
description: string;
|
|
4
9
|
error: string | null;
|
|
5
10
|
isLoading: boolean;
|
package/dist/index.d.ts
CHANGED
|
@@ -33,8 +33,10 @@ export type { VoicePlatformCoverageAssertionInput, VoicePlatformCoverageAssertio
|
|
|
33
33
|
export { assertVoiceProofTrendEvidence, appendVoiceRealCallProfileRecoveryEvidence, buildEmptyVoiceProofTrendReport, buildVoiceProofTrendProfileSummaries, buildVoiceProofTrendRecommendationReport, buildVoiceProofTrendReportFromRealCallProfiles, buildVoiceProofTrendReport, buildVoiceRealCallProfileEvidenceFromTraceEvents, buildVoiceRealCallProfileDefaults, buildVoiceRealCallProfileHistoryReport, buildVoiceRealCallProfileReadinessCheck, buildVoiceRealCallProfileRecoveryJobHistoryCheck, buildVoiceRealCallProfileRecoveryActions, createVoiceInMemoryRealCallProfileRecoveryJobStore, createVoiceRealCallProfileTraceCollector, createVoiceSQLiteRealCallProfileRecoveryJobStore, createVoiceProofTrendRecommendationRoutes, createVoiceProofTrendRoutes, createVoiceRealCallProfileHistoryRoutes, createVoiceRealCallProfileRecoveryActionRoutes, DEFAULT_VOICE_PROOF_TREND_PROFILE_DEFINITIONS, DEFAULT_VOICE_PROOF_TRENDS_MAX_AGE_MS, evaluateVoiceProofTrendEvidence, formatVoiceProofTrendAge, loadVoiceRealCallProfileEvidenceFromTraceStore, normalizeVoiceProofTrendReport, readVoiceProofTrendReportFile, renderVoiceProofTrendRecommendationHTML, renderVoiceProofTrendRecommendationMarkdown, renderVoiceRealCallProfileHistoryHTML, renderVoiceRealCallProfileHistoryMarkdown, runVoiceRealCallProfileRecoveryLoop, resolveVoiceRealCallProfileProviderRoute } from './proofTrends';
|
|
34
34
|
export { createVoiceEvidenceAssertion, createVoiceProofAssertion, summarizeVoiceProofAssertions } from './proofAssertions';
|
|
35
35
|
export { buildVoiceSessionSnapshot, buildVoiceSessionSnapshotStatus, createVoiceSessionSnapshotRoutes, parseVoiceSessionSnapshot } from './sessionSnapshot';
|
|
36
|
+
export { buildVoiceCallDebuggerReport, createVoiceCallDebuggerRoutes, renderVoiceCallDebuggerHTML } from './callDebugger';
|
|
36
37
|
export type { VoiceEvidenceAssertionInput, VoiceProofAssertionInput, VoiceProofAssertionResult, VoiceProofAssertionSummary } from './proofAssertions';
|
|
37
|
-
export type { VoiceSessionSnapshot, VoiceSessionSnapshotInput, VoiceSessionSnapshotQualityEvidence, VoiceSessionSnapshotRoutesOptions, VoiceSessionSnapshotRouteSource, VoiceSessionSnapshotRouteSourceInput, VoiceSessionSnapshotStatus } from './sessionSnapshot';
|
|
38
|
+
export type { VoiceSessionSnapshot, VoiceSessionSnapshotArtifact, VoiceSessionSnapshotArtifactKind, VoiceSessionSnapshotInput, VoiceSessionSnapshotQualityEvidence, VoiceSessionSnapshotRoutesOptions, VoiceSessionSnapshotRouteSource, VoiceSessionSnapshotRouteSourceInput, VoiceSessionSnapshotStatus } from './sessionSnapshot';
|
|
39
|
+
export type { VoiceCallDebuggerReport, VoiceCallDebuggerRoutesOptions } from './callDebugger';
|
|
38
40
|
export { fetchVoiceProofTarget, getVoiceProofTargetLogicalFailure, mapVoiceProofTargetsWithConcurrency, runVoiceCommandProofTarget, runVoiceCommandProofTargets, runVoiceProofTargets } from './proofRunner';
|
|
39
41
|
export type { VoiceCommandProofExecutionResult, VoiceCommandProofTarget, VoiceCommandProofTargetResult, VoiceCommandProofTargetRunnerOptions, VoiceCommandProofTargetRunOptions, VoiceProofTarget, VoiceProofTargetMethod, VoiceProofTargetResult, VoiceProofTargetRunnerOptions, VoiceProofTargetRunOptions } from './proofRunner';
|
|
40
42
|
export { applyVoiceProfileSwitchGuard, buildVoiceProfileSwitchReadinessReport, buildVoiceProfileSwitchLiveDecisionReport, createVoiceProfileSwitchLiveDecisionRoutes, createVoiceProfileSwitchPolicyProofRoutes, createVoiceProfileSwitchReadinessRoutes, recommendVoiceProfileSwitch, renderVoiceProfileSwitchLiveDecisionHTML, renderVoiceProfileSwitchPolicyProofHTML, renderVoiceProfileSwitchReadinessHTML, runVoiceProfileSwitchPolicyProof } from './profileSwitchRecommendation';
|