ava-engine 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.
- package/.env.example +63 -0
- package/CLA.md +43 -0
- package/COMMERCIAL-LICENSE.md +43 -0
- package/LICENSE +73 -0
- package/README.md +238 -2
- package/THIRD_PARTY_NOTICES.md +38 -0
- package/TRADEMARKS.md +31 -0
- package/bin/ava-engine.mjs +12 -0
- package/lib/asr.mjs +143 -0
- package/lib/cli/config.mjs +95 -0
- package/lib/cli/main.mjs +152 -0
- package/lib/cli/setup.mjs +219 -0
- package/lib/fetch-models.mjs +104 -0
- package/lib/guard.mjs +92 -0
- package/lib/models.mjs +47 -0
- package/lib/openrouter.mjs +153 -0
- package/lib/ours.mjs +766 -0
- package/lib/platforms.mjs +201 -0
- package/lib/state.mjs +150 -0
- package/lib/stt.mjs +22 -0
- package/lib/tts.mjs +178 -0
- package/lib/workflow.mjs +18 -0
- package/package.json +72 -4
- package/public/app.js +1708 -0
- package/public/demo.js +420 -0
- package/public/favicon.svg +1 -0
- package/public/fonts/OFL.txt +96 -0
- package/public/fonts/inter-tight-var.woff2 +0 -0
- package/public/fonts/inter-var.woff2 +0 -0
- package/public/fonts/plex-mono-400.woff2 +0 -0
- package/public/fonts/plex-mono-500.woff2 +0 -0
- package/public/img/cta-1600.avif +0 -0
- package/public/img/cta-1600.webp +0 -0
- package/public/img/cta-2400.avif +0 -0
- package/public/img/cta-2400.webp +0 -0
- package/public/img/cta-960.avif +0 -0
- package/public/img/cta-960.webp +0 -0
- package/public/img/flows-1600.avif +0 -0
- package/public/img/flows-1600.webp +0 -0
- package/public/img/flows-2400.avif +0 -0
- package/public/img/flows-2400.webp +0 -0
- package/public/img/flows-960.avif +0 -0
- package/public/img/flows-960.webp +0 -0
- package/public/img/hero-1600.avif +0 -0
- package/public/img/hero-1600.webp +0 -0
- package/public/img/hero-2400.avif +0 -0
- package/public/img/hero-2400.webp +0 -0
- package/public/img/hero-960.avif +0 -0
- package/public/img/hero-960.webp +0 -0
- package/public/img/p-csm-480.avif +0 -0
- package/public/img/p-csm-480.webp +0 -0
- package/public/img/p-csm-800.avif +0 -0
- package/public/img/p-csm-800.webp +0 -0
- package/public/img/p-docs-480.avif +0 -0
- package/public/img/p-docs-480.webp +0 -0
- package/public/img/p-docs-800.avif +0 -0
- package/public/img/p-docs-800.webp +0 -0
- package/public/img/p-quiet-480.avif +0 -0
- package/public/img/p-quiet-480.webp +0 -0
- package/public/img/p-quiet-800.avif +0 -0
- package/public/img/p-quiet-800.webp +0 -0
- package/public/img/p-tickets-480.avif +0 -0
- package/public/img/p-tickets-480.webp +0 -0
- package/public/img/p-tickets-800.avif +0 -0
- package/public/img/p-tickets-800.webp +0 -0
- package/public/img/p-trial-480.avif +0 -0
- package/public/img/p-trial-480.webp +0 -0
- package/public/img/p-trial-800.avif +0 -0
- package/public/img/p-trial-800.webp +0 -0
- package/public/img/sees-1600.avif +0 -0
- package/public/img/sees-1600.webp +0 -0
- package/public/img/sees-2400.avif +0 -0
- package/public/img/sees-2400.webp +0 -0
- package/public/img/sees-960.avif +0 -0
- package/public/img/sees-960.webp +0 -0
- package/public/img/speaks-1600.avif +0 -0
- package/public/img/speaks-1600.webp +0 -0
- package/public/img/speaks-2400.avif +0 -0
- package/public/img/speaks-2400.webp +0 -0
- package/public/img/speaks-960.avif +0 -0
- package/public/img/speaks-960.webp +0 -0
- package/public/img/voice-1600.avif +0 -0
- package/public/img/voice-1600.webp +0 -0
- package/public/img/voice-2400.avif +0 -0
- package/public/img/voice-2400.webp +0 -0
- package/public/img/voice-960.avif +0 -0
- package/public/img/voice-960.webp +0 -0
- package/public/index.html +440 -0
- package/public/landing.html +975 -0
- package/public/pcm-worklet.js +11 -0
- package/public/perceptionWorker.js +103 -0
- package/public/rtc.js +60 -0
- package/public/specialist.html +92 -0
- package/public/specialist.js +132 -0
- package/public/style.css +836 -0
- package/public/vendor/lenis.min.js +3 -0
- package/scripts/fetch-models.sh +15 -0
- package/server.mjs +502 -0
package/public/demo.js
ADDED
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* global S, updateStageLayout, observeNow, sendTurn, joinCall -- defined by app.js, loaded first */
|
|
3
|
+
/* ═══════════════════════════════════════════════════════════════════════════
|
|
4
|
+
DEMO MODE — scripted, self-recording showcase runs.
|
|
5
|
+
Renders realistic platform screens to a canvas (which Ava's vision reads),
|
|
6
|
+
drives the REAL agents with scripted user messages + deliberate human errors,
|
|
7
|
+
plays Ava's real voice, and records the whole tab to a downloadable .webm.
|
|
8
|
+
Activated with ?demo=simple|moderate|complex.
|
|
9
|
+
═══════════════════════════════════════════════════════════════════════════ */
|
|
10
|
+
(function () {
|
|
11
|
+
const params = new URLSearchParams(location.search);
|
|
12
|
+
const which = params.get("demo");
|
|
13
|
+
if (!which) return;
|
|
14
|
+
|
|
15
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
16
|
+
|
|
17
|
+
// ── canvas "screen" the agent sees + a demo banner ──
|
|
18
|
+
// main canvas = what's shared/recorded (sized to the stage). Screens draw into a
|
|
19
|
+
// fixed "site viewport" (siteCv), then get composited into a browser window.
|
|
20
|
+
let cv = document.createElement("canvas");
|
|
21
|
+
cv.width = 1600; cv.height = 900;
|
|
22
|
+
let g = cv.getContext("2d");
|
|
23
|
+
const SW = 1440, SH = 900;
|
|
24
|
+
const siteCv = document.createElement("canvas"); siteCv.width = SW; siteCv.height = SH;
|
|
25
|
+
const sg = siteCv.getContext("2d");
|
|
26
|
+
let stream = null;
|
|
27
|
+
|
|
28
|
+
// "Basewell" — a fictional backend platform drawn for the demo (light theme, orange accent)
|
|
29
|
+
const P = { bg: "#FBFAF8", side: "#F3F1ED", panel: "#FFFFFF", bd: "#E7E3DC", bd2: "#D9D3C9", card: "#FFFFFF", input: "#FFFFFF", tx: "#1A1714", mut: "#6B645B", fnt: "#9C948A", acc: "#F2541B", accInk: "#FFFFFF", amber: "#C2620A", red: "#D92D20", blue: "#2563EB",
|
|
30
|
+
btn2: "#FFFFFF", railOn: "rgba(26,23,20,0.07)", accTint: "rgba(242,84,27,0.10)", accLine: "rgba(242,84,27,0.32)", ok: "#15803D", okTint: "rgba(21,128,61,0.10)", okLine: "rgba(21,128,61,0.30)" };
|
|
31
|
+
const FONT = "'Inter','SF Pro Text',-apple-system,'Segoe UI',Roboto,Helvetica,Arial,sans-serif";
|
|
32
|
+
function rr(x, y, w, h, r) { r = Math.min(r, w / 2, h / 2); g.beginPath(); g.moveTo(x + r, y); g.arcTo(x + w, y, x + w, y + h, r); g.arcTo(x + w, y + h, x, y + h, r); g.arcTo(x, y + h, x, y, r); g.arcTo(x, y, x + w, y, r); g.closePath(); }
|
|
33
|
+
function txt(t, x, y, o = {}) { g.fillStyle = o.c || P.tx; g.font = `${o.w || 400} ${o.s || 14}px ${FONT}`; g.textAlign = o.a || "left"; g.textBaseline = "alphabetic"; g.fillText(t, x, y); g.textAlign = "left"; }
|
|
34
|
+
function box(x, y, w, h, r, o = {}) { rr(x, y, w, h, r); if (o.fill) { g.fillStyle = o.fill; g.fill(); } if (o.stroke) { g.strokeStyle = o.stroke; g.lineWidth = o.sw || 1; g.stroke(); } }
|
|
35
|
+
function input(x, y, w, h, val, o = {}) { box(x, y, w, h, 7, { fill: o.fill || P.input, stroke: o.stroke || P.bd2 }); txt(val || o.ph || "", x + 13, y + h / 2 + 5, { s: 14.5, c: val ? (o.tc || P.tx) : (o.phc || P.fnt) }); }
|
|
36
|
+
function btn(x, y, w, h, label, o = {}) { box(x, y, w, h, 7, { fill: o.fill || P.acc, stroke: o.stroke }); txt(label, x + w / 2, y + h / 2 + 5, { s: o.s || 14, w: 600, c: o.tx || P.accInk, a: "center" }); }
|
|
37
|
+
function mark(cx, cy, r) { // Basewell mark: an orange tile with three stacked "disk" bars
|
|
38
|
+
g.fillStyle = P.acc; rr(cx - r, cy - r, r * 2, r * 2, r * 0.45); g.fill();
|
|
39
|
+
g.fillStyle = "#fff";
|
|
40
|
+
[[-0.42, 1.1], [0, 0.82], [0.42, 0.54]].forEach(([dy, w]) => { rr(cx - r * w / 2 - r * 0.2, cy + dy * r - r * 0.12, r * w + r * 0.4, r * 0.24, r * 0.12); g.fill(); });
|
|
41
|
+
}
|
|
42
|
+
function icon(name, x, y, c) { // 20px line icons, top-left at x,y
|
|
43
|
+
g.strokeStyle = c; g.fillStyle = c; g.lineWidth = 1.6; g.lineJoin = "round"; g.lineCap = "round";
|
|
44
|
+
const s = (fn) => { g.beginPath(); fn(); g.stroke(); };
|
|
45
|
+
if (name === "home") s(() => { g.moveTo(x, y + 8); g.lineTo(x + 10, y); g.lineTo(x + 20, y + 8); g.moveTo(x + 2, y + 7); g.lineTo(x + 2, y + 19); g.lineTo(x + 18, y + 19); g.lineTo(x + 18, y + 7); });
|
|
46
|
+
else if (name === "table") { s(() => { g.rect(x, y + 1, 20, 18); g.moveTo(x, y + 7); g.lineTo(x + 20, y + 7); g.moveTo(x + 7, y + 1); g.lineTo(x + 7, y + 19); }); }
|
|
47
|
+
else if (name === "sql") s(() => { g.moveTo(x + 4, y + 4); g.lineTo(x, y + 10); g.lineTo(x + 4, y + 16); g.moveTo(x + 12, y + 4); g.lineTo(x + 20, y + 4); });
|
|
48
|
+
else if (name === "db") { s(() => { g.ellipse(x + 10, y + 3, 8, 3, 0, 0, 7); }); s(() => { g.moveTo(x + 2, y + 3); g.lineTo(x + 2, y + 16); g.ellipse(x + 10, y + 16, 8, 3, 0, 0, Math.PI); g.moveTo(x + 18, y + 16); g.lineTo(x + 18, y + 3); }); }
|
|
49
|
+
else if (name === "auth") { s(() => { g.arc(x + 7, y + 6, 4, 0, 7); }); s(() => { g.moveTo(x, y + 19); g.arc(x + 7, y + 19, 7, Math.PI, 0); }); s(() => { g.moveTo(x + 14, y + 8); g.lineTo(x + 20, y + 8); g.moveTo(x + 17, y + 8); g.lineTo(x + 17, y + 13); }); }
|
|
50
|
+
else if (name === "storage") s(() => { g.moveTo(x, y + 4); g.lineTo(x + 7, y + 4); g.lineTo(x + 9, y + 7); g.lineTo(x + 20, y + 7); g.lineTo(x + 20, y + 18); g.lineTo(x, y + 18); g.closePath(); });
|
|
51
|
+
else if (name === "fn") { g.font = "700 18px " + FONT; g.fillText("ƒ", x + 5, y + 17); }
|
|
52
|
+
else if (name === "realtime") s(() => { g.moveTo(x, y + 10); g.lineTo(x + 5, y + 10); g.lineTo(x + 8, y + 3); g.lineTo(x + 12, y + 17); g.lineTo(x + 15, y + 10); g.lineTo(x + 20, y + 10); });
|
|
53
|
+
else if (name === "gear") { s(() => { g.arc(x + 10, y + 10, 4, 0, 7); }); s(() => { g.arc(x + 10, y + 10, 9, 0, 7); }); }
|
|
54
|
+
g.lineWidth = 1;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// icon rail (64px) + secondary nav panel (240px)
|
|
58
|
+
function sbChrome(project, active) {
|
|
59
|
+
g.fillStyle = P.bg; g.fillRect(0, 0, cv.width, cv.height);
|
|
60
|
+
g.fillStyle = P.side; g.fillRect(0, 0, 64, cv.height);
|
|
61
|
+
mark(32, 40, 12);
|
|
62
|
+
const rail = ["home", "table", "sql", "db", "auth", "storage", "fn", "realtime"];
|
|
63
|
+
rail.forEach((n, i) => { const y = 96 + i * 52; if (n === active) { g.fillStyle = P.railOn; rr(12, y - 6, 40, 40, 9); g.fill(); } icon(n, 22, y, n === active ? P.tx : P.mut); });
|
|
64
|
+
icon("gear", 22, cv.height - 60, P.mut);
|
|
65
|
+
// secondary panel
|
|
66
|
+
g.fillStyle = P.panel; g.fillRect(64, 0, 240, cv.height); g.strokeStyle = P.bd; g.beginPath(); g.moveTo(304, 0); g.lineTo(304, cv.height); g.stroke();
|
|
67
|
+
txt(project, 84, 44, { s: 15, w: 600 });
|
|
68
|
+
txt("▾", 280, 44, { s: 12, c: P.mut, a: "right" });
|
|
69
|
+
const nav = ["Project Overview", "Tables", "Query", "Database", "Users & Auth", "Files", "Functions", "Live sync", "Usage", "Logs", "API", "Settings"];
|
|
70
|
+
nav.forEach((n, i) => { const y = 92 + i * 40, on = n === active; if (on) { g.fillStyle = P.accTint; rr(76, y - 18, 216, 34, 7); g.fill(); } txt(n, 92, y + 4, { s: 13.5, c: on ? P.acc : P.mut, w: on ? 600 : 400 }); });
|
|
71
|
+
}
|
|
72
|
+
function topbar(title, right) {
|
|
73
|
+
g.fillStyle = P.bg; g.fillRect(304, 0, cv.width - 304, 60); g.strokeStyle = P.bd; g.beginPath(); g.moveTo(304, 60); g.lineTo(cv.width, 60); g.stroke();
|
|
74
|
+
txt(title, 336, 38, { s: 15, w: 500, c: P.tx });
|
|
75
|
+
if (right) { btn(cv.width - 150, 15, 118, 32, right, { fill: P.btn2, tx: P.tx, stroke: P.bd2, s: 13 }); }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ── screens ──
|
|
79
|
+
function sbSignIn(o = {}) {
|
|
80
|
+
g.fillStyle = P.bg; g.fillRect(0, 0, cv.width, cv.height);
|
|
81
|
+
const cx = cv.width / 2, cw = 400, x = cx - cw / 2;
|
|
82
|
+
mark(cx, 150, 15);
|
|
83
|
+
txt("Welcome back", cx, 220, { s: 26, w: 700, a: "center" });
|
|
84
|
+
txt("Sign in to your account", cx, 250, { s: 15, c: P.mut, a: "center" });
|
|
85
|
+
let y = 290;
|
|
86
|
+
btn(x, y, cw, 44, "Continue with GitHub", { fill: P.btn2, tx: P.tx, stroke: P.bd2, s: 14 });
|
|
87
|
+
y += 58; btn(x, y, cw, 44, "Continue with SSO", { fill: P.btn2, tx: P.tx, stroke: P.bd2, s: 14 });
|
|
88
|
+
y += 66; g.strokeStyle = P.bd; g.beginPath(); g.moveTo(x, y); g.lineTo(x + cw, y); g.stroke(); g.fillStyle = P.bg; g.fillRect(cx - 18, y - 10, 36, 20); txt("or", cx, y + 5, { s: 13, c: P.mut, a: "center" });
|
|
89
|
+
y += 34; txt("Email", x, y, { s: 13, c: P.mut, w: 500 });
|
|
90
|
+
input(x, y + 10, cw, 44, o.email || "", { ph: "you@example.com", stroke: o.invalid ? P.red : P.bd2 });
|
|
91
|
+
y += 64; if (o.invalid) { txt("Please include an '@' in the email address.", x, y + 4, { s: 12.5, c: P.red, w: 500 }); y += 26; }
|
|
92
|
+
txt("Password", x, y, { s: 13, c: P.mut, w: 500 }); txt("Forgot Password?", x + cw, y, { s: 12.5, c: P.acc, a: "right" });
|
|
93
|
+
input(x, y + 10, cw, 44, o.pw || "••••••••••", {});
|
|
94
|
+
y += 74; btn(x, y, cw, 46, "Sign in", { s: 15 });
|
|
95
|
+
y += 78; txt("Don't have an account? Sign Up", cx, y, { s: 13.5, c: P.mut, a: "center" });
|
|
96
|
+
}
|
|
97
|
+
function sbOrgs() {
|
|
98
|
+
sbChrome("Select workspace", "Project Overview");
|
|
99
|
+
topbar("Workspaces");
|
|
100
|
+
txt("Your workspaces", 336, 118, { s: 22, w: 700 });
|
|
101
|
+
box(336, 150, cv.width - 400, 108, 12, { fill: P.panel, stroke: P.bd });
|
|
102
|
+
txt("You don't have any workspaces yet", 364, 200, { s: 16, w: 600 });
|
|
103
|
+
txt("Create a workspace to start building your project.", 364, 228, { s: 13.5, c: P.mut });
|
|
104
|
+
btn(cv.width - 260, 178, 190, 44, "+ New workspace", { s: 14 });
|
|
105
|
+
}
|
|
106
|
+
function sbNewOrg(o = {}) {
|
|
107
|
+
sbChrome("New workspace", "Project Overview");
|
|
108
|
+
topbar("Create a new workspace");
|
|
109
|
+
txt("Create a new workspace", 336, 122, { s: 22, w: 700 });
|
|
110
|
+
txt("This is your workspace on Basewell.", 336, 150, { s: 13.5, c: P.mut });
|
|
111
|
+
let y = 200; txt("Name", 336, y, { s: 13.5, c: P.mut, w: 500 });
|
|
112
|
+
input(336, y + 12, 460, 44, o.name || "", { ph: "Workspace name" });
|
|
113
|
+
y += 96; txt("Plan", 336, y, { s: 13.5, c: P.mut, w: 500 });
|
|
114
|
+
box(336, y + 12, 240, 44, 7, { fill: P.input, stroke: P.acc }); txt("Free — $0/month", 349, y + 40, { s: 14, w: 500 }); txt("▾", 556, y + 40, { s: 12, c: P.mut });
|
|
115
|
+
btn(336, y + 100, 180, 44, "Create workspace", { s: 14 });
|
|
116
|
+
}
|
|
117
|
+
function sbNewProject(o = {}) {
|
|
118
|
+
sbChrome("acme", "Project Overview");
|
|
119
|
+
topbar("New project");
|
|
120
|
+
txt("Create a new project", 336, 118, { s: 22, w: 700 });
|
|
121
|
+
txt("Your project will have its own dedicated instance and full Postgres database.", 336, 146, { s: 13, c: P.mut });
|
|
122
|
+
let y = 188;
|
|
123
|
+
txt("Workspace", 336, y, { s: 13, c: P.mut, w: 500 }); box(336, y + 10, 300, 42, 7, { fill: P.input, stroke: P.bd2 }); txt("acme", 349, y + 36, { s: 14 }); txt("▾", 616, y + 36, { s: 12, c: P.mut });
|
|
124
|
+
y += 84; txt("Project name", 336, y, { s: 13, c: P.mut, w: 500 }); input(336, y + 10, 520, 42, o.name || "acme-prod", {});
|
|
125
|
+
y += 84; txt("Database Password", 336, y, { s: 13, c: P.mut, w: 500 });
|
|
126
|
+
const weak = o.pw === "weak", strong = o.pw === "strong";
|
|
127
|
+
input(336, y + 10, 520, 42, o.pw ? "••••••••••••••••" : "", { ph: "Type a strong password", stroke: weak ? P.amber : strong ? P.ok : P.bd2 });
|
|
128
|
+
txt("Generate a password", 700, y, { s: 12.5, c: P.acc, a: "right" });
|
|
129
|
+
// strength meter
|
|
130
|
+
const segs = strong ? 4 : weak ? 1 : 0, cols = [P.red, P.amber, P.amber, P.ok];
|
|
131
|
+
for (let i = 0; i < 4; i++) { box(336 + i * 132, y + 62, 122, 5, 3, { fill: i < segs ? cols[segs - 1] : P.bd2 }); }
|
|
132
|
+
if (weak) txt("This password is too weak.", 336, y + 90, { s: 12.5, c: P.amber, w: 500 });
|
|
133
|
+
if (strong) txt("Strong — nice and secure.", 336, y + 90, { s: 12.5, c: P.ok, w: 500 });
|
|
134
|
+
y += 120; txt("Region", 336, y, { s: 13, c: P.mut, w: 500 }); box(336, y + 10, 300, 42, 7, { fill: P.input, stroke: P.bd2 }); txt("Southeast Asia (Singapore)", 349, y + 36, { s: 13.5 }); txt("▾", 616, y + 36, { s: 12, c: P.mut });
|
|
135
|
+
// footer
|
|
136
|
+
g.strokeStyle = P.bd; g.beginPath(); g.moveTo(304, cv.height - 74); g.lineTo(cv.width, cv.height - 74); g.stroke();
|
|
137
|
+
btn(cv.width - 340, cv.height - 60, 90, 40, "Cancel", { fill: P.btn2, tx: P.tx, stroke: P.bd2, s: 13.5 });
|
|
138
|
+
btn(cv.width - 240, cv.height - 60, 200, 40, "Create new project", { s: 14 });
|
|
139
|
+
}
|
|
140
|
+
function sbProvisioning() {
|
|
141
|
+
sbChrome("acme-prod", "Project Overview");
|
|
142
|
+
topbar("acme-prod", "Connect");
|
|
143
|
+
box(336, 120, cv.width - 400, 150, 12, { fill: P.panel, stroke: P.bd });
|
|
144
|
+
g.strokeStyle = P.bd2; g.lineWidth = 4; g.beginPath(); g.arc(400, 195, 22, 0, 7); g.stroke();
|
|
145
|
+
g.strokeStyle = P.acc; g.beginPath(); g.arc(400, 195, 22, -1.4, 1.2); g.stroke(); g.lineWidth = 1;
|
|
146
|
+
txt("Setting up your project", 448, 188, { s: 18, w: 600 });
|
|
147
|
+
txt("Provisioning your Postgres database — this usually takes a minute.", 448, 216, { s: 13.5, c: P.mut });
|
|
148
|
+
}
|
|
149
|
+
function sbDashboard() {
|
|
150
|
+
sbChrome("acme-prod", "Project Overview");
|
|
151
|
+
topbar("acme-prod", "Connect");
|
|
152
|
+
txt("Welcome to your project", 336, 118, { s: 22, w: 700 });
|
|
153
|
+
box(336, 138, 168, 34, 17, { fill: P.okTint, stroke: P.okLine });
|
|
154
|
+
g.fillStyle = P.ok; g.beginPath(); g.arc(358, 155, 5, 0, 7); g.fill(); txt("Project is healthy", 372, 160, { s: 13, c: P.ok, w: 600 });
|
|
155
|
+
// stat cards
|
|
156
|
+
const stats = [["Database", "Postgres 15.1", "healthy"], ["Auth", "0 users", "ready"], ["Storage", "0 files", "ready"], ["Live sync", "Connected", "live"]];
|
|
157
|
+
stats.forEach((s2, i) => { const x = 336 + i * 232, y = 196; box(x, y, 216, 104, 12, { fill: P.panel, stroke: P.bd }); txt(s2[0], x + 18, y + 34, { s: 15, w: 600 }); txt(s2[1], x + 18, y + 62, { s: 13.5, c: P.mut }); g.fillStyle = P.ok; g.beginPath(); g.arc(x + 22, y + 82, 4, 0, 7); g.fill(); txt(s2[2], x + 34, y + 86, { s: 12, c: P.ok }); });
|
|
158
|
+
// API keys card
|
|
159
|
+
const ay = 328; box(336, ay, cv.width - 400, 160, 12, { fill: P.panel, stroke: P.bd });
|
|
160
|
+
txt("Get your API keys", 360, ay + 34, { s: 16, w: 600 });
|
|
161
|
+
txt("Use these to connect your app to Basewell.", 360, ay + 60, { s: 13, c: P.mut });
|
|
162
|
+
txt("Project URL", 360, ay + 96, { s: 12, c: P.mut, w: 500 });
|
|
163
|
+
box(360, ay + 106, 460, 38, 7, { fill: P.input, stroke: P.bd2 }); txt("https://acme-prod.basewell.example", 373, ay + 130, { s: 13, c: P.tx });
|
|
164
|
+
txt("Public API key", 848, ay + 96, { s: 12, c: P.mut, w: 500 });
|
|
165
|
+
box(848, ay + 106, 460, 38, 7, { fill: P.input, stroke: P.bd2 }); txt("eyJhbGciOiJIUzI1NiIsInR5cCI6•••••••", 861, ay + 130, { s: 13, c: P.tx });
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ══════════ Stripe (light theme) ══════════
|
|
169
|
+
const SP = { bg: "#f6f9fc", side: "#ffffff", bd: "#e6ebf1", tx: "#1a1f36", mut: "#697386", pur: "#635bff", grn: "#3fb950", red: "#df1b41", amber: "#9a5b00", amberBg: "#fdecc8" };
|
|
170
|
+
function stChrome(active) {
|
|
171
|
+
g.fillStyle = SP.bg; g.fillRect(0, 0, cv.width, cv.height);
|
|
172
|
+
g.fillStyle = SP.side; g.fillRect(0, 0, 232, cv.height); g.strokeStyle = SP.bd; g.beginPath(); g.moveTo(232, 0); g.lineTo(232, cv.height); g.stroke();
|
|
173
|
+
txt("stripe", 28, 46, { s: 22, w: 700, c: SP.pur });
|
|
174
|
+
const nav = ["Home", "Balances", "Transactions", "Customers", "Product catalog", "Payments", "Connect", "Developers"];
|
|
175
|
+
nav.forEach((n, i) => { const y = 100 + i * 46, on = n === active; if (on) { g.fillStyle = "rgba(99,91,255,0.09)"; rr(12, y - 20, 208, 36, 8); g.fill(); } txt(n, 28, y + 4, { s: 14, c: on ? SP.pur : SP.mut, w: on ? 600 : 400 }); });
|
|
176
|
+
g.fillStyle = SP.side; g.fillRect(232, 0, cv.width - 232, 64); g.strokeStyle = SP.bd; g.beginPath(); g.moveTo(232, 64); g.lineTo(cv.width, 64); g.stroke();
|
|
177
|
+
box(cv.width - 210, 16, 170, 32, 16, { fill: SP.amberBg }); g.fillStyle = SP.amber; g.beginPath(); g.arc(cv.width - 186, 32, 5, 0, 7); g.fill(); txt("Test mode", cv.width - 170, 37, { s: 13, c: SP.amber, w: 700 });
|
|
178
|
+
}
|
|
179
|
+
function stDashboard() {
|
|
180
|
+
stChrome("Home"); txt("Today", 264, 116, { s: 24, w: 700, c: SP.tx });
|
|
181
|
+
box(264, 140, 360, 120, 10, { fill: "#fff", stroke: SP.bd }); txt("Gross volume", 288, 176, { s: 13, c: SP.mut, w: 500 }); txt("$0.00", 288, 214, { s: 30, w: 700, c: SP.tx }); txt("Test data", 288, 240, { s: 12, c: SP.amber, w: 600 });
|
|
182
|
+
box(648, 140, 360, 120, 10, { fill: "#fff", stroke: SP.bd }); txt("Payments", 672, 176, { s: 13, c: SP.mut, w: 500 }); txt("1 failed", 672, 214, { s: 30, w: 700, c: SP.red });
|
|
183
|
+
box(264, 288, cv.width - 304, 120, 10, { fill: "#fff", stroke: SP.bd }); txt("You're viewing test data", 288, 328, { s: 16, w: 600, c: SP.tx }); txt("Payments here use test cards and no real money moves. Toggle Test mode (top-right) off to go live.", 288, 356, { s: 13.5, c: SP.mut });
|
|
184
|
+
}
|
|
185
|
+
function stPayments() {
|
|
186
|
+
stChrome("Payments"); txt("Payments", 264, 116, { s: 24, w: 700, c: SP.tx });
|
|
187
|
+
box(264, 140, cv.width - 304, 320, 10, { fill: "#fff", stroke: SP.bd });
|
|
188
|
+
const cols = ["Amount", "Status", "Customer", "Date"]; cols.forEach((c, i) => txt(c, 288 + i * 250, 178, { s: 12, c: SP.mut, w: 600 }));
|
|
189
|
+
g.strokeStyle = SP.bd; g.beginPath(); g.moveTo(264, 196); g.lineTo(cv.width - 40, 196); g.stroke();
|
|
190
|
+
const rows = [["$49.00", "Failed", "test@acme.co", "Just now", SP.red], ["$20.00", "Succeeded", "jane@acme.co", "2:04 PM", SP.acc], ["$12.00", "Succeeded", "sam@acme.co", "1:40 PM", SP.acc]];
|
|
191
|
+
rows.forEach((r, i) => { const y = 234 + i * 56; txt(r[0], 288, y, { s: 14, w: 600, c: SP.tx }); box(538, y - 16, 90, 24, 12, { fill: r[4] === SP.red ? "#fce8ec" : "#e6f6ec" }); txt(r[1], 552, y, { s: 12, c: r[4], w: 600 }); txt(r[2], 788, y, { s: 13.5, c: SP.mut }); txt(r[3], 1038, y, { s: 13.5, c: SP.mut }); g.strokeStyle = SP.bd; g.beginPath(); g.moveTo(264, y + 20); g.lineTo(cv.width - 40, y + 20); g.stroke(); });
|
|
192
|
+
}
|
|
193
|
+
function stApiKeys() {
|
|
194
|
+
stChrome("Developers"); txt("API keys", 264, 116, { s: 24, w: 700, c: SP.tx }); txt("Test mode keys — safe to use in development.", 264, 144, { s: 13.5, c: SP.mut });
|
|
195
|
+
box(264, 172, cv.width - 304, 200, 10, { fill: "#fff", stroke: SP.bd });
|
|
196
|
+
txt("Publishable key", 288, 214, { s: 13, c: SP.mut, w: 600 }); box(288, 228, 560, 40, 7, { fill: SP.bg, stroke: SP.bd }); txt("pk_test_51Qw8kLZ2ab•••••••••••••••••", 302, 253, { s: 13.5, c: SP.tx });
|
|
197
|
+
txt("Secret key", 288, 308, { s: 13, c: SP.mut, w: 600 }); box(288, 322, 560, 40, 7, { fill: SP.bg, stroke: SP.bd }); txt("sk_test_51Qw8kLZ2•••••••••• Reveal", 302, 347, { s: 13.5, c: SP.tx });
|
|
198
|
+
btn(888, 228, 140, 40, "Reveal test key", { fill: SP.pur, tx: "#fff", s: 13 });
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ══════════ Google Workspace (light) ══════════
|
|
202
|
+
const GM = { bg: "#ffffff", panel: "#f6f8fc", red: "#ea4335", blue: "#1a73e8", tx: "#202124", mut: "#5f6368", bd: "#e0e3e7", chip: "#e8f0fe" };
|
|
203
|
+
function gmChrome(app) {
|
|
204
|
+
g.fillStyle = GM.bg; g.fillRect(0, 0, cv.width, cv.height);
|
|
205
|
+
g.fillStyle = "#fff"; g.fillRect(0, 0, cv.width, 68); g.strokeStyle = GM.bd; g.beginPath(); g.moveTo(0, 68); g.lineTo(cv.width, 68); g.stroke();
|
|
206
|
+
if (app === "gmail") { g.fillStyle = GM.red; box(28, 22, 30, 24, 5, { fill: GM.red }); txt("M", 34, 42, { s: 18, w: 700, c: "#fff" }); txt("Gmail", 68, 42, { s: 20, c: GM.mut }); }
|
|
207
|
+
else { box(28, 20, 30, 30, 6, { fill: "#fff", stroke: GM.bd }); txt("31", 34, 43, { s: 15, w: 700, c: GM.blue }); txt("Calendar", 68, 42, { s: 20, c: GM.mut }); }
|
|
208
|
+
box(320, 18, 640, 34, 17, { fill: GM.panel }); txt("Search " + (app === "gmail" ? "mail" : "calendar"), 344, 40, { s: 14, c: GM.mut });
|
|
209
|
+
}
|
|
210
|
+
function gmSidebar(active) {
|
|
211
|
+
box(24, 92, 150, 46, 23, { fill: "#c2e7ff" }); txt("✎ Compose", 46, 121, { s: 15, w: 600, c: GM.tx });
|
|
212
|
+
["Inbox", "Starred", "Sent", "Drafts", "Spam"].forEach((n, i) => { const y = 176 + i * 44, on = n === (active || "Inbox"); if (on) { g.fillStyle = "#d3e3fd"; rr(16, y - 20, 190, 36, 18); g.fill(); } txt(n, 46, y + 4, { s: 14, c: on ? GM.tx : GM.mut, w: on ? 700 : 400 }); });
|
|
213
|
+
}
|
|
214
|
+
function gmInbox(rows) {
|
|
215
|
+
gmChrome("gmail"); gmSidebar("Inbox");
|
|
216
|
+
let y = 96; (rows || []).forEach((r, i) => { g.fillStyle = i === 0 ? "#fff" : "#fff"; g.fillRect(232, y, cv.width - 232, 52); g.strokeStyle = GM.bd; g.beginPath(); g.moveTo(232, y + 52); g.lineTo(cv.width, y + 52); g.stroke(); txt(r[0], 300, y + 32, { s: 14, w: 700, c: GM.tx }); txt(r[1], 520, y + 32, { s: 14, c: GM.tx }); txt(" — " + r[2], 520 + g.measureText(r[1]).width, y + 32, { s: 14, c: GM.mut }); txt(r[3], cv.width - 60, y + 32, { s: 12.5, c: GM.mut, a: "right" }); y += 52; });
|
|
217
|
+
}
|
|
218
|
+
function gmEmail(o) {
|
|
219
|
+
gmChrome("gmail"); gmSidebar("Inbox");
|
|
220
|
+
g.fillStyle = "#fff"; g.fillRect(232, 84, cv.width - 232, cv.height - 84);
|
|
221
|
+
txt(o.subj, 300, 140, { s: 22, w: 500, c: GM.tx });
|
|
222
|
+
g.fillStyle = o.color || "#7b1fa2"; g.beginPath(); g.arc(320, 190, 20, 0, 7); g.fill(); txt(o.from[0], 314, 196, { s: 18, w: 600, c: "#fff" });
|
|
223
|
+
txt(o.from, 356, 184, { s: 14.5, w: 700, c: GM.tx }); txt("<" + o.addr + ">", 356 + g.measureText(o.from).width + 10, 184, { s: 13.5, c: GM.mut });
|
|
224
|
+
txt("to me " + o.time, 356, 208, { s: 12.5, c: GM.mut });
|
|
225
|
+
(o.body || []).forEach((line, i) => txt(line, 300, 264 + i * 30, { s: 15, c: "#3c4043" }));
|
|
226
|
+
btn(300, cv.height - 120, 130, 44, "↰ Reply", { fill: "#fff", tx: GM.blue, stroke: GM.bd, s: 14.5 });
|
|
227
|
+
btn(444, cv.height - 120, 130, 44, "↪ Forward", { fill: "#fff", tx: GM.mut, stroke: GM.bd, s: 14.5 });
|
|
228
|
+
}
|
|
229
|
+
function calWeek() {
|
|
230
|
+
gmChrome("cal");
|
|
231
|
+
btn(24, 92, 130, 48, "✎ Create", { fill: "#fff", tx: GM.tx, stroke: GM.bd, s: 15 });
|
|
232
|
+
txt("March 2026", 232, 116, { s: 22, w: 500, c: GM.tx });
|
|
233
|
+
const days = ["MON 9", "TUE 10", "WED 11", "THU 12", "FRI 13"];
|
|
234
|
+
days.forEach((d, i) => { const x = 240 + i * 230; txt(d, x, 168, { s: 13, c: GM.mut, w: 600 }); g.strokeStyle = GM.bd; g.beginPath(); g.moveTo(x - 10, 180); g.lineTo(x - 10, cv.height - 20); g.stroke(); });
|
|
235
|
+
// an existing event
|
|
236
|
+
box(240 + 2 * 230 - 6, 320, 210, 70, 8, { fill: "#d2e3fc" }); txt("Standup", 240 + 2 * 230 + 8, 348, { s: 13, w: 600, c: "#174ea6" }); txt("10:00 – 10:30", 240 + 2 * 230 + 8, 368, { s: 12, c: "#174ea6" });
|
|
237
|
+
}
|
|
238
|
+
function calEvent(o = {}) {
|
|
239
|
+
calWeek();
|
|
240
|
+
g.fillStyle = "rgba(0,0,0,0.28)"; g.fillRect(0, 0, cv.width, cv.height);
|
|
241
|
+
box(cv.width / 2 - 280, 180, 560, 460, 14, { fill: "#fff" });
|
|
242
|
+
const x = cv.width / 2 - 240; const li = { fill: "#fff", stroke: GM.bd, tc: GM.tx, phc: GM.mut };
|
|
243
|
+
input(x, 220, 480, 50, o.title || "", { ph: "Add title", ...li });
|
|
244
|
+
txt("Thursday, March 12 · 2:00 – 3:00pm", x, 320, { s: 14, c: GM.tx });
|
|
245
|
+
txt("Add guests", x, 372, { s: 13, c: GM.mut, w: 500 }); input(x, 384, 480, 44, o.guests || "", { ph: "Guest emails", ...li });
|
|
246
|
+
txt("Add Google Meet video conferencing", x, 470, { s: 13.5, c: GM.blue, w: 500 });
|
|
247
|
+
btn(cv.width / 2 + 120, 580, 130, 44, "Save", { fill: GM.blue, tx: "#fff", s: 14.5 });
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// draw the screen into the site viewport, then composite it as a browser window
|
|
251
|
+
// on a soft desktop backdrop that fills the whole canvas (no letterbox bars).
|
|
252
|
+
function render(fn, url) {
|
|
253
|
+
const mc = cv, mg = g; cv = siteCv; g = sg; sg.setTransform(1, 0, 0, 1, 0, 0); sg.clearRect(0, 0, SW, SH); fn(); cv = mc; g = mg;
|
|
254
|
+
const W = cv.width, H = cv.height, sAsp = SW / SH;
|
|
255
|
+
const bg = g.createLinearGradient(0, 0, 0, H); bg.addColorStop(0, "#eef1f6"); bg.addColorStop(1, "#dfe4ee"); g.fillStyle = bg; g.fillRect(0, 0, W, H);
|
|
256
|
+
const m = Math.round(Math.min(W, H) * 0.045), availW = W - 2 * m, availH = H - 2 * m;
|
|
257
|
+
let winW = availW, CH = winW * 0.05, winH = winW / sAsp + CH;
|
|
258
|
+
if (winH > availH) { winW = availH / (1 / sAsp + 0.05); CH = winW * 0.05; winH = availH; }
|
|
259
|
+
const wx = Math.round((W - winW) / 2), wy = Math.round((H - winH) / 2);
|
|
260
|
+
g.fillStyle = "rgba(18,22,38,0.20)"; rr(wx + 3, wy + 14, winW, winH, 16); g.fill();
|
|
261
|
+
g.fillStyle = "#fff"; rr(wx, wy, winW, winH, 16); g.fill();
|
|
262
|
+
g.fillStyle = "#e7eaf0"; rr(wx, wy, winW, CH, 16); g.fill(); g.fillRect(wx, wy + CH - 16, winW, 16);
|
|
263
|
+
["#ff5f57", "#febc2e", "#28c840"].forEach((c, i) => { g.fillStyle = c; g.beginPath(); g.arc(wx + 24 + i * 20, wy + CH / 2, 6, 0, 7); g.fill(); });
|
|
264
|
+
g.fillStyle = "#fff"; rr(wx + winW * 0.26, wy + CH * 0.24, winW * 0.48, CH * 0.52, CH * 0.26); g.fill();
|
|
265
|
+
g.fillStyle = "#6b7280"; g.font = `${Math.round(CH * 0.32)}px ${FONT}`; g.textAlign = "center"; g.fillText(url || "", wx + winW * 0.5, wy + CH * 0.62); g.textAlign = "left";
|
|
266
|
+
g.drawImage(siteCv, 0, 0, SW, SH, wx, wy + CH, winW, winH - CH);
|
|
267
|
+
}
|
|
268
|
+
let lastDraw = null;
|
|
269
|
+
function paint(fn) { lastDraw = fn; render(fn, ""); } // captureStream samples the canvas continuously
|
|
270
|
+
|
|
271
|
+
// ── demo banner overlay ──
|
|
272
|
+
const banner = document.createElement("div");
|
|
273
|
+
banner.id = "demoBanner";
|
|
274
|
+
banner.style.cssText = "position:fixed;top:10px;left:50%;transform:translateX(-50%);z-index:200;background:#111827;color:#fff;font:600 13px/1.3 'Space Grotesk',sans-serif;padding:9px 16px;border-radius:999px;box-shadow:0 8px 30px rgba(0,0,0,.35);display:none;align-items:center;gap:10px;max-width:80vw";
|
|
275
|
+
document.body.appendChild(banner);
|
|
276
|
+
const setBanner = (t) => { banner.style.display = "flex"; banner.innerHTML = `<span style="width:8px;height:8px;border-radius:50%;background:#e5484d;display:inline-block;animation:none"></span> `; banner.append(String(t)); };
|
|
277
|
+
|
|
278
|
+
// ── recorder (getDisplayMedia + MediaRecorder → .webm with audio) ──
|
|
279
|
+
let mediaRec = null, chunks = [];
|
|
280
|
+
async function startRec(name) {
|
|
281
|
+
let disp;
|
|
282
|
+
try { disp = await navigator.mediaDevices.getDisplayMedia({ video: { frameRate: 30, width: { ideal: 2560 }, height: { ideal: 1440 } }, audio: true, preferCurrentTab: true, selfBrowserSurface: "include" }); }
|
|
283
|
+
catch { return false; }
|
|
284
|
+
// ask the shared track for the highest resolution the surface can give
|
|
285
|
+
try { const vt = disp.getVideoTracks()[0]; await vt.applyConstraints({ width: { ideal: 2560 }, height: { ideal: 1440 }, frameRate: { ideal: 30 } }); } catch {}
|
|
286
|
+
// prefer MP4 (H.264/AAC) — supported in recent Chrome — else fall back to webm
|
|
287
|
+
const mime = ["video/mp4;codecs=avc1.640028,mp4a.40.2", "video/mp4;codecs=h264,aac", "video/mp4", "video/webm;codecs=vp9,opus", "video/webm;codecs=vp8,opus", "video/webm"].find((m) => MediaRecorder.isTypeSupported(m)) || "video/webm";
|
|
288
|
+
const ext = mime.indexOf("mp4") >= 0 ? "mp4" : "webm";
|
|
289
|
+
mediaRec = new MediaRecorder(disp, { mimeType: mime, videoBitsPerSecond: 12_000_000, audioBitsPerSecond: 128_000 });
|
|
290
|
+
chunks = [];
|
|
291
|
+
mediaRec.ondataavailable = (e) => { if (e.data && e.data.size) chunks.push(e.data); };
|
|
292
|
+
mediaRec.onstop = () => {
|
|
293
|
+
const blob = new Blob(chunks, { type: mime.split(";")[0] });
|
|
294
|
+
const url = URL.createObjectURL(blob);
|
|
295
|
+
const a = document.createElement("a"); a.href = url; a.download = `${name}.${ext}`; document.body.appendChild(a); a.click(); a.remove();
|
|
296
|
+
setTimeout(() => URL.revokeObjectURL(url), 4000);
|
|
297
|
+
disp.getTracks().forEach((t) => t.stop());
|
|
298
|
+
};
|
|
299
|
+
disp.getVideoTracks()[0].addEventListener("ended", () => { try { mediaRec.state !== "inactive" && mediaRec.stop(); } catch {} });
|
|
300
|
+
mediaRec.start();
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
function stopRec() { try { mediaRec && mediaRec.state !== "inactive" && mediaRec.stop(); } catch {} }
|
|
304
|
+
|
|
305
|
+
// ── share the canvas as Ava's screen ──
|
|
306
|
+
function shareCanvas() {
|
|
307
|
+
// size the canvas to the stage's exact aspect so the shared screen FILLS it
|
|
308
|
+
// (no black letterbox bars) — the site's own background fills any slack.
|
|
309
|
+
try { const sr = document.getElementById("stage").getBoundingClientRect(); if (sr.width > 200 && sr.height > 150) { cv.width = Math.round(sr.width * 2); cv.height = Math.round(sr.height * 2); } } catch {}
|
|
310
|
+
if (lastDraw) render(lastDraw, ""); // resizing clears the canvas — repaint the current screen
|
|
311
|
+
stream = cv.captureStream(4);
|
|
312
|
+
const v = document.getElementById("video");
|
|
313
|
+
v.srcObject = stream; v.classList.add("on");
|
|
314
|
+
S.stream = stream; S.sharing = true;
|
|
315
|
+
document.getElementById("privacy").hidden = false; document.getElementById("privwin").textContent = "the demo screen";
|
|
316
|
+
document.getElementById("stagelabel").textContent = "stage · demo screen";
|
|
317
|
+
document.getElementById("share").classList.add("armed"); document.getElementById("sharelabel").textContent = "Sharing demo screen";
|
|
318
|
+
if (typeof updateStageLayout === "function") updateStageLayout();
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// wait for Ava to finish the current turn + speaking (bounded)
|
|
322
|
+
async function settle(maxMs = 22000) {
|
|
323
|
+
const t0 = Date.now();
|
|
324
|
+
await sleep(500);
|
|
325
|
+
while (Date.now() - t0 < maxMs) { if (!S.turnInFlight && !S.speaking) break; await sleep(300); }
|
|
326
|
+
await sleep(400);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async function runStep(step) {
|
|
330
|
+
if (step.note) setBanner(step.note);
|
|
331
|
+
if (step.draw) { paint(step.draw); await sleep(step.settle || 1100); if (typeof observeNow === "function") { try { await observeNow(false); } catch {} } await sleep(700); }
|
|
332
|
+
if (step.say) { sendTurn(step.say); await settle(); }
|
|
333
|
+
await sleep(step.pause || 1200);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
async function run(scn) {
|
|
337
|
+
// pick agent mode + platform, voice on but NO mic
|
|
338
|
+
S.agentMode = scn.mode;
|
|
339
|
+
if (scn.platformId) S.platform = (S.platforms || []).find((p) => p.id === scn.platformId) || null;
|
|
340
|
+
const realMic = window.startLiveMic; window.startLiveMic = function () {}; // silence the mic for the demo
|
|
341
|
+
paint(scn.steps[0].draw || sbSignIn);
|
|
342
|
+
await joinCall("voice");
|
|
343
|
+
window.startLiveMic = realMic;
|
|
344
|
+
// captions on for readability; voice already on in voice mode
|
|
345
|
+
try { S.cc = true; document.getElementById("capwrap").hidden = false; document.getElementById("ccbtn").classList.add("on"); } catch {}
|
|
346
|
+
shareCanvas();
|
|
347
|
+
await settle(); // let the greeting play
|
|
348
|
+
for (const step of scn.steps) await runStep(step);
|
|
349
|
+
setBanner("Demo complete — saving the recording…");
|
|
350
|
+
await sleep(2500);
|
|
351
|
+
stopRec();
|
|
352
|
+
setBanner("Saved. You can stop sharing now.");
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// ── scenarios ──
|
|
356
|
+
const SCENARIOS = {
|
|
357
|
+
simple: {
|
|
358
|
+
name: "ava-demo-1-onboarding-simple", mode: "onboarding", platformId: "basewell",
|
|
359
|
+
steps: [
|
|
360
|
+
{ draw: () => sbSignIn({ email: "" }), note: "Onboarding · Basewell sign-in", settle: 1400, say: "hey, I need to get set up on basewell" },
|
|
361
|
+
{ draw: () => sbSignIn({ email: "alex.chenacme.co", invalid: true }), note: "Human error · typo in the email (missing @)", settle: 1600, say: "hmm it says something's wrong?" },
|
|
362
|
+
{ draw: () => sbOrgs(), note: "Signed in → create a workspace", settle: 1400, say: "ok fixed it, I'm signed in now" },
|
|
363
|
+
{ draw: () => sbNewOrg({ name: "acme" }), note: "Creating the workspace", settle: 1300, say: "made the workspace, what's next?" },
|
|
364
|
+
{ draw: () => sbNewProject({ pw: "weak" }), note: "Human confusion · weak password → RAG answers", settle: 1600, say: "what should I put for the password?" },
|
|
365
|
+
{ draw: () => sbNewProject({ pw: "strong" }), note: "Strong password set", settle: 1300, say: "done, I used generate" },
|
|
366
|
+
{ draw: () => sbProvisioning(), note: "Vision watches provisioning (no turn)", settle: 1800 },
|
|
367
|
+
{ draw: () => sbDashboard(), note: "Reached the dashboard — thinking marks it resolved", settle: 1500, say: "looks like it's ready now" },
|
|
368
|
+
],
|
|
369
|
+
},
|
|
370
|
+
moderate: {
|
|
371
|
+
name: "ava-demo-2-technical-issue-stripe", mode: "flexible", platformId: "stripe",
|
|
372
|
+
steps: [
|
|
373
|
+
{ draw: () => stDashboard(), note: "Technical issue · Stripe dashboard", settle: 1600, say: "hey, my payments aren't showing up in my real bank account" },
|
|
374
|
+
{ draw: () => stPayments(), note: "Human confusion · a $49 payment shows Failed", settle: 1700, say: "and look, this payment just failed — why?" },
|
|
375
|
+
{ draw: () => stApiKeys(), note: "RAG · where the API keys live", settle: 1500, say: "ok, where do I grab my API keys?" },
|
|
376
|
+
{ draw: () => stDashboard(), note: "RAG · how to actually go live", settle: 1500, say: "so how do I switch this to real payments?" },
|
|
377
|
+
],
|
|
378
|
+
},
|
|
379
|
+
complex: {
|
|
380
|
+
name: "ava-demo-3-flexibility-workspace", mode: "flexible", platformId: "google-workspace",
|
|
381
|
+
steps: [
|
|
382
|
+
{ draw: () => gmInbox([["The Daily Byte", "Your daily AI roundup", "three labs shipped agents", "9:12 AM"], ["Korean Air", "SkyPass mileage plan", "integrate your miles", "8:40 AM"], ["Anthropic", "Astra gains ground", "the model race tightens", "Yesterday"], ["GitHub", "CI passed on main", "build #482 green", "Mar 9"]]), note: "Flexibility · Gmail inbox", settle: 1700, say: "read me my latest email" },
|
|
383
|
+
{ draw: () => gmEmail({ from: "The Daily Byte", addr: "news@dailybyte.example", subj: "Your daily AI roundup", time: "9:12 AM", color: "#7b1fa2", body: ["Today in AI: three labs shipped agents,", "and a new model tops the leaderboard.", "Full stories inside."] }), note: "Vision reads the opened email", settle: 1600, say: "who's it from, and what's their email address?" },
|
|
384
|
+
{ draw: () => gmEmail({ from: "The Daily Byte", addr: "news@dailybyte.example", subj: "Your daily AI roundup", time: "9:12 AM", color: "#7b1fa2", body: ["Today in AI: three labs shipped agents,", "and a new model tops the leaderboard.", "Full stories inside."] }), note: "Human pivot · changes task mid-flow", settle: 1300, say: "actually never mind — help me add a meeting to my calendar instead" },
|
|
385
|
+
{ draw: () => calWeek(), note: "Pivoted to Google Calendar", settle: 1500, say: "ok I'm in calendar now, how do I add one?" },
|
|
386
|
+
{ draw: () => calEvent({}), note: "Human confusion · which field is which", settle: 1600, say: "wait, which box is the title?" },
|
|
387
|
+
{ draw: () => calEvent({ title: "Sync with Sarah", guests: "sarah@acme.co" }), note: "Filled it in — ready to save", settle: 1300, say: "done, added Sarah" },
|
|
388
|
+
{ draw: () => calWeek(), note: "Event created — thinking marks it resolved", settle: 1500, say: "is it on my calendar now?" },
|
|
389
|
+
],
|
|
390
|
+
},
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
// ── start overlay (one click = user gesture for getDisplayMedia + share picker) ──
|
|
394
|
+
function overlay(scn) {
|
|
395
|
+
const o = document.createElement("div");
|
|
396
|
+
o.style.cssText = "position:fixed;inset:0;z-index:300;background:rgba(11,10,25,.72);backdrop-filter:blur(4px);display:flex;align-items:center;justify-content:center";
|
|
397
|
+
o.innerHTML = `<div style="background:#fff;border-radius:16px;padding:30px 34px;max-width:440px;font-family:'IBM Plex Sans',sans-serif;box-shadow:0 30px 80px rgba(0,0,0,.4)">
|
|
398
|
+
<div style="font-family:'Space Grotesk';font-weight:600;font-size:19px;margin-bottom:8px">Demo · ${scn.name.replace(/^ava-demo-\d+-/, "").replace(/-/g, " ")}</div>
|
|
399
|
+
<p style="font-size:13.5px;line-height:1.55;color:#545B6B;margin:0 0 18px">Click start, then in the share dialog pick <b>This Tab</b> and turn on <b>“Also share tab audio”</b> so Ava's voice is recorded. It then runs hands-free and downloads a <b>.webm</b> when done.</p>
|
|
400
|
+
<button id="demoGo" style="height:46px;width:100%;border:0;border-radius:10px;background:#4F46E5;color:#fff;font-family:'Space Grotesk';font-weight:600;font-size:14px;cursor:pointer">Start & record</button>
|
|
401
|
+
</div>`;
|
|
402
|
+
document.body.appendChild(o);
|
|
403
|
+
o.querySelector("#demoGo").onclick = async () => {
|
|
404
|
+
o.querySelector("#demoGo").textContent = "Requesting screen + audio…";
|
|
405
|
+
const ok = await startRec(scn.name);
|
|
406
|
+
if (!ok) { o.querySelector("#demoGo").textContent = "Screen share was blocked — click to retry"; return; }
|
|
407
|
+
o.remove();
|
|
408
|
+
run(scn).catch((e) => setBanner("Demo error: " + (e && e.message)));
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// test hook: run the engine without the recorder (used to verify in environments
|
|
413
|
+
// where screen-share is blocked). Not used by the real ?demo flow.
|
|
414
|
+
window.__demo = { run, startRec, SCENARIOS, shareCanvas, paint, cv, screens: { sbSignIn, sbOrgs, sbNewOrg, sbNewProject, sbProvisioning, sbDashboard, stDashboard, stPayments, stApiKeys, gmInbox: () => gmInbox([["The Daily Byte", "Your daily AI roundup", "three labs shipped agents", "9:12 AM"], ["Korean Air", "SkyPass mileage plan", "integrate your miles", "8:40 AM"], ["Anthropic", "Astra gains ground", "the model race tightens", "Yesterday"]]), gmEmail: () => gmEmail({ from: "The Daily Byte", addr: "news@dailybyte.example", subj: "Your daily AI roundup", time: "9:12 AM", color: "#7b1fa2", body: ["Today in AI: three labs shipped agents,", "and a new model tops the leaderboard."] }), calWeek, calEvent: () => calEvent({ title: "Sync with Sarah", guests: "sarah@acme.co" }) } };
|
|
415
|
+
|
|
416
|
+
// wait for the app to boot (platforms loaded), then show the start overlay
|
|
417
|
+
const scn = SCENARIOS[which];
|
|
418
|
+
if (!scn) return;
|
|
419
|
+
(async () => { for (let i = 0; i < 60; i++) { if (Array.isArray(S.platforms) && S.platforms.length) break; await sleep(200); } overlay(scn); })();
|
|
420
|
+
})();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><rect width="24" height="24" rx="6" fill="#0A0A0C"/><path transform="translate(2.67 8.4) scale(.3)" d="M0 24L9.5 0L19 24M21.6 0L31.1 24L40.6 0M43.2 24L52.7 0L62.2 24" stroke="#fff" fill="none" stroke-width="3.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
Inter and Inter Tight
|
|
2
|
+
Copyright (c) 2016 The Inter Project Authors (https://github.com/rsms/inter)
|
|
3
|
+
|
|
4
|
+
IBM Plex Mono
|
|
5
|
+
Copyright (c) 2017 IBM Corp. with Reserved Font Name "Plex"
|
|
6
|
+
|
|
7
|
+
These Font Software files are licensed under the SIL Open Font License, Version 1.1.
|
|
8
|
+
This license is copied below, and is also available with a FAQ at:
|
|
9
|
+
https://openfontlicense.org
|
|
10
|
+
|
|
11
|
+
-----------------------------------------------------------
|
|
12
|
+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
13
|
+
-----------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
PREAMBLE
|
|
16
|
+
The goals of the Open Font License (OFL) are to stimulate worldwide
|
|
17
|
+
development of collaborative font projects, to support the font creation
|
|
18
|
+
efforts of academic and linguistic communities, and to provide a free and
|
|
19
|
+
open framework in which fonts may be shared and improved in partnership
|
|
20
|
+
with others.
|
|
21
|
+
|
|
22
|
+
The OFL allows the licensed fonts to be used, studied, modified and
|
|
23
|
+
redistributed freely as long as they are not sold by themselves. The
|
|
24
|
+
fonts, including any derivative works, can be bundled, embedded,
|
|
25
|
+
redistributed and/or sold with any software provided that any reserved
|
|
26
|
+
names are not used by derivative works. The fonts and derivatives,
|
|
27
|
+
however, cannot be released under any other type of license. The
|
|
28
|
+
requirement for fonts to remain under this license does not apply
|
|
29
|
+
to any document created using the fonts or their derivatives.
|
|
30
|
+
|
|
31
|
+
DEFINITIONS
|
|
32
|
+
"Font Software" refers to the set of files released by the Copyright
|
|
33
|
+
Holder(s) under this license and clearly marked as such. This may
|
|
34
|
+
include source files, build scripts and documentation.
|
|
35
|
+
|
|
36
|
+
"Reserved Font Name" refers to any names specified as such after the
|
|
37
|
+
copyright statement(s).
|
|
38
|
+
|
|
39
|
+
"Original Version" refers to the collection of Font Software components as
|
|
40
|
+
distributed by the Copyright Holder(s).
|
|
41
|
+
|
|
42
|
+
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
43
|
+
or substituting -- in part or in whole -- any of the components of the
|
|
44
|
+
Original Version, by changing formats or by porting the Font Software to a
|
|
45
|
+
new environment.
|
|
46
|
+
|
|
47
|
+
"Author" refers to any designer, engineer, programmer, technical
|
|
48
|
+
writer or other person who contributed to the Font Software.
|
|
49
|
+
|
|
50
|
+
PERMISSION & CONDITIONS
|
|
51
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
52
|
+
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
53
|
+
redistribute, and sell modified and unmodified copies of the Font
|
|
54
|
+
Software, subject to the following conditions:
|
|
55
|
+
|
|
56
|
+
1) Neither the Font Software nor any of its individual components,
|
|
57
|
+
in Original or Modified Versions, may be sold by itself.
|
|
58
|
+
|
|
59
|
+
2) Original or Modified Versions of the Font Software may be bundled,
|
|
60
|
+
redistributed and/or sold with any software, provided that each copy
|
|
61
|
+
contains the above copyright notice and this license. These can be
|
|
62
|
+
included either as stand-alone text files, human-readable headers or
|
|
63
|
+
in the appropriate machine-readable metadata fields within text or
|
|
64
|
+
binary files as long as those fields can be easily viewed by the user.
|
|
65
|
+
|
|
66
|
+
3) No Modified Version of the Font Software may use the Reserved Font
|
|
67
|
+
Name(s) unless explicit written permission is granted by the corresponding
|
|
68
|
+
Copyright Holder. This restriction only applies to the primary font name as
|
|
69
|
+
presented to the users.
|
|
70
|
+
|
|
71
|
+
4) The name(s) of the Copyright Holder(s) and the Author(s) of the Font
|
|
72
|
+
Software shall not be used to promote, endorse or advertise any
|
|
73
|
+
Modified Version, except to acknowledge the contribution(s) of the
|
|
74
|
+
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
75
|
+
permission.
|
|
76
|
+
|
|
77
|
+
5) The Font Software, modified or unmodified, in part or in whole,
|
|
78
|
+
must be distributed entirely under this license, and must not be
|
|
79
|
+
distributed under any other license. The requirement for fonts to
|
|
80
|
+
remain under this license does not apply to any document created
|
|
81
|
+
using the Font Software.
|
|
82
|
+
|
|
83
|
+
TERMINATION
|
|
84
|
+
This license becomes null and void if any of the above conditions are
|
|
85
|
+
not met.
|
|
86
|
+
|
|
87
|
+
DISCLAIMER
|
|
88
|
+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
89
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
90
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
91
|
+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
92
|
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
93
|
+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
94
|
+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
95
|
+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
|
96
|
+
OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|