@nexalink/carecompass-sdk 0.4.0-beta.15 → 0.6.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/README.md +285 -3
- package/dist/cli.js +0 -0
- package/dist/client.d.ts +32 -2
- package/dist/client.js +45 -3
- package/dist/compliance.d.ts +69 -0
- package/dist/compliance.js +121 -0
- package/dist/component.d.ts +24 -2
- package/dist/component.js +63 -4
- package/dist/core.d.ts +19 -2
- package/dist/core.js +140 -8
- package/dist/index.d.ts +11 -5
- package/dist/index.js +5 -2
- package/dist/markdown.d.ts +2 -1
- package/dist/mount.d.ts +4 -1
- package/dist/mount.js +2 -2
- package/dist/rcm.d.ts +95 -0
- package/dist/rcm.js +288 -0
- package/dist/react.d.ts +2 -0
- package/dist/react.js +1 -0
- package/dist/schema.d.ts +49 -0
- package/dist/schema.js +36 -0
- package/dist/telemetry.d.ts +3 -2
- package/dist/telemetry.js +127 -114
- package/dist/ui.d.ts +2 -2
- package/dist/ui.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +2 -2
- package/dist/web-component.d.ts +13 -0
- package/dist/web-component.js +236 -0
- package/package.json +30 -14
- package/qa-fixtures/DEMO-ENC-1042-rcm-policy.md +27 -0
package/dist/telemetry.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { careCompassApiBaseUrlSchema, } from "./schema.js";
|
|
1
2
|
import { CARECOMPASS_COMPONENT_VERSION, CARECOMPASS_SDK_VERSION, CARECOMPASS_TELEMETRY_CONTRACT_VERSION, } from "./version.js";
|
|
2
3
|
export const CARECOMPASS_TELEMETRY_EVENTS = {
|
|
3
4
|
SDK_INITIALIZED: "carecompass_sdk_initialized",
|
|
@@ -62,39 +63,71 @@ export const CARECOMPASS_TELEMETRY_ERROR_CODES = {
|
|
|
62
63
|
CLI_COMMAND_UNKNOWN: "CLI_COMMAND_UNKNOWN",
|
|
63
64
|
UNKNOWN_ERROR: "UNKNOWN_ERROR",
|
|
64
65
|
};
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
const POSTHOG_HOSTNAMES = new Set(["us.i.posthog.com", "eu.i.posthog.com"]);
|
|
68
|
-
const CARECOMPASS_DEFAULT_POSTHOG_API_HOST = "https://us.i.posthog.com";
|
|
69
|
-
const CARECOMPASS_DEFAULT_POSTHOG_PROJECT_TOKEN = "phc_vYl3rpIu6Iz5vCeJUG62BBA3nlPCeF4RSYpCb8FjnOl";
|
|
66
|
+
const CARECOMPASS_DEFAULT_API_BASE_URL = "https://www.nexalink.care";
|
|
67
|
+
const CARECOMPASS_TELEMETRY_PATH = "/api/carecompass/telemetry";
|
|
70
68
|
const TELEMETRY_CAPTURE_TIMEOUT_MS = 1_000;
|
|
69
|
+
const TELEMETRY_EVENTS = new Set(Object.values(CARECOMPASS_TELEMETRY_EVENTS));
|
|
70
|
+
const TELEMETRY_ERROR_CODES = new Set(Object.values(CARECOMPASS_TELEMETRY_ERROR_CODES));
|
|
71
|
+
const FAILURE_CATEGORIES = new Set([
|
|
72
|
+
"authentication",
|
|
73
|
+
"auth_error",
|
|
74
|
+
"baa_not_signed",
|
|
75
|
+
"configuration",
|
|
76
|
+
"config_error",
|
|
77
|
+
"inference",
|
|
78
|
+
"model_error",
|
|
79
|
+
"network",
|
|
80
|
+
"network_error",
|
|
81
|
+
"permission_error",
|
|
82
|
+
"rate_limit",
|
|
83
|
+
"rate_limit_error",
|
|
84
|
+
"rendering",
|
|
85
|
+
"timeout",
|
|
86
|
+
"unknown",
|
|
87
|
+
]);
|
|
88
|
+
const AUTH_METHODS = new Set(["api_key", "env_var", "config_file"]);
|
|
89
|
+
const BAA_STATUSES = new Set(["signed", "pending", "not_required"]);
|
|
90
|
+
const COMMAND_NAMES = new Set([
|
|
91
|
+
"auth",
|
|
92
|
+
"completion",
|
|
93
|
+
"config",
|
|
94
|
+
"configure",
|
|
95
|
+
"doctor",
|
|
96
|
+
"embed",
|
|
97
|
+
"health",
|
|
98
|
+
"help",
|
|
99
|
+
"init",
|
|
100
|
+
"login",
|
|
101
|
+
"logout",
|
|
102
|
+
"query",
|
|
103
|
+
"setup",
|
|
104
|
+
"snippet",
|
|
105
|
+
"unknown_command",
|
|
106
|
+
"validate",
|
|
107
|
+
"version",
|
|
108
|
+
]);
|
|
109
|
+
const EXIT_REASONS = new Set([
|
|
110
|
+
"user_exit",
|
|
111
|
+
"error",
|
|
112
|
+
"timeout",
|
|
113
|
+
"signal",
|
|
114
|
+
]);
|
|
115
|
+
const AUTH_RESULTS = new Set(["success", "failure"]);
|
|
116
|
+
const KEY_SOURCES = new Set(["env_var", "config_file", "cli_flag"]);
|
|
71
117
|
const disabledTelemetry = {
|
|
72
118
|
enabled: false,
|
|
73
119
|
capture: () => undefined,
|
|
74
120
|
captureAsync: async () => undefined,
|
|
75
121
|
};
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (typeof value !== "string")
|
|
122
|
+
const approvedRelayBaseUrl = (value, environment) => {
|
|
123
|
+
const parsed = careCompassApiBaseUrlSchema.safeParse(value);
|
|
124
|
+
if (!parsed.success)
|
|
80
125
|
return null;
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
url.hostname === "127.0.0.1" ||
|
|
87
|
-
url.hostname === "[::1]";
|
|
88
|
-
if (isLocalhost) {
|
|
89
|
-
return environment === "development" ? value.replace(/\/+$/, "") : null;
|
|
90
|
-
}
|
|
91
|
-
return url.protocol === "https:" && POSTHOG_HOSTNAMES.has(url.hostname)
|
|
92
|
-
? value.replace(/\/+$/, "")
|
|
93
|
-
: null;
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
126
|
+
const url = new URL(parsed.data);
|
|
127
|
+
const isLocalhost = url.hostname === "localhost" ||
|
|
128
|
+
url.hostname === "127.0.0.1" ||
|
|
129
|
+
url.hostname === "[::1]";
|
|
130
|
+
return isLocalhost && environment !== "development" ? null : parsed.data;
|
|
98
131
|
};
|
|
99
132
|
export const careCompassTelemetryDurationBucket = (durationMs) => {
|
|
100
133
|
if (durationMs < 250)
|
|
@@ -107,117 +140,102 @@ export const careCompassTelemetryDurationBucket = (durationMs) => {
|
|
|
107
140
|
return "5_to_29_999_ms";
|
|
108
141
|
return "30_s_or_more";
|
|
109
142
|
};
|
|
110
|
-
const integerMs = (value) =>
|
|
111
|
-
|
|
143
|
+
const integerMs = (value) => typeof value === "number" && Number.isFinite(value)
|
|
144
|
+
? Math.max(0, Math.round(value))
|
|
145
|
+
: undefined;
|
|
146
|
+
const integerCount = (value) => typeof value === "number" && Number.isFinite(value)
|
|
147
|
+
? Math.max(0, Math.trunc(value))
|
|
148
|
+
: undefined;
|
|
149
|
+
const allowedValue = (value, values) => typeof value === "string" && values.has(value) ? value : undefined;
|
|
150
|
+
const allowedEnvironment = (value) => value === "development" || value === "staging" || value === "production"
|
|
151
|
+
? value
|
|
152
|
+
: "unknown";
|
|
112
153
|
function createTelemetry(context, options = {}) {
|
|
113
|
-
const projectToken = CARECOMPASS_DEFAULT_POSTHOG_PROJECT_TOKEN;
|
|
114
|
-
// Invalid identifiers disable telemetry instead of producing malformed events
|
|
115
|
-
// or sending data under an ambiguous deployment identity.
|
|
116
|
-
if (!DEPLOYMENT_ID_RE.test(context.deploymentId) ||
|
|
117
|
-
!DEPLOYMENT_ID_RE.test(context.distinctId) ||
|
|
118
|
-
!POSTHOG_PROJECT_TOKEN_RE.test(projectToken)) {
|
|
119
|
-
return disabledTelemetry;
|
|
120
|
-
}
|
|
121
154
|
const fetcher = options.fetcher ?? fetch;
|
|
122
155
|
const now = options.now ?? (() => new Date());
|
|
123
|
-
const
|
|
124
|
-
if (!
|
|
156
|
+
const relayBaseUrl = approvedRelayBaseUrl(context.apiBaseUrl, context.environment);
|
|
157
|
+
if (!relayBaseUrl)
|
|
125
158
|
return disabledTelemetry;
|
|
126
159
|
const captureAsync = async (event, captureOptions = {}) => {
|
|
160
|
+
if (!TELEMETRY_EVENTS.has(event))
|
|
161
|
+
return;
|
|
127
162
|
const eventTimestamp = now().toISOString();
|
|
163
|
+
const durationMs = integerMs(captureOptions.durationMs);
|
|
164
|
+
const failureCategory = captureOptions.failureCategory === null
|
|
165
|
+
? null
|
|
166
|
+
: allowedValue(captureOptions.failureCategory, FAILURE_CATEGORIES);
|
|
167
|
+
const errorCode = allowedValue(captureOptions.errorCode, TELEMETRY_ERROR_CODES);
|
|
168
|
+
const initDurationMs = integerMs(captureOptions.initDurationMs);
|
|
169
|
+
const authMethod = allowedValue(captureOptions.authMethod, AUTH_METHODS);
|
|
170
|
+
const baaStatus = allowedValue(captureOptions.baaStatus, BAA_STATUSES);
|
|
171
|
+
const commandName = allowedValue(captureOptions.commandName, COMMAND_NAMES);
|
|
172
|
+
const commandArgsCount = integerCount(captureOptions.commandArgsCount);
|
|
173
|
+
const commandDurationMs = integerMs(captureOptions.commandDurationMs);
|
|
174
|
+
const tokensUsed = captureOptions.tokensUsed === null
|
|
175
|
+
? null
|
|
176
|
+
: integerCount(captureOptions.tokensUsed);
|
|
177
|
+
const sessionDurationMs = integerMs(captureOptions.sessionDurationMs);
|
|
178
|
+
const commandsAttempted = integerCount(captureOptions.commandsAttempted);
|
|
179
|
+
const commandsSucceeded = integerCount(captureOptions.commandsSucceeded);
|
|
180
|
+
const commandsFailed = integerCount(captureOptions.commandsFailed);
|
|
181
|
+
const exitReason = allowedValue(captureOptions.exitReason, EXIT_REASONS);
|
|
182
|
+
const authResult = allowedValue(captureOptions.authResult, AUTH_RESULTS);
|
|
183
|
+
const keySource = allowedValue(captureOptions.keySource, KEY_SOURCES);
|
|
128
184
|
// Properties are allowlisted here by design. Do not add prompts, responses,
|
|
129
|
-
// user context, credentials,
|
|
185
|
+
// user context, credentials, raw exception messages, or stable identifiers.
|
|
130
186
|
const properties = {
|
|
131
187
|
$process_person_profile: false,
|
|
132
188
|
contract_version: CARECOMPASS_TELEMETRY_CONTRACT_VERSION,
|
|
133
189
|
sdk_version: CARECOMPASS_SDK_VERSION,
|
|
134
190
|
component_version: CARECOMPASS_COMPONENT_VERSION,
|
|
135
191
|
platform: context.platform,
|
|
136
|
-
environment: context.environment,
|
|
137
|
-
payer_id: context.payerId,
|
|
138
|
-
deployment_id: context.deploymentId,
|
|
192
|
+
environment: allowedEnvironment(context.environment),
|
|
139
193
|
event_timestamp: eventTimestamp,
|
|
140
|
-
...(
|
|
141
|
-
...(captureOptions.durationMs === undefined
|
|
194
|
+
...(durationMs === undefined
|
|
142
195
|
? {}
|
|
143
196
|
: {
|
|
144
|
-
duration_bucket: careCompassTelemetryDurationBucket(
|
|
197
|
+
duration_bucket: careCompassTelemetryDurationBucket(durationMs),
|
|
145
198
|
}),
|
|
146
|
-
...(
|
|
199
|
+
...(failureCategory === undefined
|
|
147
200
|
? {}
|
|
148
|
-
: { failure_category:
|
|
149
|
-
...(
|
|
150
|
-
|
|
151
|
-
: {}),
|
|
152
|
-
...(captureOptions.initDurationMs === undefined
|
|
201
|
+
: { failure_category: failureCategory }),
|
|
202
|
+
...(errorCode ? { error_code: errorCode } : {}),
|
|
203
|
+
...(initDurationMs === undefined
|
|
153
204
|
? {}
|
|
154
|
-
: { init_duration_ms:
|
|
155
|
-
...(
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
...(
|
|
159
|
-
? { baa_status: captureOptions.baaStatus }
|
|
160
|
-
: {}),
|
|
161
|
-
...(captureOptions.commandName
|
|
162
|
-
? { command_name: captureOptions.commandName }
|
|
163
|
-
: {}),
|
|
164
|
-
...(captureOptions.commandArgsCount === undefined
|
|
205
|
+
: { init_duration_ms: initDurationMs }),
|
|
206
|
+
...(authMethod ? { auth_method: authMethod } : {}),
|
|
207
|
+
...(baaStatus ? { baa_status: baaStatus } : {}),
|
|
208
|
+
...(commandName ? { command_name: commandName } : {}),
|
|
209
|
+
...(commandArgsCount === undefined
|
|
165
210
|
? {}
|
|
166
|
-
: {
|
|
167
|
-
|
|
168
|
-
}),
|
|
169
|
-
...(captureOptions.commandDurationMs === undefined
|
|
211
|
+
: { command_args_count: commandArgsCount }),
|
|
212
|
+
...(commandDurationMs === undefined
|
|
170
213
|
? {}
|
|
171
|
-
: {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
...(captureOptions.tokensUsed === undefined
|
|
214
|
+
: { command_duration_ms: commandDurationMs }),
|
|
215
|
+
...(tokensUsed === undefined ? {} : { tokens_used: tokensUsed }),
|
|
216
|
+
...(sessionDurationMs === undefined
|
|
175
217
|
? {}
|
|
176
|
-
: {
|
|
177
|
-
|
|
178
|
-
? null
|
|
179
|
-
: integerCount(captureOptions.tokensUsed),
|
|
180
|
-
}),
|
|
181
|
-
...(captureOptions.modelId === undefined
|
|
218
|
+
: { session_duration_ms: sessionDurationMs }),
|
|
219
|
+
...(commandsAttempted === undefined
|
|
182
220
|
? {}
|
|
183
|
-
: {
|
|
184
|
-
...(
|
|
221
|
+
: { commands_attempted: commandsAttempted }),
|
|
222
|
+
...(commandsSucceeded === undefined
|
|
185
223
|
? {}
|
|
186
|
-
: {
|
|
187
|
-
|
|
188
|
-
}),
|
|
189
|
-
...(captureOptions.commandsAttempted === undefined
|
|
190
|
-
? {}
|
|
191
|
-
: {
|
|
192
|
-
commands_attempted: integerCount(captureOptions.commandsAttempted),
|
|
193
|
-
}),
|
|
194
|
-
...(captureOptions.commandsSucceeded === undefined
|
|
195
|
-
? {}
|
|
196
|
-
: {
|
|
197
|
-
commands_succeeded: integerCount(captureOptions.commandsSucceeded),
|
|
198
|
-
}),
|
|
199
|
-
...(captureOptions.commandsFailed === undefined
|
|
224
|
+
: { commands_succeeded: commandsSucceeded }),
|
|
225
|
+
...(commandsFailed === undefined
|
|
200
226
|
? {}
|
|
201
|
-
: { commands_failed:
|
|
202
|
-
...(
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
...(captureOptions.authResult
|
|
206
|
-
? { auth_result: captureOptions.authResult }
|
|
207
|
-
: {}),
|
|
208
|
-
...(captureOptions.keySource
|
|
209
|
-
? { key_source: captureOptions.keySource }
|
|
210
|
-
: {}),
|
|
227
|
+
: { commands_failed: commandsFailed }),
|
|
228
|
+
...(exitReason ? { exit_reason: exitReason } : {}),
|
|
229
|
+
...(authResult ? { auth_result: authResult } : {}),
|
|
230
|
+
...(keySource ? { key_source: keySource } : {}),
|
|
211
231
|
};
|
|
212
232
|
const controller = new AbortController();
|
|
213
233
|
const timeout = setTimeout(() => controller.abort(), TELEMETRY_CAPTURE_TIMEOUT_MS);
|
|
214
234
|
try {
|
|
215
|
-
await fetcher(`${
|
|
235
|
+
await fetcher(`${relayBaseUrl}${CARECOMPASS_TELEMETRY_PATH}`, {
|
|
216
236
|
method: "POST",
|
|
217
237
|
headers: { "content-type": "application/json" },
|
|
218
238
|
body: JSON.stringify({
|
|
219
|
-
api_key: projectToken,
|
|
220
|
-
distinct_id: context.distinctId,
|
|
221
239
|
event,
|
|
222
240
|
properties,
|
|
223
241
|
timestamp: eventTimestamp,
|
|
@@ -239,24 +257,19 @@ function createTelemetry(context, options = {}) {
|
|
|
239
257
|
};
|
|
240
258
|
}
|
|
241
259
|
export function createCareCompassTelemetry(config, options = {}) {
|
|
242
|
-
const deploymentId = `${config.payerId}-${config.environment}-web`;
|
|
243
260
|
return createTelemetry({
|
|
244
|
-
|
|
245
|
-
distinctId: deploymentId,
|
|
261
|
+
apiBaseUrl: config.apiBaseUrl,
|
|
246
262
|
environment: config.environment,
|
|
247
|
-
payerId: config.payerId,
|
|
248
263
|
platform: "web",
|
|
249
264
|
}, options);
|
|
250
265
|
}
|
|
251
266
|
export function createCareCompassCliTelemetry(sessionId, context = {}, options = {}) {
|
|
267
|
+
// Keep the public signature stable, but session IDs must never reach telemetry.
|
|
268
|
+
void sessionId;
|
|
252
269
|
const environment = context.environment ?? "unknown";
|
|
253
|
-
const payerId = context.payerId ?? "anonymous";
|
|
254
270
|
return createTelemetry({
|
|
255
|
-
|
|
256
|
-
deploymentId: `${payerId}-${environment}-cli`,
|
|
257
|
-
distinctId: sessionId,
|
|
271
|
+
apiBaseUrl: context.apiBaseUrl ?? CARECOMPASS_DEFAULT_API_BASE_URL,
|
|
258
272
|
environment,
|
|
259
|
-
payerId,
|
|
260
273
|
platform: "cli",
|
|
261
274
|
}, options);
|
|
262
275
|
}
|
package/dist/ui.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { CareCompassChat, CareCompassEmbed } from "./component.js";
|
|
2
|
-
export type { CareCompassChatProps } from "./component.js";
|
|
1
|
+
export { CareCompassChat, CareCompassEmbed, useCareCompass, } from "./component.js";
|
|
2
|
+
export type { CareCompassChatProps, CareCompassReactController, UseCareCompassOptions, } from "./component.js";
|
|
3
3
|
export { mountCareCompass } from "./mount.js";
|
package/dist/ui.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { CareCompassChat, CareCompassEmbed } from "./component.js";
|
|
1
|
+
export { CareCompassChat, CareCompassEmbed, useCareCompass, } from "./component.js";
|
|
2
2
|
export { mountCareCompass } from "./mount.js";
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export const CARECOMPASS_SDK_VERSION = "0.
|
|
1
|
+
export const CARECOMPASS_SDK_VERSION = "0.6.0"; // x-release-please-version
|
|
2
2
|
export const CARECOMPASS_COMPONENT_VERSION = "1.0.0";
|
|
3
|
-
export const CARECOMPASS_TELEMETRY_CONTRACT_VERSION = "1.
|
|
3
|
+
export const CARECOMPASS_TELEMETRY_CONTRACT_VERSION = "1.1.0";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CareCompassRuntimeTokenProvider } from "./client.js";
|
|
2
|
+
import { type CareCompassConfig } from "./schema.js";
|
|
3
|
+
export declare const CARECOMPASS_ELEMENT_TAG = "carecompass-chat";
|
|
4
|
+
export type CareCompassWebComponentElement = HTMLElement & {
|
|
5
|
+
config: CareCompassConfig | undefined;
|
|
6
|
+
runtimeTokenProvider: CareCompassRuntimeTokenProvider | undefined;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Register a framework-neutral embed backed directly by the headless core.
|
|
10
|
+
* Deferring the HTMLElement subclass creation keeps this entry point safe to
|
|
11
|
+
* import during server rendering.
|
|
12
|
+
*/
|
|
13
|
+
export declare function defineCareCompassElement(tagName?: string): CustomElementConstructor | undefined;
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { createCareCompassCore, } from "./core.js";
|
|
2
|
+
import { validateCareCompassConfig } from "./schema.js";
|
|
3
|
+
export const CARECOMPASS_ELEMENT_TAG = "carecompass-chat";
|
|
4
|
+
/**
|
|
5
|
+
* Register a framework-neutral embed backed directly by the headless core.
|
|
6
|
+
* Deferring the HTMLElement subclass creation keeps this entry point safe to
|
|
7
|
+
* import during server rendering.
|
|
8
|
+
*/
|
|
9
|
+
export function defineCareCompassElement(tagName = CARECOMPASS_ELEMENT_TAG) {
|
|
10
|
+
if (typeof customElements === "undefined" ||
|
|
11
|
+
typeof HTMLElement === "undefined") {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
const existing = customElements.get(tagName);
|
|
15
|
+
if (existing)
|
|
16
|
+
return existing;
|
|
17
|
+
class CareCompassElement extends HTMLElement {
|
|
18
|
+
constructor() {
|
|
19
|
+
super(...arguments);
|
|
20
|
+
this.draft = "";
|
|
21
|
+
this.hasPropertyConfig = false;
|
|
22
|
+
}
|
|
23
|
+
static get observedAttributes() {
|
|
24
|
+
return ["config"];
|
|
25
|
+
}
|
|
26
|
+
get config() {
|
|
27
|
+
return this.configuredValue;
|
|
28
|
+
}
|
|
29
|
+
set config(value) {
|
|
30
|
+
this.hasPropertyConfig = true;
|
|
31
|
+
this.configuredValue = value;
|
|
32
|
+
this.remount();
|
|
33
|
+
}
|
|
34
|
+
get runtimeTokenProvider() {
|
|
35
|
+
return this.runtimeTokenProviderValue;
|
|
36
|
+
}
|
|
37
|
+
set runtimeTokenProvider(value) {
|
|
38
|
+
this.runtimeTokenProviderValue = value;
|
|
39
|
+
this.remount();
|
|
40
|
+
}
|
|
41
|
+
connectedCallback() {
|
|
42
|
+
this.remount();
|
|
43
|
+
}
|
|
44
|
+
disconnectedCallback() {
|
|
45
|
+
this.unmount();
|
|
46
|
+
}
|
|
47
|
+
attributeChangedCallback(name, _oldValue, value) {
|
|
48
|
+
if (name !== "config" || this.hasPropertyConfig)
|
|
49
|
+
return;
|
|
50
|
+
if (value === null) {
|
|
51
|
+
this.configuredValue = undefined;
|
|
52
|
+
this.remount();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
this.configuredValue = JSON.parse(value);
|
|
57
|
+
this.remount();
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
this.reportConfigurationError();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
unmount() {
|
|
64
|
+
this.unsubscribe?.();
|
|
65
|
+
this.unsubscribe = undefined;
|
|
66
|
+
this.core?.dispose();
|
|
67
|
+
this.core = undefined;
|
|
68
|
+
this.replaceChildren();
|
|
69
|
+
}
|
|
70
|
+
remount() {
|
|
71
|
+
this.unmount();
|
|
72
|
+
if (!this.isConnected || !this.configuredValue)
|
|
73
|
+
return;
|
|
74
|
+
const validation = validateCareCompassConfig(this.configuredValue);
|
|
75
|
+
if (!validation.ok) {
|
|
76
|
+
this.reportConfigurationError();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
this.core = createCareCompassCore(validation.config, {
|
|
80
|
+
onAuditEvent: (event) => this.dispatchMetadataEvent("carecompass-audit", event),
|
|
81
|
+
onSafetyEvent: (event) => this.dispatchMetadataEvent("carecompass-safety", event),
|
|
82
|
+
runtimeTokenProvider: this.runtimeTokenProviderValue,
|
|
83
|
+
});
|
|
84
|
+
this.unsubscribe = this.core.subscribe((snapshot) => this.renderSnapshot(validation.config, snapshot));
|
|
85
|
+
this.renderSnapshot(validation.config, this.core.getSnapshot());
|
|
86
|
+
}
|
|
87
|
+
dispatchMetadataEvent(name, event) {
|
|
88
|
+
// Core audit and safety records intentionally contain metadata and PHI
|
|
89
|
+
// summaries only; never interpolate message text into DOM events.
|
|
90
|
+
this.dispatchEvent(new CustomEvent(name, {
|
|
91
|
+
bubbles: true,
|
|
92
|
+
detail: event,
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
renderSnapshot(config, snapshot) {
|
|
96
|
+
const section = document.createElement("section");
|
|
97
|
+
section.setAttribute("aria-label", config.labels.title);
|
|
98
|
+
Object.assign(section.style, {
|
|
99
|
+
background: config.theme.backgroundColor,
|
|
100
|
+
border: `1px solid ${config.theme.textColor}22`,
|
|
101
|
+
borderRadius: config.theme.borderRadius,
|
|
102
|
+
color: config.theme.textColor,
|
|
103
|
+
display: "flex",
|
|
104
|
+
flexDirection: "column",
|
|
105
|
+
fontFamily: config.theme.fontFamily,
|
|
106
|
+
maxWidth: config.placement.mode === "sidebar" ? "420px" : "720px",
|
|
107
|
+
minHeight: "420px",
|
|
108
|
+
overflow: "hidden",
|
|
109
|
+
});
|
|
110
|
+
const header = document.createElement("header");
|
|
111
|
+
Object.assign(header.style, {
|
|
112
|
+
borderBottom: `1px solid ${config.theme.textColor}22`,
|
|
113
|
+
padding: "16px",
|
|
114
|
+
});
|
|
115
|
+
const title = document.createElement("strong");
|
|
116
|
+
title.textContent = config.labels.title;
|
|
117
|
+
const brand = document.createElement("div");
|
|
118
|
+
brand.textContent = config.branding.displayName;
|
|
119
|
+
brand.style.fontSize = "12px";
|
|
120
|
+
brand.style.opacity = "0.72";
|
|
121
|
+
header.append(title, brand);
|
|
122
|
+
const messages = document.createElement("div");
|
|
123
|
+
messages.setAttribute("aria-live", "polite");
|
|
124
|
+
Object.assign(messages.style, {
|
|
125
|
+
display: "flex",
|
|
126
|
+
flex: "1",
|
|
127
|
+
flexDirection: "column",
|
|
128
|
+
gap: "12px",
|
|
129
|
+
overflowY: "auto",
|
|
130
|
+
padding: "16px",
|
|
131
|
+
});
|
|
132
|
+
for (const message of snapshot.messages) {
|
|
133
|
+
const bubble = document.createElement("div");
|
|
134
|
+
bubble.textContent = message.text;
|
|
135
|
+
Object.assign(bubble.style, {
|
|
136
|
+
alignSelf: message.role === "user" ? "flex-end" : "flex-start",
|
|
137
|
+
background: message.role === "user"
|
|
138
|
+
? config.theme.primaryColor
|
|
139
|
+
: `${config.theme.textColor}12`,
|
|
140
|
+
borderRadius: "12px",
|
|
141
|
+
color: message.role === "user" ? "#ffffff" : "inherit",
|
|
142
|
+
maxWidth: "82%",
|
|
143
|
+
padding: "10px 12px",
|
|
144
|
+
whiteSpace: "pre-wrap",
|
|
145
|
+
});
|
|
146
|
+
messages.append(bubble);
|
|
147
|
+
}
|
|
148
|
+
if (snapshot.loading) {
|
|
149
|
+
const thinking = document.createElement("div");
|
|
150
|
+
thinking.textContent = "Thinking...";
|
|
151
|
+
thinking.style.fontSize = "13px";
|
|
152
|
+
thinking.style.opacity = "0.72";
|
|
153
|
+
messages.append(thinking);
|
|
154
|
+
}
|
|
155
|
+
const form = document.createElement("form");
|
|
156
|
+
Object.assign(form.style, {
|
|
157
|
+
borderTop: `1px solid ${config.theme.textColor}22`,
|
|
158
|
+
display: "flex",
|
|
159
|
+
flexDirection: "column",
|
|
160
|
+
gap: "8px",
|
|
161
|
+
padding: "12px",
|
|
162
|
+
});
|
|
163
|
+
form.addEventListener("submit", (event) => {
|
|
164
|
+
event.preventDefault();
|
|
165
|
+
void this.sendMessage(snapshot);
|
|
166
|
+
});
|
|
167
|
+
const controls = document.createElement("div");
|
|
168
|
+
controls.style.display = "flex";
|
|
169
|
+
controls.style.gap = "8px";
|
|
170
|
+
const input = document.createElement("input");
|
|
171
|
+
input.setAttribute("aria-label", config.labels.placeholder);
|
|
172
|
+
input.disabled = snapshot.loading;
|
|
173
|
+
input.placeholder = config.labels.placeholder;
|
|
174
|
+
input.value = this.draft;
|
|
175
|
+
input.style.flex = "1";
|
|
176
|
+
input.style.minWidth = "0";
|
|
177
|
+
input.style.padding = "10px 12px";
|
|
178
|
+
input.addEventListener("input", () => {
|
|
179
|
+
this.draft = input.value;
|
|
180
|
+
send.disabled = snapshot.loading || !this.draft.trim();
|
|
181
|
+
});
|
|
182
|
+
const send = document.createElement("button");
|
|
183
|
+
send.disabled = snapshot.loading || !this.draft.trim();
|
|
184
|
+
send.textContent = config.labels.sendButton;
|
|
185
|
+
send.type = "submit";
|
|
186
|
+
Object.assign(send.style, {
|
|
187
|
+
background: config.theme.primaryColor,
|
|
188
|
+
border: "0",
|
|
189
|
+
borderRadius: "8px",
|
|
190
|
+
color: "#ffffff",
|
|
191
|
+
fontWeight: "700",
|
|
192
|
+
padding: "0 14px",
|
|
193
|
+
});
|
|
194
|
+
controls.append(input, send);
|
|
195
|
+
form.append(controls);
|
|
196
|
+
if (snapshot.error) {
|
|
197
|
+
const error = document.createElement("div");
|
|
198
|
+
error.setAttribute("role", "alert");
|
|
199
|
+
error.textContent = snapshot.error.message;
|
|
200
|
+
error.style.color = "#b91c1c";
|
|
201
|
+
error.style.fontSize = "12px";
|
|
202
|
+
form.append(error);
|
|
203
|
+
}
|
|
204
|
+
const disclaimer = document.createElement("div");
|
|
205
|
+
disclaimer.textContent = config.labels.disclaimer;
|
|
206
|
+
disclaimer.style.fontSize = "11px";
|
|
207
|
+
disclaimer.style.opacity = "0.7";
|
|
208
|
+
form.append(disclaimer);
|
|
209
|
+
section.append(header, messages, form);
|
|
210
|
+
this.replaceChildren(section);
|
|
211
|
+
}
|
|
212
|
+
async sendMessage(snapshot) {
|
|
213
|
+
const prompt = this.draft.trim();
|
|
214
|
+
if (!prompt || snapshot.loading || !this.core)
|
|
215
|
+
return;
|
|
216
|
+
this.draft = "";
|
|
217
|
+
try {
|
|
218
|
+
this.core.recordWidgetAction();
|
|
219
|
+
await this.core.sendMessage(prompt);
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
// The snapshot already carries the safe, user-visible error state.
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
reportConfigurationError() {
|
|
226
|
+
// Do not include raw config or validation detail in DOM events; a config
|
|
227
|
+
// can contain user-controlled branding and labels.
|
|
228
|
+
this.dispatchEvent(new CustomEvent("carecompass-error", {
|
|
229
|
+
bubbles: true,
|
|
230
|
+
detail: { code: "configuration_invalid" },
|
|
231
|
+
}));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
customElements.define(tagName, CareCompassElement);
|
|
235
|
+
return CareCompassElement;
|
|
236
|
+
}
|