@elizaos/plugin-phone 2.0.3-beta.2 → 2.0.3-beta.4
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/companion/components/Chat.d.ts +14 -0
- package/dist/companion/components/Chat.d.ts.map +1 -0
- package/dist/companion/components/Chat.js +195 -0
- package/dist/companion/components/Chat.js.map +1 -0
- package/dist/companion/components/Pairing.d.ts +9 -0
- package/dist/companion/components/Pairing.d.ts.map +1 -0
- package/dist/companion/components/Pairing.js +175 -0
- package/dist/companion/components/Pairing.js.map +1 -0
- package/dist/companion/components/PhoneCompanionApp.d.ts +9 -0
- package/dist/companion/components/PhoneCompanionApp.d.ts.map +1 -0
- package/dist/companion/components/PhoneCompanionApp.js +174 -0
- package/dist/companion/components/PhoneCompanionApp.js.map +1 -0
- package/dist/companion/components/RemoteSession.d.ts +9 -0
- package/dist/companion/components/RemoteSession.d.ts.map +1 -0
- package/dist/companion/components/RemoteSession.js +382 -0
- package/dist/companion/components/RemoteSession.js.map +1 -0
- package/dist/companion/components/index.d.ts +5 -0
- package/dist/companion/components/index.d.ts.map +1 -0
- package/dist/companion/components/index.js +11 -0
- package/dist/companion/components/index.js.map +1 -0
- package/dist/companion/index.d.ts +14 -0
- package/dist/companion/index.d.ts.map +1 -0
- package/dist/companion/index.js +12 -0
- package/dist/companion/index.js.map +1 -0
- package/dist/companion/services/eliza-intent.d.ts +63 -0
- package/dist/companion/services/eliza-intent.d.ts.map +1 -0
- package/dist/companion/services/eliza-intent.js +52 -0
- package/dist/companion/services/eliza-intent.js.map +1 -0
- package/dist/companion/services/env.d.ts +11 -0
- package/dist/companion/services/env.d.ts.map +1 -0
- package/dist/companion/services/env.js +20 -0
- package/dist/companion/services/env.js.map +1 -0
- package/dist/companion/services/index.d.ts +14 -0
- package/dist/companion/services/index.d.ts.map +1 -0
- package/dist/companion/services/index.js +29 -0
- package/dist/companion/services/index.js.map +1 -0
- package/dist/companion/services/intent-bridge.d.ts +11 -0
- package/dist/companion/services/intent-bridge.d.ts.map +1 -0
- package/dist/companion/services/intent-bridge.js +15 -0
- package/dist/companion/services/intent-bridge.js.map +1 -0
- package/dist/companion/services/logger.d.ts +14 -0
- package/dist/companion/services/logger.d.ts.map +1 -0
- package/dist/companion/services/logger.js +50 -0
- package/dist/companion/services/logger.js.map +1 -0
- package/dist/companion/services/navigation.d.ts +17 -0
- package/dist/companion/services/navigation.d.ts.map +1 -0
- package/dist/companion/services/navigation.js +104 -0
- package/dist/companion/services/navigation.js.map +1 -0
- package/dist/companion/services/push.d.ts +27 -0
- package/dist/companion/services/push.d.ts.map +1 -0
- package/dist/companion/services/push.js +101 -0
- package/dist/companion/services/push.js.map +1 -0
- package/dist/companion/services/session-client.d.ts +114 -0
- package/dist/companion/services/session-client.d.ts.map +1 -0
- package/dist/companion/services/session-client.js +197 -0
- package/dist/companion/services/session-client.js.map +1 -0
- package/dist/components/PhoneAppView.d.ts +18 -0
- package/dist/components/PhoneAppView.d.ts.map +1 -0
- package/dist/components/PhoneAppView.helpers.d.ts +12 -0
- package/dist/components/PhoneAppView.helpers.d.ts.map +1 -0
- package/dist/components/PhoneAppView.helpers.js +44 -0
- package/dist/components/PhoneAppView.helpers.js.map +1 -0
- package/dist/components/PhoneAppView.interact.d.ts +2 -0
- package/dist/components/PhoneAppView.interact.d.ts.map +1 -0
- package/dist/components/PhoneAppView.interact.js +63 -0
- package/dist/components/PhoneAppView.interact.js.map +1 -0
- package/dist/components/PhoneAppView.js +566 -0
- package/dist/components/PhoneAppView.js.map +1 -0
- package/dist/components/PhoneSpatialView.d.ts +43 -0
- package/dist/components/PhoneSpatialView.d.ts.map +1 -0
- package/dist/components/PhoneSpatialView.js +180 -0
- package/dist/components/PhoneSpatialView.js.map +1 -0
- package/dist/components/PhoneView.d.ts +13 -0
- package/dist/components/PhoneView.d.ts.map +1 -0
- package/dist/components/PhoneView.js +161 -0
- package/dist/components/PhoneView.js.map +1 -0
- package/dist/components/phone-app.d.ts +13 -0
- package/dist/components/phone-app.d.ts.map +1 -0
- package/dist/components/phone-app.js +20 -0
- package/dist/components/phone-app.js.map +1 -0
- package/dist/components/phone-view-bundle.d.ts +3 -0
- package/dist/components/phone-view-bundle.d.ts.map +1 -0
- package/dist/components/phone-view-bundle.js +7 -0
- package/dist/components/phone-view-bundle.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin.d.ts +17 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +48 -0
- package/dist/plugin.js.map +1 -0
- package/dist/providers/call-log.d.ts +11 -0
- package/dist/providers/call-log.d.ts.map +1 -0
- package/dist/providers/call-log.js +66 -0
- package/dist/providers/call-log.js.map +1 -0
- package/dist/register-companion-page.d.ts +13 -0
- package/dist/register-companion-page.d.ts.map +1 -0
- package/dist/register-companion-page.js +12 -0
- package/dist/register-companion-page.js.map +1 -0
- package/dist/register-terminal-view.d.ts +15 -0
- package/dist/register-terminal-view.d.ts.map +1 -0
- package/dist/register-terminal-view.js +21 -0
- package/dist/register-terminal-view.js.map +1 -0
- package/dist/register.d.ts +9 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +11 -0
- package/dist/register.js.map +1 -0
- package/dist/twilio.d.ts +33 -0
- package/dist/twilio.d.ts.map +1 -0
- package/dist/twilio.js +238 -0
- package/dist/twilio.js.map +1 -0
- package/dist/ui.d.ts +8 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +23 -0
- package/dist/ui.js.map +1 -0
- package/dist/views/bundle.js +407 -0
- package/dist/views/bundle.js.map +1 -0
- package/dist/views/dist-Cd2YtKy4.js +270 -0
- package/dist/views/dist-Cd2YtKy4.js.map +1 -0
- package/dist/views/web-TGRkTsa8.js +58 -0
- package/dist/views/web-TGRkTsa8.js.map +1 -0
- package/package.json +9 -8
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Phone } from "@elizaos/capacitor-phone";
|
|
3
|
+
import { Button, useAgentElement } from "@elizaos/ui";
|
|
4
|
+
import { consumePendingPhoneNumber } from "@elizaos/ui/app-navigate-view";
|
|
5
|
+
import { PermissionRecoveryCallout } from "@elizaos/ui/components";
|
|
6
|
+
import {
|
|
7
|
+
Tabs,
|
|
8
|
+
TabsContent,
|
|
9
|
+
TabsList,
|
|
10
|
+
TabsTrigger
|
|
11
|
+
} from "@elizaos/ui/components/ui/tabs";
|
|
12
|
+
import {
|
|
13
|
+
ArrowLeft,
|
|
14
|
+
Delete,
|
|
15
|
+
Phone as PhoneIcon,
|
|
16
|
+
PhoneIncoming,
|
|
17
|
+
PhoneMissed,
|
|
18
|
+
PhoneOutgoing,
|
|
19
|
+
Users as UsersIcon,
|
|
20
|
+
Voicemail
|
|
21
|
+
} from "lucide-react";
|
|
22
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
23
|
+
import { callLabelFor, normalizeNumber } from "./PhoneAppView.helpers.js";
|
|
24
|
+
const DIAL_KEYS = [
|
|
25
|
+
"1",
|
|
26
|
+
"2",
|
|
27
|
+
"3",
|
|
28
|
+
"4",
|
|
29
|
+
"5",
|
|
30
|
+
"6",
|
|
31
|
+
"7",
|
|
32
|
+
"8",
|
|
33
|
+
"9",
|
|
34
|
+
"*",
|
|
35
|
+
"0",
|
|
36
|
+
"#"
|
|
37
|
+
];
|
|
38
|
+
function formatTimestamp(epochMs) {
|
|
39
|
+
const date = new Date(epochMs);
|
|
40
|
+
if (Number.isNaN(date.getTime())) return "";
|
|
41
|
+
const now = /* @__PURE__ */ new Date();
|
|
42
|
+
const sameDay = date.getFullYear() === now.getFullYear() && date.getMonth() === now.getMonth() && date.getDate() === now.getDate();
|
|
43
|
+
if (sameDay) {
|
|
44
|
+
return date.toLocaleTimeString(void 0, {
|
|
45
|
+
hour: "2-digit",
|
|
46
|
+
minute: "2-digit"
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return date.toLocaleString(void 0, {
|
|
50
|
+
month: "short",
|
|
51
|
+
day: "numeric",
|
|
52
|
+
hour: "2-digit",
|
|
53
|
+
minute: "2-digit"
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function callIconFor(type) {
|
|
57
|
+
switch (type) {
|
|
58
|
+
case "incoming":
|
|
59
|
+
case "answered_externally":
|
|
60
|
+
return /* @__PURE__ */ jsx(PhoneIncoming, { className: "h-4 w-4 text-ok", "aria-hidden": true });
|
|
61
|
+
case "outgoing":
|
|
62
|
+
return /* @__PURE__ */ jsx(PhoneOutgoing, { className: "h-4 w-4 text-accent", "aria-hidden": true });
|
|
63
|
+
case "missed":
|
|
64
|
+
case "rejected":
|
|
65
|
+
case "blocked":
|
|
66
|
+
return /* @__PURE__ */ jsx(PhoneMissed, { className: "h-4 w-4 text-danger", "aria-hidden": true });
|
|
67
|
+
case "voicemail":
|
|
68
|
+
return /* @__PURE__ */ jsx(Voicemail, { className: "h-4 w-4 text-muted", "aria-hidden": true });
|
|
69
|
+
default:
|
|
70
|
+
return /* @__PURE__ */ jsx(PhoneIcon, { className: "h-4 w-4 text-muted", "aria-hidden": true });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function isPhonePermissionError(message) {
|
|
74
|
+
const normalized = message.toLowerCase();
|
|
75
|
+
return normalized.includes("permission") || normalized.includes("denied") || normalized.includes("access is needed") || normalized.includes("call_phone") || normalized.includes("read_call_log");
|
|
76
|
+
}
|
|
77
|
+
function PhoneTabTrigger({
|
|
78
|
+
tab,
|
|
79
|
+
label,
|
|
80
|
+
active,
|
|
81
|
+
disabled
|
|
82
|
+
}) {
|
|
83
|
+
const { ref, agentProps } = useAgentElement({
|
|
84
|
+
id: `tab-${tab}`,
|
|
85
|
+
role: "tab",
|
|
86
|
+
label,
|
|
87
|
+
group: "phone-tabs",
|
|
88
|
+
status: active ? "active" : "inactive",
|
|
89
|
+
description: `Switch to the ${label} tab`
|
|
90
|
+
});
|
|
91
|
+
return /* @__PURE__ */ jsx(
|
|
92
|
+
TabsTrigger,
|
|
93
|
+
{
|
|
94
|
+
ref,
|
|
95
|
+
value: tab,
|
|
96
|
+
disabled,
|
|
97
|
+
"aria-current": active ? "true" : void 0,
|
|
98
|
+
className: "font-semibold transition-colors",
|
|
99
|
+
style: {
|
|
100
|
+
backgroundColor: "transparent",
|
|
101
|
+
color: active ? "var(--accent)" : "var(--muted)",
|
|
102
|
+
border: "1px solid transparent"
|
|
103
|
+
},
|
|
104
|
+
...agentProps,
|
|
105
|
+
children: label
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
function PhoneDialKey({
|
|
110
|
+
digit,
|
|
111
|
+
onPress
|
|
112
|
+
}) {
|
|
113
|
+
const { ref, agentProps } = useAgentElement({
|
|
114
|
+
id: `dial-key-${digit}`,
|
|
115
|
+
role: "button",
|
|
116
|
+
label: `Dial ${digit}`,
|
|
117
|
+
group: "phone-dialpad",
|
|
118
|
+
description: `Append ${digit} to the number being dialed`
|
|
119
|
+
});
|
|
120
|
+
return /* @__PURE__ */ jsx(
|
|
121
|
+
"button",
|
|
122
|
+
{
|
|
123
|
+
ref,
|
|
124
|
+
type: "button",
|
|
125
|
+
className: "h-16 text-2xl font-semibold transition active:scale-95 sm:h-20",
|
|
126
|
+
style: {
|
|
127
|
+
backgroundColor: "transparent",
|
|
128
|
+
color: "var(--text)",
|
|
129
|
+
border: "none"
|
|
130
|
+
},
|
|
131
|
+
onClick: () => onPress(digit),
|
|
132
|
+
"aria-label": `Dial ${digit}`,
|
|
133
|
+
"data-testid": `phone-dial-key-${digit}`,
|
|
134
|
+
...agentProps,
|
|
135
|
+
children: digit
|
|
136
|
+
}
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
function RecentCallButton({
|
|
140
|
+
entry,
|
|
141
|
+
onCall
|
|
142
|
+
}) {
|
|
143
|
+
const label = callLabelFor(entry);
|
|
144
|
+
const showNumber = entry.cachedName && entry.cachedName.trim().length > 0 ? entry.number : null;
|
|
145
|
+
const { ref, agentProps } = useAgentElement({
|
|
146
|
+
id: `recent-call-${entry.id}`,
|
|
147
|
+
role: "list-item",
|
|
148
|
+
label: `Call ${label}`,
|
|
149
|
+
group: "phone-recent",
|
|
150
|
+
description: `Place a call to ${label}`,
|
|
151
|
+
onActivate: () => onCall(entry.number)
|
|
152
|
+
});
|
|
153
|
+
return /* @__PURE__ */ jsxs(
|
|
154
|
+
"button",
|
|
155
|
+
{
|
|
156
|
+
ref,
|
|
157
|
+
type: "button",
|
|
158
|
+
onClick: () => onCall(entry.number),
|
|
159
|
+
className: "flex w-full items-center gap-3 px-2 py-2 text-left transition active:scale-[0.99]",
|
|
160
|
+
style: {
|
|
161
|
+
backgroundColor: "transparent",
|
|
162
|
+
border: "none"
|
|
163
|
+
},
|
|
164
|
+
...agentProps,
|
|
165
|
+
children: [
|
|
166
|
+
/* @__PURE__ */ jsx(
|
|
167
|
+
"span",
|
|
168
|
+
{
|
|
169
|
+
className: "flex h-9 w-9 shrink-0 items-center justify-center rounded-full",
|
|
170
|
+
style: { backgroundColor: "var(--accent-subtle)" },
|
|
171
|
+
children: callIconFor(entry.type)
|
|
172
|
+
}
|
|
173
|
+
),
|
|
174
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
|
|
175
|
+
/* @__PURE__ */ jsx("span", { className: "block truncate text-sm font-semibold text-txt", children: label }),
|
|
176
|
+
/* @__PURE__ */ jsxs("span", { className: "block truncate text-xs text-muted", children: [
|
|
177
|
+
showNumber ? `${showNumber} \xB7 ` : "",
|
|
178
|
+
formatTimestamp(entry.date)
|
|
179
|
+
] })
|
|
180
|
+
] }),
|
|
181
|
+
/* @__PURE__ */ jsx(PhoneIcon, { className: "h-4 w-4 shrink-0 text-muted" })
|
|
182
|
+
]
|
|
183
|
+
}
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
function PhoneAppView({ exitToApps, t }) {
|
|
187
|
+
const [activeTab, setActiveTab] = useState("dialer");
|
|
188
|
+
const [dialed, setDialed] = useState("");
|
|
189
|
+
const [calling, setCalling] = useState(false);
|
|
190
|
+
const [callError, setCallError] = useState(null);
|
|
191
|
+
const [calls, setCalls] = useState([]);
|
|
192
|
+
const [callsLoading, setCallsLoading] = useState(false);
|
|
193
|
+
const [callsError, setCallsError] = useState(null);
|
|
194
|
+
const recentAutoLoadedRef = useRef(false);
|
|
195
|
+
const refreshCalls = useCallback(async () => {
|
|
196
|
+
setCallsLoading(true);
|
|
197
|
+
setCallsError(null);
|
|
198
|
+
try {
|
|
199
|
+
const status = await Phone.requestPermissions().catch(() => null);
|
|
200
|
+
if (status && status.phone !== "granted") {
|
|
201
|
+
setCalls([]);
|
|
202
|
+
setCallsError(
|
|
203
|
+
"Phone access is needed for recent calls and dialing. Grant it in your device settings, then retry."
|
|
204
|
+
);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const { calls: fetched } = await Phone.listRecentCalls({ limit: 50 });
|
|
208
|
+
setCalls(fetched);
|
|
209
|
+
} catch (err) {
|
|
210
|
+
setCallsError(err instanceof Error ? err.message : String(err));
|
|
211
|
+
setCalls([]);
|
|
212
|
+
} finally {
|
|
213
|
+
setCallsLoading(false);
|
|
214
|
+
}
|
|
215
|
+
}, []);
|
|
216
|
+
useEffect(() => {
|
|
217
|
+
const pending = consumePendingPhoneNumber();
|
|
218
|
+
if (pending) {
|
|
219
|
+
setCallError(null);
|
|
220
|
+
setDialed(pending);
|
|
221
|
+
setActiveTab("dialer");
|
|
222
|
+
}
|
|
223
|
+
}, []);
|
|
224
|
+
useEffect(() => {
|
|
225
|
+
if (activeTab !== "recent") return;
|
|
226
|
+
if (!recentAutoLoadedRef.current && !callsLoading) {
|
|
227
|
+
recentAutoLoadedRef.current = true;
|
|
228
|
+
void refreshCalls();
|
|
229
|
+
}
|
|
230
|
+
const interval = setInterval(() => {
|
|
231
|
+
void refreshCalls();
|
|
232
|
+
}, 2e4);
|
|
233
|
+
return () => clearInterval(interval);
|
|
234
|
+
}, [activeTab, callsLoading, refreshCalls]);
|
|
235
|
+
const appendDigit = useCallback((digit) => {
|
|
236
|
+
setCallError(null);
|
|
237
|
+
setDialed((prev) => `${prev}${digit}`);
|
|
238
|
+
}, []);
|
|
239
|
+
const appendPlus = useCallback(() => {
|
|
240
|
+
setCallError(null);
|
|
241
|
+
setDialed((prev) => prev.length === 0 ? "+" : prev);
|
|
242
|
+
}, []);
|
|
243
|
+
const backspace = useCallback(() => {
|
|
244
|
+
setCallError(null);
|
|
245
|
+
setDialed((prev) => prev.slice(0, -1));
|
|
246
|
+
}, []);
|
|
247
|
+
const placeCall = useCallback(async (number) => {
|
|
248
|
+
const normalized = normalizeNumber(number);
|
|
249
|
+
if (!normalized) {
|
|
250
|
+
setCallError("Enter a number to call.");
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
setCalling(true);
|
|
254
|
+
setCallError(null);
|
|
255
|
+
try {
|
|
256
|
+
await Phone.placeCall({ number: normalized });
|
|
257
|
+
} catch (err) {
|
|
258
|
+
setCallError(err instanceof Error ? err.message : String(err));
|
|
259
|
+
} finally {
|
|
260
|
+
setCalling(false);
|
|
261
|
+
}
|
|
262
|
+
}, []);
|
|
263
|
+
const onDialerCall = useCallback(() => {
|
|
264
|
+
void placeCall(dialed);
|
|
265
|
+
}, [dialed, placeCall]);
|
|
266
|
+
const onCallEntry = useCallback(
|
|
267
|
+
(number) => {
|
|
268
|
+
void placeCall(number);
|
|
269
|
+
},
|
|
270
|
+
[placeCall]
|
|
271
|
+
);
|
|
272
|
+
const dialerDisplay = useMemo(() => dialed || "", [dialed]);
|
|
273
|
+
const openContacts = useCallback(() => {
|
|
274
|
+
if (typeof window === "undefined") return;
|
|
275
|
+
window.dispatchEvent(
|
|
276
|
+
new CustomEvent("eliza:navigate:view", {
|
|
277
|
+
detail: { viewId: "contacts", viewPath: "/contacts" }
|
|
278
|
+
})
|
|
279
|
+
);
|
|
280
|
+
}, []);
|
|
281
|
+
const backLabel = t("nav.back", { defaultValue: "Back" });
|
|
282
|
+
const callLabel = t("phone.dialer.call", { defaultValue: "Call" });
|
|
283
|
+
const intlLabel = t("phone.dialer.intl", {
|
|
284
|
+
defaultValue: "Insert + for international dialing"
|
|
285
|
+
});
|
|
286
|
+
const backspaceLabel = t("phone.dialer.backspace", {
|
|
287
|
+
defaultValue: "Delete digit"
|
|
288
|
+
});
|
|
289
|
+
const backAgent = useAgentElement({
|
|
290
|
+
id: "action-back",
|
|
291
|
+
role: "button",
|
|
292
|
+
label: backLabel,
|
|
293
|
+
group: "phone-nav",
|
|
294
|
+
description: "Leave the Phone app and return to the app grid"
|
|
295
|
+
});
|
|
296
|
+
const contactsLabel = t("phone.tabs.contacts", { defaultValue: "Contacts" });
|
|
297
|
+
const contactsNavAgent = useAgentElement({
|
|
298
|
+
id: "action-contacts",
|
|
299
|
+
role: "button",
|
|
300
|
+
label: contactsLabel,
|
|
301
|
+
group: "phone-nav",
|
|
302
|
+
description: "Open the Contacts app to browse the address book",
|
|
303
|
+
onActivate: openContacts
|
|
304
|
+
});
|
|
305
|
+
const plusAgent = useAgentElement({
|
|
306
|
+
id: "dial-plus",
|
|
307
|
+
role: "button",
|
|
308
|
+
label: intlLabel,
|
|
309
|
+
group: "phone-dialer",
|
|
310
|
+
description: "Insert a leading + for international dialing"
|
|
311
|
+
});
|
|
312
|
+
const callAgent = useAgentElement({
|
|
313
|
+
id: "action-call",
|
|
314
|
+
role: "button",
|
|
315
|
+
label: callLabel,
|
|
316
|
+
group: "phone-dialer",
|
|
317
|
+
description: "Place a call to the dialed number"
|
|
318
|
+
});
|
|
319
|
+
const backspaceAgent = useAgentElement({
|
|
320
|
+
id: "dial-backspace",
|
|
321
|
+
role: "button",
|
|
322
|
+
label: backspaceLabel,
|
|
323
|
+
group: "phone-dialer",
|
|
324
|
+
description: "Delete the last digit of the dialed number"
|
|
325
|
+
});
|
|
326
|
+
const emptyDialerAgent = useAgentElement({
|
|
327
|
+
id: "recent-empty-dialer",
|
|
328
|
+
role: "button",
|
|
329
|
+
label: t("phone.tabs.dialer", { defaultValue: "Dialer" }),
|
|
330
|
+
group: "phone-recent",
|
|
331
|
+
description: "Switch to the Dialer tab from the empty recent-calls state",
|
|
332
|
+
onActivate: () => setActiveTab("dialer")
|
|
333
|
+
});
|
|
334
|
+
return /* @__PURE__ */ jsxs(
|
|
335
|
+
"div",
|
|
336
|
+
{
|
|
337
|
+
"data-testid": "phone-shell",
|
|
338
|
+
className: "fixed inset-0 z-50 flex h-[100vh] flex-col overflow-hidden bg-bg pb-[var(--safe-area-bottom,0px)] pl-[var(--safe-area-left,0px)] pr-[var(--safe-area-right,0px)] pt-[var(--safe-area-top,0px)] supports-[height:100dvh]:h-[100dvh]",
|
|
339
|
+
children: [
|
|
340
|
+
/* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center justify-between px-3 py-2", children: [
|
|
341
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
342
|
+
/* @__PURE__ */ jsx(
|
|
343
|
+
Button,
|
|
344
|
+
{
|
|
345
|
+
ref: backAgent.ref,
|
|
346
|
+
variant: "ghost",
|
|
347
|
+
size: "icon",
|
|
348
|
+
className: "h-9 w-9 text-muted hover:text-txt",
|
|
349
|
+
onClick: exitToApps,
|
|
350
|
+
"aria-label": backLabel,
|
|
351
|
+
...backAgent.agentProps,
|
|
352
|
+
children: /* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4" })
|
|
353
|
+
}
|
|
354
|
+
),
|
|
355
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
356
|
+
/* @__PURE__ */ jsx("h1", { className: "text-base font-semibold text-txt", children: t("phone.title", { defaultValue: "Phone" }) }),
|
|
357
|
+
/* @__PURE__ */ jsx("p", { className: "sr-only text-xs-tight text-muted leading-none", children: t("phone.subtitle", {
|
|
358
|
+
defaultValue: "Dialer and recent calls"
|
|
359
|
+
}) })
|
|
360
|
+
] })
|
|
361
|
+
] }),
|
|
362
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-1", children: /* @__PURE__ */ jsx(
|
|
363
|
+
Button,
|
|
364
|
+
{
|
|
365
|
+
ref: contactsNavAgent.ref,
|
|
366
|
+
variant: "ghost",
|
|
367
|
+
size: "icon",
|
|
368
|
+
className: "h-9 w-9 text-muted hover:text-txt",
|
|
369
|
+
onClick: openContacts,
|
|
370
|
+
"aria-label": contactsLabel,
|
|
371
|
+
"data-testid": "phone-open-contacts",
|
|
372
|
+
...contactsNavAgent.agentProps,
|
|
373
|
+
children: /* @__PURE__ */ jsx(UsersIcon, { className: "h-4 w-4" })
|
|
374
|
+
}
|
|
375
|
+
) })
|
|
376
|
+
] }),
|
|
377
|
+
/* @__PURE__ */ jsxs(
|
|
378
|
+
Tabs,
|
|
379
|
+
{
|
|
380
|
+
value: activeTab,
|
|
381
|
+
onValueChange: (v) => setActiveTab(v),
|
|
382
|
+
className: "flex flex-1 min-h-0 flex-col",
|
|
383
|
+
children: [
|
|
384
|
+
/* @__PURE__ */ jsx("div", { className: "shrink-0 px-3 py-1", children: /* @__PURE__ */ jsxs(TabsList, { className: "grid w-full max-w-sm grid-cols-2 gap-1 bg-transparent", children: [
|
|
385
|
+
/* @__PURE__ */ jsx(
|
|
386
|
+
PhoneTabTrigger,
|
|
387
|
+
{
|
|
388
|
+
tab: "dialer",
|
|
389
|
+
label: t("phone.tabs.dialer", { defaultValue: "Dialer" }),
|
|
390
|
+
active: activeTab === "dialer"
|
|
391
|
+
}
|
|
392
|
+
),
|
|
393
|
+
/* @__PURE__ */ jsx(
|
|
394
|
+
PhoneTabTrigger,
|
|
395
|
+
{
|
|
396
|
+
tab: "recent",
|
|
397
|
+
label: t("phone.tabs.recent", { defaultValue: "Recent" }),
|
|
398
|
+
active: activeTab === "recent"
|
|
399
|
+
}
|
|
400
|
+
)
|
|
401
|
+
] }) }),
|
|
402
|
+
/* @__PURE__ */ jsx(
|
|
403
|
+
TabsContent,
|
|
404
|
+
{
|
|
405
|
+
value: "dialer",
|
|
406
|
+
className: "flex-1 overflow-y-auto focus-visible:outline-none",
|
|
407
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex min-h-full flex-col items-center px-4 pb-32 pt-6", children: [
|
|
408
|
+
/* @__PURE__ */ jsxs("div", { className: "flex w-full max-w-sm flex-col items-center gap-3 pt-2", children: [
|
|
409
|
+
/* @__PURE__ */ jsx(
|
|
410
|
+
"output",
|
|
411
|
+
{
|
|
412
|
+
className: "block min-h-[3rem] w-full select-text px-4 py-3 text-center font-mono text-2xl",
|
|
413
|
+
style: {
|
|
414
|
+
backgroundColor: "transparent",
|
|
415
|
+
border: "none",
|
|
416
|
+
color: "var(--text)"
|
|
417
|
+
},
|
|
418
|
+
"aria-live": "polite",
|
|
419
|
+
"aria-label": t("phone.dialer.display", {
|
|
420
|
+
defaultValue: "Number being dialed"
|
|
421
|
+
}),
|
|
422
|
+
children: dialerDisplay || /* @__PURE__ */ jsx("span", { className: "text-muted", children: t("phone.dialer.placeholder", {
|
|
423
|
+
defaultValue: "Enter a number"
|
|
424
|
+
}) })
|
|
425
|
+
}
|
|
426
|
+
),
|
|
427
|
+
callError && isPhonePermissionError(callError) ? /* @__PURE__ */ jsx(
|
|
428
|
+
PermissionRecoveryCallout,
|
|
429
|
+
{
|
|
430
|
+
permission: "phone",
|
|
431
|
+
title: t("phone.permissionTitle", {
|
|
432
|
+
defaultValue: "Phone access is off"
|
|
433
|
+
}),
|
|
434
|
+
description: callError,
|
|
435
|
+
onRetry: () => void refreshCalls(),
|
|
436
|
+
retryLabel: t("actions.retry", { defaultValue: "Try again" }),
|
|
437
|
+
className: "w-full max-w-sm",
|
|
438
|
+
testId: "phone-permission-callout"
|
|
439
|
+
}
|
|
440
|
+
) : callError ? /* @__PURE__ */ jsx("p", { className: "w-full text-center text-sm text-danger", children: callError }) : null
|
|
441
|
+
] }),
|
|
442
|
+
/* @__PURE__ */ jsx("div", { className: "grid w-full max-w-sm grid-cols-3 gap-3 py-4", children: DIAL_KEYS.map((key) => /* @__PURE__ */ jsx(PhoneDialKey, { digit: key, onPress: appendDigit }, key)) }),
|
|
443
|
+
/* @__PURE__ */ jsxs("div", { className: "grid w-full max-w-sm grid-cols-3 items-center gap-3 pb-4", children: [
|
|
444
|
+
/* @__PURE__ */ jsx(
|
|
445
|
+
"button",
|
|
446
|
+
{
|
|
447
|
+
ref: plusAgent.ref,
|
|
448
|
+
type: "button",
|
|
449
|
+
className: "h-12 text-lg font-semibold active:scale-95",
|
|
450
|
+
style: {
|
|
451
|
+
backgroundColor: "transparent",
|
|
452
|
+
border: "none",
|
|
453
|
+
color: "var(--text)"
|
|
454
|
+
},
|
|
455
|
+
onClick: appendPlus,
|
|
456
|
+
"data-testid": "phone-dial-plus",
|
|
457
|
+
"aria-label": intlLabel,
|
|
458
|
+
...plusAgent.agentProps,
|
|
459
|
+
children: "+"
|
|
460
|
+
}
|
|
461
|
+
),
|
|
462
|
+
/* @__PURE__ */ jsx(
|
|
463
|
+
"button",
|
|
464
|
+
{
|
|
465
|
+
ref: callAgent.ref,
|
|
466
|
+
type: "button",
|
|
467
|
+
onClick: onDialerCall,
|
|
468
|
+
disabled: calling || dialed.length === 0,
|
|
469
|
+
className: "flex h-14 items-center justify-center rounded-full transition-colors active:scale-95 disabled:opacity-50",
|
|
470
|
+
style: {
|
|
471
|
+
backgroundColor: "var(--accent)",
|
|
472
|
+
color: "var(--accent-foreground)"
|
|
473
|
+
},
|
|
474
|
+
onMouseEnter: (e) => {
|
|
475
|
+
e.currentTarget.style.backgroundColor = "var(--accent-hover)";
|
|
476
|
+
},
|
|
477
|
+
onMouseLeave: (e) => {
|
|
478
|
+
e.currentTarget.style.backgroundColor = "var(--accent)";
|
|
479
|
+
},
|
|
480
|
+
"aria-label": callLabel,
|
|
481
|
+
"data-testid": "phone-dial-call",
|
|
482
|
+
...callAgent.agentProps,
|
|
483
|
+
children: /* @__PURE__ */ jsx(PhoneIcon, { className: "h-6 w-6", "aria-hidden": true })
|
|
484
|
+
}
|
|
485
|
+
),
|
|
486
|
+
/* @__PURE__ */ jsx(
|
|
487
|
+
"button",
|
|
488
|
+
{
|
|
489
|
+
ref: backspaceAgent.ref,
|
|
490
|
+
type: "button",
|
|
491
|
+
className: "flex h-12 items-center justify-center active:scale-95 disabled:opacity-50",
|
|
492
|
+
style: {
|
|
493
|
+
backgroundColor: "transparent",
|
|
494
|
+
border: "none",
|
|
495
|
+
color: "var(--text)"
|
|
496
|
+
},
|
|
497
|
+
onClick: backspace,
|
|
498
|
+
disabled: dialed.length === 0,
|
|
499
|
+
"aria-label": backspaceLabel,
|
|
500
|
+
"data-testid": "phone-dial-backspace",
|
|
501
|
+
...backspaceAgent.agentProps,
|
|
502
|
+
children: /* @__PURE__ */ jsx(Delete, { className: "h-5 w-5", "aria-hidden": true })
|
|
503
|
+
}
|
|
504
|
+
)
|
|
505
|
+
] })
|
|
506
|
+
] })
|
|
507
|
+
}
|
|
508
|
+
),
|
|
509
|
+
/* @__PURE__ */ jsx(
|
|
510
|
+
TabsContent,
|
|
511
|
+
{
|
|
512
|
+
value: "recent",
|
|
513
|
+
className: "flex-1 overflow-hidden focus-visible:outline-none",
|
|
514
|
+
children: /* @__PURE__ */ jsxs("div", { className: "chat-native-scrollbar h-full overflow-y-auto px-4 pb-32 pt-3", children: [
|
|
515
|
+
callsError && isPhonePermissionError(callsError) ? /* @__PURE__ */ jsx(
|
|
516
|
+
PermissionRecoveryCallout,
|
|
517
|
+
{
|
|
518
|
+
permission: "phone",
|
|
519
|
+
title: t("phone.permissionTitle", {
|
|
520
|
+
defaultValue: "Phone access is off"
|
|
521
|
+
}),
|
|
522
|
+
description: callsError,
|
|
523
|
+
onRetry: refreshCalls,
|
|
524
|
+
retryLabel: t("actions.retry", { defaultValue: "Try again" }),
|
|
525
|
+
className: "mb-3",
|
|
526
|
+
testId: "phone-recent-permission-callout"
|
|
527
|
+
}
|
|
528
|
+
) : callsError ? /* @__PURE__ */ jsx("p", { className: "px-1 py-2 text-sm text-danger", children: callsError }) : null,
|
|
529
|
+
!callsError && calls.length === 0 && callsLoading ? /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center px-6 text-center text-sm text-muted", children: t("phone.recent.loading", {
|
|
530
|
+
defaultValue: "Loading recent calls\u2026"
|
|
531
|
+
}) }) : null,
|
|
532
|
+
!callsError && calls.length === 0 && !callsLoading ? /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center px-6 text-center", children: /* @__PURE__ */ jsxs("div", { className: "max-w-sm", children: [
|
|
533
|
+
/* @__PURE__ */ jsx(PhoneIcon, { className: "mx-auto h-12 w-12 text-muted" }),
|
|
534
|
+
/* @__PURE__ */ jsx("div", { className: "mt-3 text-sm font-medium text-txt", children: t("phone.recent.empty", {
|
|
535
|
+
defaultValue: "None"
|
|
536
|
+
}) }),
|
|
537
|
+
/* @__PURE__ */ jsx("p", { className: "sr-only mt-1 text-xs text-muted", children: t("phone.recent.emptyBody", {
|
|
538
|
+
defaultValue: "Recent incoming, outgoing, and missed calls will appear here after Android grants call-log access."
|
|
539
|
+
}) }),
|
|
540
|
+
/* @__PURE__ */ jsx("div", { className: "mt-4 flex justify-center gap-2", children: /* @__PURE__ */ jsx(
|
|
541
|
+
Button,
|
|
542
|
+
{
|
|
543
|
+
ref: emptyDialerAgent.ref,
|
|
544
|
+
variant: "ghost",
|
|
545
|
+
size: "sm",
|
|
546
|
+
onClick: () => setActiveTab("dialer"),
|
|
547
|
+
...emptyDialerAgent.agentProps,
|
|
548
|
+
children: t("phone.tabs.dialer", { defaultValue: "Dialer" })
|
|
549
|
+
}
|
|
550
|
+
) })
|
|
551
|
+
] }) }) : null,
|
|
552
|
+
/* @__PURE__ */ jsx("ul", { className: "flex flex-col gap-1", children: calls.map((entry) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(RecentCallButton, { entry, onCall: onCallEntry }) }, entry.id)) })
|
|
553
|
+
] })
|
|
554
|
+
}
|
|
555
|
+
)
|
|
556
|
+
]
|
|
557
|
+
}
|
|
558
|
+
)
|
|
559
|
+
]
|
|
560
|
+
}
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
export {
|
|
564
|
+
PhoneAppView
|
|
565
|
+
};
|
|
566
|
+
//# sourceMappingURL=PhoneAppView.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/PhoneAppView.tsx"],"sourcesContent":["/**\n * PhoneAppView — full-screen overlay for the Phone app.\n *\n * Two tabs:\n * - Dialer: number pad, in-progress display, place-call button.\n * - Recent: scrollable call log with type icon, name/number, timestamp.\n *\n * The address book lives in the separate Contacts view; the header \"Contacts\"\n * affordance navigates there via the `eliza:navigate:view` bus rather than\n * embedding a duplicate contacts pane here.\n *\n * The native dependency (`@elizaos/capacitor-phone`) is imported eagerly —\n * Capacitor's `registerPlugin` returns a proxy that resolves the web fallback\n * on web/iOS, so the import is safe even on non-Android platforms.\n */\n\nimport type { CallLogEntry, CallLogType } from \"@elizaos/capacitor-phone\";\nimport { Phone } from \"@elizaos/capacitor-phone\";\nimport type { OverlayAppContext } from \"@elizaos/ui\";\nimport { Button, useAgentElement } from \"@elizaos/ui\";\nimport { consumePendingPhoneNumber } from \"@elizaos/ui/app-navigate-view\";\nimport { PermissionRecoveryCallout } from \"@elizaos/ui/components\";\nimport {\n Tabs,\n TabsContent,\n TabsList,\n TabsTrigger,\n} from \"@elizaos/ui/components/ui/tabs\";\nimport {\n ArrowLeft,\n Delete,\n Phone as PhoneIcon,\n PhoneIncoming,\n PhoneMissed,\n PhoneOutgoing,\n Users as UsersIcon,\n Voicemail,\n} from \"lucide-react\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { callLabelFor, normalizeNumber } from \"./PhoneAppView.helpers.js\";\n\nconst DIAL_KEYS: readonly string[] = [\n \"1\",\n \"2\",\n \"3\",\n \"4\",\n \"5\",\n \"6\",\n \"7\",\n \"8\",\n \"9\",\n \"*\",\n \"0\",\n \"#\",\n];\n\ntype PhoneTab = \"dialer\" | \"recent\";\n\nfunction formatTimestamp(epochMs: number): string {\n const date = new Date(epochMs);\n if (Number.isNaN(date.getTime())) return \"\";\n const now = new Date();\n const sameDay =\n date.getFullYear() === now.getFullYear() &&\n date.getMonth() === now.getMonth() &&\n date.getDate() === now.getDate();\n if (sameDay) {\n return date.toLocaleTimeString(undefined, {\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n }\n return date.toLocaleString(undefined, {\n month: \"short\",\n day: \"numeric\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n}\n\nfunction callIconFor(type: CallLogType) {\n switch (type) {\n case \"incoming\":\n case \"answered_externally\":\n return <PhoneIncoming className=\"h-4 w-4 text-ok\" aria-hidden />;\n case \"outgoing\":\n return <PhoneOutgoing className=\"h-4 w-4 text-accent\" aria-hidden />;\n case \"missed\":\n case \"rejected\":\n case \"blocked\":\n return <PhoneMissed className=\"h-4 w-4 text-danger\" aria-hidden />;\n case \"voicemail\":\n return <Voicemail className=\"h-4 w-4 text-muted\" aria-hidden />;\n default:\n return <PhoneIcon className=\"h-4 w-4 text-muted\" aria-hidden />;\n }\n}\n\nfunction isPhonePermissionError(message: string): boolean {\n const normalized = message.toLowerCase();\n return (\n normalized.includes(\"permission\") ||\n normalized.includes(\"denied\") ||\n normalized.includes(\"access is needed\") ||\n normalized.includes(\"call_phone\") ||\n normalized.includes(\"read_call_log\")\n );\n}\n\nfunction PhoneTabTrigger({\n tab,\n label,\n active,\n disabled,\n}: {\n tab: PhoneTab;\n label: string;\n active: boolean;\n disabled?: boolean;\n}) {\n const { ref, agentProps } = useAgentElement<HTMLButtonElement>({\n id: `tab-${tab}`,\n role: \"tab\",\n label,\n group: \"phone-tabs\",\n status: active ? \"active\" : \"inactive\",\n description: `Switch to the ${label} tab`,\n });\n return (\n <TabsTrigger\n ref={ref}\n value={tab}\n disabled={disabled}\n aria-current={active ? \"true\" : undefined}\n className=\"font-semibold transition-colors\"\n style={{\n backgroundColor: \"transparent\",\n color: active ? \"var(--accent)\" : \"var(--muted)\",\n border: \"1px solid transparent\",\n }}\n {...agentProps}\n >\n {label}\n </TabsTrigger>\n );\n}\n\nfunction PhoneDialKey({\n digit,\n onPress,\n}: {\n digit: string;\n onPress: (digit: string) => void;\n}) {\n const { ref, agentProps } = useAgentElement<HTMLButtonElement>({\n id: `dial-key-${digit}`,\n role: \"button\",\n label: `Dial ${digit}`,\n group: \"phone-dialpad\",\n description: `Append ${digit} to the number being dialed`,\n });\n return (\n <button\n ref={ref}\n type=\"button\"\n className=\"h-16 text-2xl font-semibold transition active:scale-95 sm:h-20\"\n style={{\n backgroundColor: \"transparent\",\n color: \"var(--text)\",\n border: \"none\",\n }}\n onClick={() => onPress(digit)}\n aria-label={`Dial ${digit}`}\n data-testid={`phone-dial-key-${digit}`}\n {...agentProps}\n >\n {digit}\n </button>\n );\n}\n\nfunction RecentCallButton({\n entry,\n onCall,\n}: {\n entry: CallLogEntry;\n onCall: (number: string) => void;\n}) {\n const label = callLabelFor(entry);\n const showNumber =\n entry.cachedName && entry.cachedName.trim().length > 0\n ? entry.number\n : null;\n const { ref, agentProps } = useAgentElement<HTMLButtonElement>({\n id: `recent-call-${entry.id}`,\n role: \"list-item\",\n label: `Call ${label}`,\n group: \"phone-recent\",\n description: `Place a call to ${label}`,\n onActivate: () => onCall(entry.number),\n });\n return (\n <button\n ref={ref}\n type=\"button\"\n onClick={() => onCall(entry.number)}\n className=\"flex w-full items-center gap-3 px-2 py-2 text-left transition active:scale-[0.99]\"\n style={{\n backgroundColor: \"transparent\",\n border: \"none\",\n }}\n {...agentProps}\n >\n <span\n className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-full\"\n style={{ backgroundColor: \"var(--accent-subtle)\" }}\n >\n {callIconFor(entry.type)}\n </span>\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate text-sm font-semibold text-txt\">\n {label}\n </span>\n <span className=\"block truncate text-xs text-muted\">\n {showNumber ? `${showNumber} · ` : \"\"}\n {formatTimestamp(entry.date)}\n </span>\n </span>\n <PhoneIcon className=\"h-4 w-4 shrink-0 text-muted\" />\n </button>\n );\n}\n\nexport function PhoneAppView({ exitToApps, t }: OverlayAppContext) {\n const [activeTab, setActiveTab] = useState<PhoneTab>(\"dialer\");\n const [dialed, setDialed] = useState(\"\");\n const [calling, setCalling] = useState(false);\n const [callError, setCallError] = useState<string | null>(null);\n\n const [calls, setCalls] = useState<CallLogEntry[]>([]);\n const [callsLoading, setCallsLoading] = useState(false);\n const [callsError, setCallsError] = useState<string | null>(null);\n\n // Guards the lazy auto-load so an empty recent-calls result does not retrigger\n // the fetch forever (an empty list keeps `calls.length === 0`, which would\n // otherwise re-satisfy the effect's guard on every render → infinite reload).\n const recentAutoLoadedRef = useRef(false);\n\n const refreshCalls = useCallback(async () => {\n setCallsLoading(true);\n setCallsError(null);\n try {\n // Feature-gated: request phone access on first open (idempotent — already\n // granted never re-prompts). Tolerates older bridges without the request\n // path by falling through to listRecentCalls.\n const status = await Phone.requestPermissions().catch(() => null);\n if (status && status.phone !== \"granted\") {\n setCalls([]);\n setCallsError(\n \"Phone access is needed for recent calls and dialing. Grant it in your device settings, then retry.\",\n );\n return;\n }\n const { calls: fetched } = await Phone.listRecentCalls({ limit: 50 });\n setCalls(fetched);\n } catch (err) {\n setCallsError(err instanceof Error ? err.message : String(err));\n setCalls([]);\n } finally {\n setCallsLoading(false);\n }\n }, []);\n\n // Seed the dialer from a cross-view handoff (e.g. a Contacts \"Call\" control\n // that navigated here with a number). Single-shot: the number is consumed so\n // a later plain navigation to Phone does not re-seed a stale value.\n useEffect(() => {\n const pending = consumePendingPhoneNumber();\n if (pending) {\n setCallError(null);\n setDialed(pending);\n setActiveTab(\"dialer\");\n }\n }, []);\n\n // Lazy-load the recent-calls tab on first activation, then keep it fresh with\n // a quiet 20s poll while the tab is active (no manual Refresh control). The\n // poll is torn down when the tab changes or the view unmounts.\n useEffect(() => {\n if (activeTab !== \"recent\") return;\n if (!recentAutoLoadedRef.current && !callsLoading) {\n recentAutoLoadedRef.current = true;\n void refreshCalls();\n }\n const interval = setInterval(() => {\n void refreshCalls();\n }, 20_000);\n return () => clearInterval(interval);\n }, [activeTab, callsLoading, refreshCalls]);\n\n const appendDigit = useCallback((digit: string) => {\n setCallError(null);\n setDialed((prev) => `${prev}${digit}`);\n }, []);\n\n const appendPlus = useCallback(() => {\n setCallError(null);\n setDialed((prev) => (prev.length === 0 ? \"+\" : prev));\n }, []);\n\n const backspace = useCallback(() => {\n setCallError(null);\n setDialed((prev) => prev.slice(0, -1));\n }, []);\n\n const placeCall = useCallback(async (number: string) => {\n const normalized = normalizeNumber(number);\n if (!normalized) {\n setCallError(\"Enter a number to call.\");\n return;\n }\n setCalling(true);\n setCallError(null);\n try {\n await Phone.placeCall({ number: normalized });\n } catch (err) {\n setCallError(err instanceof Error ? err.message : String(err));\n } finally {\n setCalling(false);\n }\n }, []);\n\n const onDialerCall = useCallback(() => {\n void placeCall(dialed);\n }, [dialed, placeCall]);\n\n const onCallEntry = useCallback(\n (number: string) => {\n void placeCall(number);\n },\n [placeCall],\n );\n\n const dialerDisplay = useMemo(() => dialed || \"\", [dialed]);\n\n // Open the separate Contacts view via the navigation bus. Contacts live in\n // their own plugin/view; the Phone app links to them rather than embedding a\n // duplicate address book.\n const openContacts = useCallback(() => {\n if (typeof window === \"undefined\") return;\n window.dispatchEvent(\n new CustomEvent(\"eliza:navigate:view\", {\n detail: { viewId: \"contacts\", viewPath: \"/contacts\" },\n }),\n );\n }, []);\n\n const backLabel = t(\"nav.back\", { defaultValue: \"Back\" });\n const callLabel = t(\"phone.dialer.call\", { defaultValue: \"Call\" });\n const intlLabel = t(\"phone.dialer.intl\", {\n defaultValue: \"Insert + for international dialing\",\n });\n const backspaceLabel = t(\"phone.dialer.backspace\", {\n defaultValue: \"Delete digit\",\n });\n\n const backAgent = useAgentElement<HTMLButtonElement>({\n id: \"action-back\",\n role: \"button\",\n label: backLabel,\n group: \"phone-nav\",\n description: \"Leave the Phone app and return to the app grid\",\n });\n const contactsLabel = t(\"phone.tabs.contacts\", { defaultValue: \"Contacts\" });\n const contactsNavAgent = useAgentElement<HTMLButtonElement>({\n id: \"action-contacts\",\n role: \"button\",\n label: contactsLabel,\n group: \"phone-nav\",\n description: \"Open the Contacts app to browse the address book\",\n onActivate: openContacts,\n });\n const plusAgent = useAgentElement<HTMLButtonElement>({\n id: \"dial-plus\",\n role: \"button\",\n label: intlLabel,\n group: \"phone-dialer\",\n description: \"Insert a leading + for international dialing\",\n });\n const callAgent = useAgentElement<HTMLButtonElement>({\n id: \"action-call\",\n role: \"button\",\n label: callLabel,\n group: \"phone-dialer\",\n description: \"Place a call to the dialed number\",\n });\n const backspaceAgent = useAgentElement<HTMLButtonElement>({\n id: \"dial-backspace\",\n role: \"button\",\n label: backspaceLabel,\n group: \"phone-dialer\",\n description: \"Delete the last digit of the dialed number\",\n });\n const emptyDialerAgent = useAgentElement<HTMLButtonElement>({\n id: \"recent-empty-dialer\",\n role: \"button\",\n label: t(\"phone.tabs.dialer\", { defaultValue: \"Dialer\" }),\n group: \"phone-recent\",\n description: \"Switch to the Dialer tab from the empty recent-calls state\",\n onActivate: () => setActiveTab(\"dialer\"),\n });\n\n return (\n <div\n data-testid=\"phone-shell\"\n className=\"fixed inset-0 z-50 flex h-[100vh] flex-col overflow-hidden bg-bg pb-[var(--safe-area-bottom,0px)] pl-[var(--safe-area-left,0px)] pr-[var(--safe-area-right,0px)] pt-[var(--safe-area-top,0px)] supports-[height:100dvh]:h-[100dvh]\"\n >\n {/* Header */}\n <div className=\"flex shrink-0 items-center justify-between px-3 py-2\">\n <div className=\"flex items-center gap-3\">\n <Button\n ref={backAgent.ref}\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-9 w-9 text-muted hover:text-txt\"\n onClick={exitToApps}\n aria-label={backLabel}\n {...backAgent.agentProps}\n >\n <ArrowLeft className=\"h-4 w-4\" />\n </Button>\n <div>\n <h1 className=\"text-base font-semibold text-txt\">\n {t(\"phone.title\", { defaultValue: \"Phone\" })}\n </h1>\n <p className=\"sr-only text-xs-tight text-muted leading-none\">\n {t(\"phone.subtitle\", {\n defaultValue: \"Dialer and recent calls\",\n })}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-1\">\n <Button\n ref={contactsNavAgent.ref}\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-9 w-9 text-muted hover:text-txt\"\n onClick={openContacts}\n aria-label={contactsLabel}\n data-testid=\"phone-open-contacts\"\n {...contactsNavAgent.agentProps}\n >\n <UsersIcon className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n\n {/* Tabs */}\n <Tabs\n value={activeTab}\n onValueChange={(v: string) => setActiveTab(v as PhoneTab)}\n className=\"flex flex-1 min-h-0 flex-col\"\n >\n <div className=\"shrink-0 px-3 py-1\">\n <TabsList className=\"grid w-full max-w-sm grid-cols-2 gap-1 bg-transparent\">\n <PhoneTabTrigger\n tab=\"dialer\"\n label={t(\"phone.tabs.dialer\", { defaultValue: \"Dialer\" })}\n active={activeTab === \"dialer\"}\n />\n <PhoneTabTrigger\n tab=\"recent\"\n label={t(\"phone.tabs.recent\", { defaultValue: \"Recent\" })}\n active={activeTab === \"recent\"}\n />\n </TabsList>\n </div>\n\n {/* Dialer */}\n <TabsContent\n value=\"dialer\"\n className=\"flex-1 overflow-y-auto focus-visible:outline-none\"\n >\n <div className=\"flex min-h-full flex-col items-center px-4 pb-32 pt-6\">\n <div className=\"flex w-full max-w-sm flex-col items-center gap-3 pt-2\">\n <output\n className=\"block min-h-[3rem] w-full select-text px-4 py-3 text-center font-mono text-2xl\"\n style={{\n backgroundColor: \"transparent\",\n border: \"none\",\n color: \"var(--text)\",\n }}\n aria-live=\"polite\"\n aria-label={t(\"phone.dialer.display\", {\n defaultValue: \"Number being dialed\",\n })}\n >\n {dialerDisplay || (\n <span className=\"text-muted\">\n {t(\"phone.dialer.placeholder\", {\n defaultValue: \"Enter a number\",\n })}\n </span>\n )}\n </output>\n {callError && isPhonePermissionError(callError) ? (\n <PermissionRecoveryCallout\n permission=\"phone\"\n title={t(\"phone.permissionTitle\", {\n defaultValue: \"Phone access is off\",\n })}\n description={callError}\n onRetry={() => void refreshCalls()}\n retryLabel={t(\"actions.retry\", { defaultValue: \"Try again\" })}\n className=\"w-full max-w-sm\"\n testId=\"phone-permission-callout\"\n />\n ) : callError ? (\n <p className=\"w-full text-center text-sm text-danger\">\n {callError}\n </p>\n ) : null}\n </div>\n\n {/* Number pad */}\n <div className=\"grid w-full max-w-sm grid-cols-3 gap-3 py-4\">\n {DIAL_KEYS.map((key) => (\n <PhoneDialKey key={key} digit={key} onPress={appendDigit} />\n ))}\n </div>\n\n {/* Bottom row: + (long-press equivalent), call, backspace */}\n <div className=\"grid w-full max-w-sm grid-cols-3 items-center gap-3 pb-4\">\n <button\n ref={plusAgent.ref}\n type=\"button\"\n className=\"h-12 text-lg font-semibold active:scale-95\"\n style={{\n backgroundColor: \"transparent\",\n border: \"none\",\n color: \"var(--text)\",\n }}\n onClick={appendPlus}\n data-testid=\"phone-dial-plus\"\n aria-label={intlLabel}\n {...plusAgent.agentProps}\n >\n +\n </button>\n <button\n ref={callAgent.ref}\n type=\"button\"\n onClick={onDialerCall}\n disabled={calling || dialed.length === 0}\n className=\"flex h-14 items-center justify-center rounded-full transition-colors active:scale-95 disabled:opacity-50\"\n style={{\n backgroundColor: \"var(--accent)\",\n color: \"var(--accent-foreground)\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.backgroundColor = \"var(--accent-hover)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.backgroundColor = \"var(--accent)\";\n }}\n aria-label={callLabel}\n data-testid=\"phone-dial-call\"\n {...callAgent.agentProps}\n >\n <PhoneIcon className=\"h-6 w-6\" aria-hidden />\n </button>\n <button\n ref={backspaceAgent.ref}\n type=\"button\"\n className=\"flex h-12 items-center justify-center active:scale-95 disabled:opacity-50\"\n style={{\n backgroundColor: \"transparent\",\n border: \"none\",\n color: \"var(--text)\",\n }}\n onClick={backspace}\n disabled={dialed.length === 0}\n aria-label={backspaceLabel}\n data-testid=\"phone-dial-backspace\"\n {...backspaceAgent.agentProps}\n >\n <Delete className=\"h-5 w-5\" aria-hidden />\n </button>\n </div>\n </div>\n </TabsContent>\n\n {/* Recent */}\n <TabsContent\n value=\"recent\"\n className=\"flex-1 overflow-hidden focus-visible:outline-none\"\n >\n <div className=\"chat-native-scrollbar h-full overflow-y-auto px-4 pb-32 pt-3\">\n {callsError && isPhonePermissionError(callsError) ? (\n <PermissionRecoveryCallout\n permission=\"phone\"\n title={t(\"phone.permissionTitle\", {\n defaultValue: \"Phone access is off\",\n })}\n description={callsError}\n onRetry={refreshCalls}\n retryLabel={t(\"actions.retry\", { defaultValue: \"Try again\" })}\n className=\"mb-3\"\n testId=\"phone-recent-permission-callout\"\n />\n ) : callsError ? (\n <p className=\"px-1 py-2 text-sm text-danger\">{callsError}</p>\n ) : null}\n {!callsError && calls.length === 0 && callsLoading ? (\n <div className=\"flex h-full items-center justify-center px-6 text-center text-sm text-muted\">\n {t(\"phone.recent.loading\", {\n defaultValue: \"Loading recent calls…\",\n })}\n </div>\n ) : null}\n {!callsError && calls.length === 0 && !callsLoading ? (\n <div className=\"flex h-full items-center justify-center px-6 text-center\">\n <div className=\"max-w-sm\">\n <PhoneIcon className=\"mx-auto h-12 w-12 text-muted\" />\n <div className=\"mt-3 text-sm font-medium text-txt\">\n {t(\"phone.recent.empty\", {\n defaultValue: \"None\",\n })}\n </div>\n <p className=\"sr-only mt-1 text-xs text-muted\">\n {t(\"phone.recent.emptyBody\", {\n defaultValue:\n \"Recent incoming, outgoing, and missed calls will appear here after Android grants call-log access.\",\n })}\n </p>\n <div className=\"mt-4 flex justify-center gap-2\">\n <Button\n ref={emptyDialerAgent.ref}\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => setActiveTab(\"dialer\")}\n {...emptyDialerAgent.agentProps}\n >\n {t(\"phone.tabs.dialer\", { defaultValue: \"Dialer\" })}\n </Button>\n </div>\n </div>\n </div>\n ) : null}\n <ul className=\"flex flex-col gap-1\">\n {calls.map((entry) => (\n <li key={entry.id}>\n <RecentCallButton entry={entry} onCall={onCallEntry} />\n </li>\n ))}\n </ul>\n </div>\n </TabsContent>\n </Tabs>\n </div>\n );\n}\n"],"mappings":"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","names":[]}
|