@callembed/sdk 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +66 -2
  3. package/dist/callembed.iife.js +129 -0
  4. package/dist/core/agent.d.ts +10 -0
  5. package/dist/core/agent.js +19 -0
  6. package/dist/core/api.d.ts +44 -0
  7. package/dist/core/api.js +169 -0
  8. package/dist/core/audio.d.ts +20 -0
  9. package/dist/core/audio.js +68 -0
  10. package/dist/core/call.d.ts +45 -0
  11. package/dist/core/call.js +252 -0
  12. package/dist/core/client.d.ts +53 -0
  13. package/dist/core/client.js +157 -0
  14. package/dist/core/errors.d.ts +22 -0
  15. package/dist/core/errors.js +41 -0
  16. package/dist/core/events.d.ts +20 -0
  17. package/dist/core/events.js +43 -0
  18. package/dist/core/metadata.d.ts +13 -0
  19. package/dist/core/metadata.js +38 -0
  20. package/dist/core/ring-watch.d.ts +20 -0
  21. package/dist/core/ring-watch.js +46 -0
  22. package/dist/core/room-events.d.ts +13 -0
  23. package/dist/core/room-events.js +29 -0
  24. package/dist/core/state-machine.d.ts +11 -0
  25. package/dist/core/state-machine.js +44 -0
  26. package/dist/core/teardown.d.ts +49 -0
  27. package/dist/core/teardown.js +107 -0
  28. package/dist/core/types.d.ts +175 -0
  29. package/dist/core/types.js +5 -0
  30. package/dist/iife.d.ts +23 -0
  31. package/dist/iife.js +27 -0
  32. package/dist/index.d.ts +11 -0
  33. package/dist/index.js +10 -0
  34. package/dist/ui/bind.d.ts +14 -0
  35. package/dist/ui/bind.js +82 -0
  36. package/dist/ui/button.d.ts +28 -0
  37. package/dist/ui/button.js +57 -0
  38. package/dist/ui/copy-en.d.ts +3 -0
  39. package/dist/ui/copy-en.js +25 -0
  40. package/dist/ui/copy-zh-tw.d.ts +28 -0
  41. package/dist/ui/copy-zh-tw.js +25 -0
  42. package/dist/ui/copy.d.ts +18 -0
  43. package/dist/ui/copy.js +45 -0
  44. package/dist/ui/index.d.ts +12 -0
  45. package/dist/ui/index.js +12 -0
  46. package/dist/ui/panel.d.ts +26 -0
  47. package/dist/ui/panel.js +62 -0
  48. package/dist/ui/theme.d.ts +13 -0
  49. package/dist/ui/theme.js +124 -0
  50. package/package.json +68 -4
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Shared public types for the CallEmbed headless core.
3
+ * UI types that only apply to panels live under `src/ui/`.
4
+ */
5
+ /** Locale packs shipped with the optional UI layer (also accepted by `init`). */
6
+ export type Locale = "zh-TW" | "en";
7
+ /**
8
+ * Call lifecycle statuses.
9
+ * Legal transitions are enforced by the state machine in `state-machine.ts`.
10
+ */
11
+ export type CallStatus = "idle" | "connecting" | "ringing" | "answered" | "ended" | "error";
12
+ /**
13
+ * Options for {@link CallEmbed.init}.
14
+ *
15
+ * @remarks
16
+ * Only a **publishable** key (`pk_…`) is accepted. Never pass secret / agent /
17
+ * service-role keys into the browser SDK.
18
+ */
19
+ export interface InitOptions {
20
+ /** Project publishable key (`pk_…`). */
21
+ publishableKey: string;
22
+ /**
23
+ * API origin. Use `""` for same-origin (Vite proxy / reverse proxy).
24
+ */
25
+ apiBaseUrl: string;
26
+ /**
27
+ * Default UI locale when using `@callembed/sdk/ui`.
28
+ * When omitted: `zh-TW` if `navigator.language` starts with `zh`, else `en`.
29
+ */
30
+ locale?: Locale;
31
+ /**
32
+ * Partial copy overrides (merged over the locale pack).
33
+ * Values are always inserted with `textContent` — never interpreted as HTML.
34
+ */
35
+ copy?: Partial<Record<string, string>>;
36
+ /**
37
+ * When `false`, the default panel hides the CallEmbed brand line.
38
+ * Default: `true`.
39
+ */
40
+ branding?: boolean;
41
+ /**
42
+ * When `true`, the default panel may show `callId` (never tokens or keys).
43
+ * Default: `false`.
44
+ */
45
+ debug?: boolean;
46
+ }
47
+ /**
48
+ * Options for {@link CallEmbed.startCall}.
49
+ * (`on` handlers are typed via {@link StartCallEventHandlers} below.)
50
+ */
51
+ export interface StartCallOptions {
52
+ /** Queue name configured on the project (e.g. `onboarding`). */
53
+ queue: string;
54
+ /** Opaque JSON object forwarded to the API / webhooks (size-capped). */
55
+ metadata?: Record<string, unknown>;
56
+ /** Optional caller display name (not used for agent detection). */
57
+ displayName?: string;
58
+ /**
59
+ * When `true`, no default panel is created (headless).
60
+ * Remote audio still plays via a hidden core-managed host.
61
+ */
62
+ hidePanel?: boolean;
63
+ /**
64
+ * Mount the default panel into this element (UI layer only).
65
+ * Ignored when `hidePanel` is true. Requires `enableDefaultUi()` or `mountButton`.
66
+ */
67
+ container?: HTMLElement | string;
68
+ /**
69
+ * Typed listeners attached before connection starts.
70
+ * Startup failures (e.g. `no_agent_online`) also emit `error` here;
71
+ * the returned Promise still rejects.
72
+ */
73
+ on?: StartCallEventHandlers;
74
+ }
75
+ /**
76
+ * Successful `POST /v1/calls` payload.
77
+ * `token` (LiveKit JWT) and `endToken` are kept in memory only —
78
+ * never localStorage, URL query, logs, or webhook payloads.
79
+ */
80
+ export interface CallSession {
81
+ callId: string;
82
+ roomName: string;
83
+ livekitUrl: string;
84
+ token: string;
85
+ identity: string;
86
+ role: string;
87
+ ttlSeconds: number;
88
+ expiresAt: string;
89
+ queue: string;
90
+ metadata: Record<string, unknown>;
91
+ status: string;
92
+ /**
93
+ * One-time capability to end this call as the caller.
94
+ * Required on `POST /v1/calls/:id/end` when using the publishable key.
95
+ */
96
+ endToken?: string;
97
+ }
98
+ export interface ApiErrorBody {
99
+ error?: string;
100
+ message?: string;
101
+ }
102
+ /** Typed event names emitted by an active call. */
103
+ export type CallEventName = "statusChange" | "ringing" | "answered" | "ended" | "error" | "muteChange";
104
+ /** Payload map for {@link CallEventName}. */
105
+ export interface CallEventMap {
106
+ statusChange: {
107
+ status: CallStatus;
108
+ previousStatus: CallStatus;
109
+ };
110
+ ringing: {
111
+ callId: string;
112
+ };
113
+ answered: {
114
+ callId: string;
115
+ agentIdentity: string;
116
+ };
117
+ ended: {
118
+ callId: string;
119
+ status: "ended";
120
+ };
121
+ error: {
122
+ callId: string;
123
+ code: string;
124
+ message: string;
125
+ };
126
+ muteChange: {
127
+ muted: boolean;
128
+ };
129
+ }
130
+ export type CallEventHandler<E extends CallEventName> = (payload: CallEventMap[E]) => void;
131
+ /**
132
+ * Event handlers registered **before** the call connects.
133
+ * Use these to observe `connecting` / `ringing` (and startup `error`)
134
+ * without missing events that fire during `await startCall(...)`.
135
+ */
136
+ export type StartCallEventHandlers = {
137
+ [E in CallEventName]?: CallEventHandler<E>;
138
+ };
139
+ /**
140
+ * Handle returned by {@link CallEmbed.startCall}.
141
+ */
142
+ export interface ActiveCall {
143
+ /** Server call id once created; `"pending"` before `POST /v1/calls` returns. */
144
+ readonly callId: string;
145
+ /** Current lifecycle status (see state machine). */
146
+ readonly status: CallStatus;
147
+ /**
148
+ * End the call, stop microphone tracks, disconnect LiveKit, and notify the API.
149
+ * Safe to call multiple times (idempotent).
150
+ */
151
+ hangUp(): Promise<void>;
152
+ /**
153
+ * Mute / unmute the local microphone.
154
+ * May be called before the room connects; the preference is applied on connect.
155
+ */
156
+ setMuted(muted: boolean): Promise<void>;
157
+ /** Whether the local microphone is muted. */
158
+ isMuted(): boolean;
159
+ /**
160
+ * Subscribe to a typed call event.
161
+ * @returns Unsubscribe function.
162
+ */
163
+ on<E extends CallEventName>(event: E, handler: CallEventHandler<E>): () => void;
164
+ /** Remove a previously registered handler. */
165
+ off<E extends CallEventName>(event: E, handler: CallEventHandler<E>): void;
166
+ }
167
+ /** Resolved runtime config held after `init` (no secrets beyond publishable key). */
168
+ export interface RuntimeConfig {
169
+ publishableKey: string;
170
+ apiBaseUrl: string;
171
+ locale: Locale;
172
+ copyOverrides: Partial<Record<string, string>>;
173
+ branding: boolean;
174
+ debug: boolean;
175
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Shared public types for the CallEmbed headless core.
3
+ * UI types that only apply to panels live under `src/ui/`.
4
+ */
5
+ export {};
package/dist/iife.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ import { CallEmbedError } from "./core/errors.js";
2
+ import { mountButton } from "./ui/button.js";
3
+ import { enableDefaultUi } from "./ui/bind.js";
4
+ import { zhTwCopy, enCopy } from "./ui/copy.js";
5
+ import { THEME_VARIABLES, ensureStyles } from "./ui/theme.js";
6
+ /**
7
+ * Full browser global: init / startCall / mountButton.
8
+ */
9
+ export declare const CallEmbed: {
10
+ init: (options: import("./index.js").InitOptions) => void;
11
+ startCall: (options: import("./index.js").StartCallOptions) => Promise<import("./index.js").ActiveCall>;
12
+ getActiveCall: () => import("./index.js").ActiveCall | null;
13
+ mountButton: typeof mountButton;
14
+ enableDefaultUi: typeof enableDefaultUi;
15
+ };
16
+ export { CallEmbedError, zhTwCopy, enCopy, THEME_VARIABLES, ensureStyles };
17
+ declare global {
18
+ interface Window {
19
+ CallEmbed: typeof CallEmbed;
20
+ CallEmbedError: typeof CallEmbedError;
21
+ }
22
+ }
23
+ export default CallEmbed;
package/dist/iife.js ADDED
@@ -0,0 +1,27 @@
1
+ /**
2
+ * CDN / IIFE entry: headless core + default UI on `window.CallEmbed`.
3
+ */
4
+ import { CallEmbed as Core } from "./core/client.js";
5
+ import { CallEmbedError } from "./core/errors.js";
6
+ import { mountButton } from "./ui/button.js";
7
+ import { enableDefaultUi } from "./ui/bind.js";
8
+ import { zhTwCopy, enCopy } from "./ui/copy.js";
9
+ import { THEME_VARIABLES, ensureStyles } from "./ui/theme.js";
10
+ // CDN one-liner expects panel on startCall / mountButton.
11
+ enableDefaultUi();
12
+ /**
13
+ * Full browser global: init / startCall / mountButton.
14
+ */
15
+ export const CallEmbed = {
16
+ init: Core.init.bind(Core),
17
+ startCall: Core.startCall.bind(Core),
18
+ getActiveCall: Core.getActiveCall.bind(Core),
19
+ mountButton,
20
+ enableDefaultUi,
21
+ };
22
+ export { CallEmbedError, zhTwCopy, enCopy, THEME_VARIABLES, ensureStyles };
23
+ if (typeof window !== "undefined") {
24
+ window.CallEmbed = CallEmbed;
25
+ window.CallEmbedError = CallEmbedError;
26
+ }
27
+ export default CallEmbed;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @callembed/sdk — headless core entry.
3
+ *
4
+ * For the default panel / `mountButton`, import `@callembed/sdk/ui` and call
5
+ * `mountButton()` or `enableDefaultUi()`.
6
+ */
7
+ export { CallEmbed } from "./core/client.js";
8
+ export { CallEmbedError, type ErrorCode } from "./core/errors.js";
9
+ export { METADATA_MAX_BYTES, assertValidMetadata } from "./core/metadata.js";
10
+ export type { ActiveCall, ApiErrorBody, CallEventHandler, CallEventMap, CallEventName, CallSession, CallStatus, InitOptions, Locale, RuntimeConfig, StartCallEventHandlers, StartCallOptions, } from "./core/types.js";
11
+ export { CallEmbed as default } from "./core/client.js";
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @callembed/sdk — headless core entry.
3
+ *
4
+ * For the default panel / `mountButton`, import `@callembed/sdk/ui` and call
5
+ * `mountButton()` or `enableDefaultUi()`.
6
+ */
7
+ export { CallEmbed } from "./core/client.js";
8
+ export { CallEmbedError } from "./core/errors.js";
9
+ export { METADATA_MAX_BYTES, assertValidMetadata } from "./core/metadata.js";
10
+ export { CallEmbed as default } from "./core/client.js";
@@ -0,0 +1,14 @@
1
+ import type { CallSessionController, CallUiHooks } from "../core/call.js";
2
+ import type { StartCallOptions } from "../core/types.js";
3
+ /**
4
+ * Build UI hooks that mirror call status onto a default panel.
5
+ */
6
+ export declare function createPanelHooks(call: CallSessionController, opts: StartCallOptions): CallUiHooks;
7
+ /**
8
+ * Register the default panel factory on the headless client.
9
+ * Must be called explicitly (or via {@link mountButton}); importing the UI
10
+ * package alone does not change core `startCall` behaviour.
11
+ */
12
+ export declare function enableDefaultUi(): void;
13
+ /** @internal Test helper */
14
+ export declare function _resetDefaultUiForTests(): void;
@@ -0,0 +1,82 @@
1
+ import { CallEmbed, registerUiHooksFactory } from "../core/client.js";
2
+ import { friendlyErrorMessage, resolveCopy } from "./copy.js";
3
+ import { createCallPanel, destroyPanel, setPanelStatus, } from "./panel.js";
4
+ function resolveContainer(input) {
5
+ if (!input)
6
+ return undefined;
7
+ if (typeof input === "string") {
8
+ const found = document.querySelector(input);
9
+ return found instanceof HTMLElement ? found : undefined;
10
+ }
11
+ return input;
12
+ }
13
+ /**
14
+ * Build UI hooks that mirror call status onto a default panel.
15
+ */
16
+ export function createPanelHooks(call, opts) {
17
+ if (opts.hidePanel)
18
+ return {};
19
+ const runtime = CallEmbed._getRuntime();
20
+ const copy = resolveCopy(runtime);
21
+ const panel = createCallPanel({
22
+ copy,
23
+ branding: runtime?.branding !== false,
24
+ container: resolveContainer(opts.container),
25
+ });
26
+ panel.muteBtn.addEventListener("click", () => {
27
+ void call.setMuted(!call.isMuted());
28
+ });
29
+ panel.hangupBtn.addEventListener("click", () => {
30
+ void call.hangUp();
31
+ });
32
+ setPanelStatus(panel, "connecting", copy, copy.creating);
33
+ call.on("error", ({ code, message }) => {
34
+ setPanelStatus(panel, "error", copy, friendlyErrorMessage(copy, code, message));
35
+ panel.muteBtn.disabled = true;
36
+ panel.hangupBtn.textContent = copy.close;
37
+ panel.hangupBtn.onclick = () => {
38
+ destroyPanel(panel);
39
+ };
40
+ });
41
+ return {
42
+ onStatus: (status, message) => {
43
+ if (status === "error") {
44
+ // Friendly copy handled by the `error` event listener above.
45
+ return;
46
+ }
47
+ setPanelStatus(panel, status, copy, message);
48
+ if (runtime?.debug && call.callId && call.callId !== "pending") {
49
+ panel.metaEl.hidden = false;
50
+ panel.metaEl.textContent = `callId ${call.callId}`;
51
+ }
52
+ if (status === "ended") {
53
+ panel.muteBtn.disabled = true;
54
+ panel.hangupBtn.disabled = true;
55
+ window.setTimeout(() => destroyPanel(panel), 900);
56
+ }
57
+ },
58
+ onMuteChange: (muted) => {
59
+ panel.muteBtn.textContent = muted ? copy.unmute : copy.mute;
60
+ },
61
+ onTerminal: () => {
62
+ // Panel self-destruct handled in onStatus / error listener.
63
+ },
64
+ };
65
+ }
66
+ let bound = false;
67
+ /**
68
+ * Register the default panel factory on the headless client.
69
+ * Must be called explicitly (or via {@link mountButton}); importing the UI
70
+ * package alone does not change core `startCall` behaviour.
71
+ */
72
+ export function enableDefaultUi() {
73
+ if (bound)
74
+ return;
75
+ bound = true;
76
+ registerUiHooksFactory((call, opts) => createPanelHooks(call, opts));
77
+ }
78
+ /** @internal Test helper */
79
+ export function _resetDefaultUiForTests() {
80
+ bound = false;
81
+ registerUiHooksFactory(null);
82
+ }
@@ -0,0 +1,28 @@
1
+ import { CallEmbedError } from "../core/errors.js";
2
+ import type { ActiveCall, StartCallOptions } from "../core/types.js";
3
+ /** Options for {@link mountButton}. */
4
+ export interface MountButtonOptions extends StartCallOptions {
5
+ /** Button label; defaults to locale `launchDefault`. */
6
+ label?: string;
7
+ /**
8
+ * Called when `startCall` rejects (also receives `error` events via `options.on`).
9
+ */
10
+ onError?: (error: CallEmbedError) => void;
11
+ }
12
+ /** Handle returned by {@link mountButton}. */
13
+ export interface MountButtonResult {
14
+ /** The injected launch button. */
15
+ button: HTMLButtonElement;
16
+ /** Call started by the latest click, if any. */
17
+ getCall(): ActiveCall | null;
18
+ }
19
+ /**
20
+ * One-line default UI: inject a launch button that starts a call with the panel.
21
+ *
22
+ * Explicitly enables default UI (does not rely on import side effects).
23
+ * Never calls `window.alert`.
24
+ *
25
+ * @param selector - CSS selector for the host element.
26
+ * @param options - Queue / metadata / label / error callback.
27
+ */
28
+ export declare function mountButton(selector: string, options: MountButtonOptions): MountButtonResult;
@@ -0,0 +1,57 @@
1
+ import { CallEmbed } from "../core/client.js";
2
+ import { CallEmbedError } from "../core/errors.js";
3
+ import { enableDefaultUi } from "./bind.js";
4
+ import { resolveCopy } from "./copy.js";
5
+ import { ensureStyles } from "./theme.js";
6
+ /**
7
+ * One-line default UI: inject a launch button that starts a call with the panel.
8
+ *
9
+ * Explicitly enables default UI (does not rely on import side effects).
10
+ * Never calls `window.alert`.
11
+ *
12
+ * @param selector - CSS selector for the host element.
13
+ * @param options - Queue / metadata / label / error callback.
14
+ */
15
+ export function mountButton(selector, options) {
16
+ enableDefaultUi();
17
+ if (!CallEmbed._getRuntime()) {
18
+ throw new CallEmbedError("not_initialized", "Call CallEmbed.init({ publishableKey, apiBaseUrl }) first.");
19
+ }
20
+ ensureStyles();
21
+ const host = document.querySelector(selector);
22
+ if (!(host instanceof HTMLElement)) {
23
+ throw new CallEmbedError("invalid_selector", `Element not found: ${selector}`);
24
+ }
25
+ const copy = resolveCopy(CallEmbed._getRuntime());
26
+ const btn = document.createElement("button");
27
+ btn.type = "button";
28
+ btn.className = "ce-launch";
29
+ btn.textContent = options.label ?? copy.launchDefault;
30
+ host.appendChild(btn);
31
+ let lastCall = null;
32
+ btn.addEventListener("click", async () => {
33
+ btn.disabled = true;
34
+ try {
35
+ lastCall = await CallEmbed.startCall({
36
+ ...options,
37
+ hidePanel: false,
38
+ });
39
+ }
40
+ catch (err) {
41
+ lastCall = CallEmbed.getActiveCall();
42
+ if (err instanceof CallEmbedError) {
43
+ options.onError?.(err);
44
+ }
45
+ else {
46
+ options.onError?.(new CallEmbedError("call_failed", err instanceof Error ? err.message : String(err)));
47
+ }
48
+ }
49
+ finally {
50
+ btn.disabled = false;
51
+ }
52
+ });
53
+ return {
54
+ button: btn,
55
+ getCall: () => lastCall,
56
+ };
57
+ }
@@ -0,0 +1,3 @@
1
+ import type { PanelCopy } from "./copy-zh-tw.js";
2
+ /** English default panel / button copy. */
3
+ export declare const enCopy: PanelCopy;
@@ -0,0 +1,25 @@
1
+ /** English default panel / button copy. */
2
+ export const enCopy = {
3
+ brand: "CallEmbed",
4
+ title: "Voice call",
5
+ ariaLabel: "CallEmbed call panel",
6
+ connecting: "Connecting…",
7
+ creating: "Creating call…",
8
+ ringing: "Connected — waiting for an agent…",
9
+ answered: "In call",
10
+ ended: "Call ended",
11
+ disconnected: "Disconnected",
12
+ reconnecting: "Reconnecting…",
13
+ error: "Something went wrong. Please try again.",
14
+ errNoAgent: "No agent is online right now. Please try again later.",
15
+ errQuota: "Call unavailable right now. Please try again later.",
16
+ errMicDenied: "Microphone permission is required to place a call.",
17
+ errNoMic: "No microphone device was found.",
18
+ errConnection: "Connection failed. Check your network and try again.",
19
+ ready: "Ready",
20
+ mute: "Mute",
21
+ unmute: "Unmute",
22
+ hangup: "Hang up",
23
+ close: "Close",
24
+ launchDefault: "Talk to a person",
25
+ };
@@ -0,0 +1,28 @@
1
+ /** Traditional Chinese (zh-TW) default panel / button copy. */
2
+ export declare const zhTwCopy: {
3
+ readonly brand: "CallEmbed";
4
+ readonly title: "語音通話";
5
+ readonly ariaLabel: "CallEmbed 通話面板";
6
+ readonly connecting: "連線中…";
7
+ readonly creating: "正在建立通話…";
8
+ readonly ringing: "已連線 — 等待客服接聽…";
9
+ readonly answered: "通話中";
10
+ readonly ended: "通話已結束";
11
+ readonly disconnected: "已斷線";
12
+ readonly reconnecting: "重新連線中…";
13
+ readonly error: "發生錯誤,請稍後再試。";
14
+ readonly errNoAgent: "目前沒有客服在線,請稍後再試。";
15
+ readonly errQuota: "目前無法通話,請稍後再試。";
16
+ readonly errMicDenied: "需要麥克風權限才能通話。";
17
+ readonly errNoMic: "找不到麥克風裝置。";
18
+ readonly errConnection: "連線失敗,請檢查網路後再試。";
19
+ readonly ready: "就緒";
20
+ readonly mute: "靜音";
21
+ readonly unmute: "取消靜音";
22
+ readonly hangup: "掛斷";
23
+ readonly close: "關閉";
24
+ readonly launchDefault: "找真人聊聊";
25
+ };
26
+ export type PanelCopy = {
27
+ [K in keyof typeof zhTwCopy]: string;
28
+ };
@@ -0,0 +1,25 @@
1
+ /** Traditional Chinese (zh-TW) default panel / button copy. */
2
+ export const zhTwCopy = {
3
+ brand: "CallEmbed",
4
+ title: "語音通話",
5
+ ariaLabel: "CallEmbed 通話面板",
6
+ connecting: "連線中…",
7
+ creating: "正在建立通話…",
8
+ ringing: "已連線 — 等待客服接聽…",
9
+ answered: "通話中",
10
+ ended: "通話已結束",
11
+ disconnected: "已斷線",
12
+ reconnecting: "重新連線中…",
13
+ error: "發生錯誤,請稍後再試。",
14
+ errNoAgent: "目前沒有客服在線,請稍後再試。",
15
+ errQuota: "目前無法通話,請稍後再試。",
16
+ errMicDenied: "需要麥克風權限才能通話。",
17
+ errNoMic: "找不到麥克風裝置。",
18
+ errConnection: "連線失敗,請檢查網路後再試。",
19
+ ready: "就緒",
20
+ mute: "靜音",
21
+ unmute: "取消靜音",
22
+ hangup: "掛斷",
23
+ close: "關閉",
24
+ launchDefault: "找真人聊聊",
25
+ };
@@ -0,0 +1,18 @@
1
+ import type { RuntimeConfig } from "../core/types.js";
2
+ import { enCopy } from "./copy-en.js";
3
+ import { zhTwCopy, type PanelCopy } from "./copy-zh-tw.js";
4
+ /**
5
+ * Resolve panel copy from locale + safe string overrides.
6
+ * Override values are coerced to strings (never used as HTML).
7
+ */
8
+ export declare function resolveCopy(runtime: RuntimeConfig | null): PanelCopy;
9
+ /**
10
+ * Map machine error codes to friendly panel copy (never show raw codes to B).
11
+ */
12
+ /**
13
+ * Map machine error codes to friendly panel copy.
14
+ * Unknown codes use the locale generic `error` string — never raw server text.
15
+ */
16
+ export declare function friendlyErrorMessage(copy: PanelCopy, code: string, _fallback?: string): string;
17
+ export { zhTwCopy, enCopy };
18
+ export type { PanelCopy };
@@ -0,0 +1,45 @@
1
+ import { enCopy } from "./copy-en.js";
2
+ import { zhTwCopy } from "./copy-zh-tw.js";
3
+ const PACKS = {
4
+ "zh-TW": { ...zhTwCopy },
5
+ en: { ...enCopy },
6
+ };
7
+ /**
8
+ * Resolve panel copy from locale + safe string overrides.
9
+ * Override values are coerced to strings (never used as HTML).
10
+ */
11
+ export function resolveCopy(runtime) {
12
+ const locale = runtime?.locale ?? "en";
13
+ const base = { ...(PACKS[locale] ?? PACKS.en) };
14
+ const overrides = runtime?.copyOverrides ?? {};
15
+ for (const [key, value] of Object.entries(overrides)) {
16
+ if (key in base && value !== undefined && value !== null) {
17
+ base[key] = String(value);
18
+ }
19
+ }
20
+ return base;
21
+ }
22
+ /**
23
+ * Map machine error codes to friendly panel copy (never show raw codes to B).
24
+ */
25
+ /**
26
+ * Map machine error codes to friendly panel copy.
27
+ * Unknown codes use the locale generic `error` string — never raw server text.
28
+ */
29
+ export function friendlyErrorMessage(copy, code, _fallback) {
30
+ switch (code) {
31
+ case "no_agent_online":
32
+ return copy.errNoAgent;
33
+ case "quota_exceeded":
34
+ return copy.errQuota;
35
+ case "mic_permission_denied":
36
+ return copy.errMicDenied;
37
+ case "no_microphone":
38
+ return copy.errNoMic;
39
+ case "connection_failed":
40
+ return copy.errConnection;
41
+ default:
42
+ return copy.error;
43
+ }
44
+ }
45
+ export { zhTwCopy, enCopy };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Optional CallEmbed UI layer (panel, launch button, theme, copy).
3
+ * Depends on the headless core; core never imports this module.
4
+ *
5
+ * Importing this package does **not** change `CallEmbed.startCall` behaviour.
6
+ * Call {@link enableDefaultUi} or {@link mountButton} to attach the default panel.
7
+ */
8
+ export { enableDefaultUi, createPanelHooks } from "./bind.js";
9
+ export { mountButton, type MountButtonOptions, type MountButtonResult, } from "./button.js";
10
+ export { createCallPanel, destroyPanel, setPanelStatus, type CallPanelElements, type CreatePanelOptions, } from "./panel.js";
11
+ export { ensureStyles, THEME_VARIABLES } from "./theme.js";
12
+ export { resolveCopy, friendlyErrorMessage, zhTwCopy, enCopy, type PanelCopy, } from "./copy.js";
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Optional CallEmbed UI layer (panel, launch button, theme, copy).
3
+ * Depends on the headless core; core never imports this module.
4
+ *
5
+ * Importing this package does **not** change `CallEmbed.startCall` behaviour.
6
+ * Call {@link enableDefaultUi} or {@link mountButton} to attach the default panel.
7
+ */
8
+ export { enableDefaultUi, createPanelHooks } from "./bind.js";
9
+ export { mountButton, } from "./button.js";
10
+ export { createCallPanel, destroyPanel, setPanelStatus, } from "./panel.js";
11
+ export { ensureStyles, THEME_VARIABLES } from "./theme.js";
12
+ export { resolveCopy, friendlyErrorMessage, zhTwCopy, enCopy, } from "./copy.js";
@@ -0,0 +1,26 @@
1
+ import type { CallStatus } from "../core/types.js";
2
+ import type { PanelCopy } from "./copy-zh-tw.js";
3
+ export interface CallPanelElements {
4
+ root: HTMLElement;
5
+ brandEl: HTMLElement;
6
+ titleEl: HTMLElement;
7
+ statusEl: HTMLElement;
8
+ muteBtn: HTMLButtonElement;
9
+ hangupBtn: HTMLButtonElement;
10
+ metaEl: HTMLElement;
11
+ }
12
+ export interface CreatePanelOptions {
13
+ copy: PanelCopy;
14
+ branding?: boolean;
15
+ container?: HTMLElement;
16
+ }
17
+ /**
18
+ * Build the default call panel with `createElement` / `textContent` only.
19
+ * Never interprets copy strings as HTML.
20
+ */
21
+ export declare function createCallPanel(options: CreatePanelOptions): CallPanelElements;
22
+ /**
23
+ * Update panel status line (always via `textContent`).
24
+ */
25
+ export declare function setPanelStatus(panel: CallPanelElements, status: CallStatus, copy: PanelCopy, message?: string): void;
26
+ export declare function destroyPanel(panel: CallPanelElements): void;