bloby-bot 0.70.12 → 0.71.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/bin/cli.js +234 -48
- package/dist-bloby/assets/{bloby-DSNB0g4w.js → bloby-es6cZJzs.js} +6 -6
- package/dist-bloby/assets/globals-DBqwNiJV.css +2 -0
- package/dist-bloby/assets/{globals-B3cTbITX.js → globals-DN3F0CQE.js} +1 -1
- package/dist-bloby/assets/{highlighted-body-OFNGDK62-BLforpkr.js → highlighted-body-OFNGDK62-8PiOHw9p.js} +1 -1
- package/dist-bloby/assets/mermaid-GHXKKRXX-BJWX8urU.js +1 -0
- package/dist-bloby/assets/{onboard-Dn2Ws_G2.js → onboard-BKgy17OU.js} +1 -1
- package/dist-bloby/bloby.html +3 -3
- package/dist-bloby/onboard.html +3 -3
- package/package.json +3 -4
- package/scripts/install +156 -41
- package/scripts/install.ps1 +146 -29
- package/scripts/install.sh +156 -41
- package/shared/config.ts +37 -2
- package/shared/relay.ts +3 -1
- package/supervisor/channels/manager.ts +84 -44
- package/supervisor/channels/telegram.ts +57 -16
- package/supervisor/channels/types.ts +4 -1
- package/supervisor/channels/whatsapp.ts +57 -10
- package/supervisor/chat/OnboardWizard.tsx +0 -15
- package/supervisor/chat/src/components/Chat/AudioBubble.tsx +1 -1
- package/supervisor/chat/src/components/Chat/AuthedImage.tsx +16 -3
- package/supervisor/chat/src/components/Chat/BlobyImageCard.tsx +2 -2
- package/supervisor/chat/src/components/Chat/ImageLightbox.tsx +25 -8
- package/supervisor/chat/src/components/Chat/InputBar.tsx +62 -7
- package/supervisor/chat/src/components/Chat/MessageBubble.tsx +37 -18
- package/supervisor/chat/src/components/Chat/MessageList.tsx +3 -3
- package/supervisor/chat/src/hooks/useChat.ts +52 -0
- package/supervisor/chat/src/lib/authedFile.ts +24 -12
- package/supervisor/file-saver.ts +92 -19
- package/supervisor/harnesses/attachment-policy.ts +111 -0
- package/supervisor/harnesses/claude.ts +62 -15
- package/supervisor/harnesses/codex.ts +69 -43
- package/supervisor/harnesses/pi/index.ts +367 -112
- package/supervisor/harnesses/pi/providers/humanize-error.ts +27 -2
- package/supervisor/harnesses/pi/providers/retry.ts +31 -0
- package/supervisor/harnesses/pi/providers/stream-anthropic.ts +31 -3
- package/supervisor/harnesses/pi/providers/stream-google.ts +26 -3
- package/supervisor/harnesses/pi/providers/stream-openai-completions.ts +32 -9
- package/supervisor/harnesses/pi/providers/types.ts +29 -1
- package/supervisor/harnesses/pi/session.ts +143 -3
- package/supervisor/harnesses/pi/test-completion.ts +56 -0
- package/supervisor/harnesses/pi/tools/bash.ts +198 -22
- package/supervisor/harnesses/pi/tools/glob.ts +79 -0
- package/supervisor/harnesses/pi/tools/grep.ts +0 -0
- package/supervisor/harnesses/pi/tools/registry.ts +18 -6
- package/supervisor/harnesses/pi/tools/todo-write.ts +45 -0
- package/supervisor/harnesses/pi/tools/web-fetch.ts +129 -0
- package/supervisor/index.ts +93 -18
- package/supervisor/widget.js +19 -5
- package/worker/db.ts +2 -0
- package/worker/index.ts +18 -1
- package/worker/prompts/bloby-system-prompt-codex.txt +1 -1
- package/worker/prompts/bloby-system-prompt-pi.txt +6 -24
- package/worker/prompts/bloby-system-prompt.txt +1 -1
- package/workspace/client/src/components/Dashboard/DashboardPage.tsx +4 -117
- package/workspace/client/src/components/Dashboard/deleteme_placeholders.tsx +194 -0
- package/workspace/client/src/components/Layout/Sidebar.tsx +52 -30
- package/workspace/client/src/components/deleteme_onboarding/WorkspaceTour.tsx +25 -15
- package/workspace/client/src/components/deleteme_onboarding/tour-theme.css +24 -0
- package/workspace/skills/mac/SKILL.md +13 -4
- package/dist-bloby/assets/globals-DyeW509Y.css +0 -2
- package/dist-bloby/assets/mermaid-GHXKKRXX-C1H_fSCU.js +0 -1
- package/supervisor/public/headphones_spritesheet.webp +0 -0
- package/supervisor/public/spritesheet.webp +0 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ┌──────────────────────────────────────────────────────────────────┐
|
|
3
|
+
* │ EXAMPLE / PLACEHOLDER DASHBOARD WIDGETS — SAFE TO DELETE │
|
|
4
|
+
* └──────────────────────────────────────────────────────────────────┘
|
|
5
|
+
*
|
|
6
|
+
* Everything in this file is demo content to show what a Bloby workspace
|
|
7
|
+
* can look like. NONE of it is connected to real data.
|
|
8
|
+
*
|
|
9
|
+
* To start fresh with the user's real widgets:
|
|
10
|
+
* 1. Delete this entire file (deleteme_placeholders.tsx).
|
|
11
|
+
* 2. In DashboardPage.tsx, remove the `PlaceholderWidgets` import and the
|
|
12
|
+
* <PlaceholderWidgets /> usage.
|
|
13
|
+
* The dashboard will then be empty — ready for real apps/widgets.
|
|
14
|
+
*
|
|
15
|
+
* It is intentionally fully self-contained (own data, icons, styles, and the
|
|
16
|
+
* dismissible "these are examples" banner) so removal is a one-file delete.
|
|
17
|
+
*/
|
|
18
|
+
import { useState } from 'react';
|
|
19
|
+
import { Search, TrendingUp, Sparkles, X as XIcon } from 'lucide-react';
|
|
20
|
+
import { AreaChart, Area, BarChart, Bar, ResponsiveContainer } from 'recharts';
|
|
21
|
+
|
|
22
|
+
const DISMISS_KEY = 'bloby_example_widgets_dismissed';
|
|
23
|
+
|
|
24
|
+
const CARD = 'relative rounded-xl overflow-hidden';
|
|
25
|
+
const BORDER = 'absolute inset-0 rounded-xl bg-gradient-to-b from-white/[0.08] via-white/[0.02] to-transparent pointer-events-none';
|
|
26
|
+
const INNER = 'relative rounded-xl bg-[#141414] m-px p-3.5 h-full';
|
|
27
|
+
|
|
28
|
+
const rev = [{ v: 82 }, { v: 89 }, { v: 94 }, { v: 101 }, { v: 108 }, { v: 112 }, { v: 125 }];
|
|
29
|
+
const fol = [{ v: 12 }, { v: 18 }, { v: 9 }, { v: 24 }, { v: 31 }, { v: 19 }, { v: 27 }];
|
|
30
|
+
|
|
31
|
+
function StripeSvg() {
|
|
32
|
+
return <svg className="h-3.5 w-3.5 text-[#635BFF]" viewBox="0 0 24 24" fill="currentColor"><path d="M13.976 9.15c-2.172-.806-3.356-1.426-3.356-2.409 0-.831.683-1.305 1.901-1.305 2.227 0 4.515.858 6.09 1.631l.89-5.494C18.252.975 15.697 0 12.165 0 9.667 0 7.589.654 6.104 1.872 4.56 3.147 3.757 4.992 3.757 7.218c0 4.039 2.467 5.76 6.476 7.219 2.585.92 3.445 1.574 3.445 2.583 0 .98-.84 1.545-2.354 1.545-1.875 0-4.965-.921-6.99-2.109l-.9 5.555C5.175 22.99 8.385 24 11.714 24c2.641 0 4.843-.624 6.328-1.813 1.664-1.305 2.525-3.236 2.525-5.732 0-4.128-2.524-5.851-6.591-7.305z" /></svg>;
|
|
33
|
+
}
|
|
34
|
+
function GmailSvg() {
|
|
35
|
+
return <svg className="h-3.5 w-3.5 text-[#EA4335]" viewBox="0 0 24 24" fill="currentColor"><path d="M24 5.457v13.909c0 .904-.732 1.636-1.636 1.636h-3.819V11.73L12 16.64l-6.545-4.91v9.273H1.636A1.636 1.636 0 0 1 0 19.366V5.457c0-2.023 2.309-3.178 3.927-1.964L5.455 4.64 12 9.548l6.545-4.91 1.528-1.145C21.69 2.28 24 3.434 24 5.457z" /></svg>;
|
|
36
|
+
}
|
|
37
|
+
function XSvg() {
|
|
38
|
+
return <svg className="h-3.5 w-3.5 text-white" viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" /></svg>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Small "Example" tag shown on each placeholder card. */
|
|
42
|
+
function ExampleBadge() {
|
|
43
|
+
return (
|
|
44
|
+
<span className="inline-flex items-center text-[8.5px] font-bold uppercase tracking-wider text-muted-foreground/60 bg-white/[0.05] border border-white/[0.06] px-1.5 py-0.5 rounded">
|
|
45
|
+
Example
|
|
46
|
+
</span>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function PlaceholderWidgets() {
|
|
51
|
+
const [dismissed, setDismissed] = useState(() => {
|
|
52
|
+
try { return localStorage.getItem(DISMISS_KEY) === '1'; } catch { return false; }
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
function dismissBanner() {
|
|
56
|
+
setDismissed(true);
|
|
57
|
+
try { localStorage.setItem(DISMISS_KEY, '1'); } catch {}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<>
|
|
62
|
+
{/* "These are examples" banner — dismissible, remembers via localStorage */}
|
|
63
|
+
{!dismissed && (
|
|
64
|
+
<div className="relative flex items-start gap-3 mb-4 rounded-xl bg-[#141414] border border-white/[0.07] p-3.5">
|
|
65
|
+
<div
|
|
66
|
+
className="h-7 w-7 rounded-lg flex items-center justify-center shrink-0"
|
|
67
|
+
style={{ background: 'linear-gradient(135deg, #0166FF, #4AEEFF)' }}
|
|
68
|
+
>
|
|
69
|
+
<Sparkles className="h-4 w-4 text-white" />
|
|
70
|
+
</div>
|
|
71
|
+
<div className="flex-1 min-w-0">
|
|
72
|
+
<p className="text-xs font-bold">These are example widgets</p>
|
|
73
|
+
<p className="text-[11px] text-muted-foreground/70 mt-0.5 leading-snug">
|
|
74
|
+
Just a preview of what your workspace can look like — none of it is real data.
|
|
75
|
+
Ask Bloby to replace them with your own apps.
|
|
76
|
+
</p>
|
|
77
|
+
</div>
|
|
78
|
+
<button
|
|
79
|
+
type="button"
|
|
80
|
+
onClick={dismissBanner}
|
|
81
|
+
aria-label="Dismiss"
|
|
82
|
+
className="text-muted-foreground/50 hover:text-foreground transition-colors shrink-0 -mr-1 -mt-1 p-1"
|
|
83
|
+
>
|
|
84
|
+
<XIcon className="h-4 w-4" />
|
|
85
|
+
</button>
|
|
86
|
+
</div>
|
|
87
|
+
)}
|
|
88
|
+
|
|
89
|
+
<div className="grid grid-cols-3 gap-2.5">
|
|
90
|
+
|
|
91
|
+
{/* Stripe — 2 cols */}
|
|
92
|
+
<div className={`${CARD} col-span-2`}>
|
|
93
|
+
<div className={BORDER} />
|
|
94
|
+
<div className={INNER}>
|
|
95
|
+
<div className="flex items-center justify-between">
|
|
96
|
+
<div className="flex items-center gap-2">
|
|
97
|
+
<div className="h-7 w-7 rounded-lg bg-[#635BFF]/10 flex items-center justify-center"><StripeSvg /></div>
|
|
98
|
+
<span className="text-xs font-bold">Stripe</span>
|
|
99
|
+
<ExampleBadge />
|
|
100
|
+
</div>
|
|
101
|
+
<div className="flex items-center gap-1 text-emerald-500">
|
|
102
|
+
<TrendingUp className="h-3 w-3" />
|
|
103
|
+
<span className="text-[10px] font-bold">+12.5%</span>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
<p className="text-2xl font-bold tracking-tight mt-2">$12,480</p>
|
|
107
|
+
<p className="text-[10px] text-muted-foreground/50 mb-1">MRR</p>
|
|
108
|
+
<div className="h-12 overflow-hidden">
|
|
109
|
+
<ResponsiveContainer width="100%" height={48}>
|
|
110
|
+
<AreaChart data={rev}>
|
|
111
|
+
<defs>
|
|
112
|
+
<linearGradient id="sg" x1="0" y1="0" x2="1" y2="0"><stop offset="0%" stopColor="#0166FF" /><stop offset="50%" stopColor="#009AFE" /><stop offset="100%" stopColor="#4AEEFF" /></linearGradient>
|
|
113
|
+
<linearGradient id="sf" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stopColor="#009AFE" stopOpacity={0.12} /><stop offset="100%" stopColor="#009AFE" stopOpacity={0} /></linearGradient>
|
|
114
|
+
</defs>
|
|
115
|
+
<Area type="monotone" dataKey="v" stroke="url(#sg)" strokeWidth={1.5} fill="url(#sf)" />
|
|
116
|
+
</AreaChart>
|
|
117
|
+
</ResponsiveContainer>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
{/* X — 1 col */}
|
|
123
|
+
<div className={`${CARD} col-span-1`}>
|
|
124
|
+
<div className={BORDER} />
|
|
125
|
+
<div className={INNER}>
|
|
126
|
+
<div className="flex items-center gap-2 mb-2">
|
|
127
|
+
<div className="h-7 w-7 rounded-lg bg-white/[0.06] flex items-center justify-center"><XSvg /></div>
|
|
128
|
+
<span className="text-xs font-bold">X</span>
|
|
129
|
+
<ExampleBadge />
|
|
130
|
+
</div>
|
|
131
|
+
<p className="text-2xl font-bold tracking-tight">24.8K</p>
|
|
132
|
+
<div className="flex items-center gap-1 text-emerald-500 mb-1">
|
|
133
|
+
<TrendingUp className="h-2.5 w-2.5" />
|
|
134
|
+
<span className="text-[10px] font-bold">+1.4K</span>
|
|
135
|
+
</div>
|
|
136
|
+
<div className="h-10 overflow-hidden">
|
|
137
|
+
<ResponsiveContainer width="100%" height={40}>
|
|
138
|
+
<BarChart data={fol} barCategoryGap="25%">
|
|
139
|
+
<defs>
|
|
140
|
+
<linearGradient id="xg" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stopColor="#009AFE" stopOpacity={0.3} /><stop offset="100%" stopColor="#0166FF" stopOpacity={0.05} /></linearGradient>
|
|
141
|
+
</defs>
|
|
142
|
+
<Bar dataKey="v" fill="url(#xg)" radius={[2, 2, 0, 0]} />
|
|
143
|
+
</BarChart>
|
|
144
|
+
</ResponsiveContainer>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
{/* Gmail — 1 col */}
|
|
150
|
+
<div className={`${CARD} col-span-1`}>
|
|
151
|
+
<div className={BORDER} />
|
|
152
|
+
<div className={INNER}>
|
|
153
|
+
<div className="flex items-center gap-2 mb-2.5">
|
|
154
|
+
<div className="h-7 w-7 rounded-lg bg-[#EA4335]/10 flex items-center justify-center"><GmailSvg /></div>
|
|
155
|
+
<span className="text-xs font-bold">Gmail</span>
|
|
156
|
+
<ExampleBadge />
|
|
157
|
+
<span className="ml-auto text-[10px] text-muted-foreground/50">3 new</span>
|
|
158
|
+
</div>
|
|
159
|
+
{['Sarah Chen', 'Stripe', 'Alex R.'].map((n) => (
|
|
160
|
+
<div key={n} className="flex items-center gap-2 py-1.5">
|
|
161
|
+
<div className="h-5 w-5 rounded-full bg-white/[0.06] text-[9px] font-bold flex items-center justify-center shrink-0">{n[0]}</div>
|
|
162
|
+
<span className="text-[11px] truncate">{n}</span>
|
|
163
|
+
</div>
|
|
164
|
+
))}
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
{/* Research — 2 cols */}
|
|
169
|
+
<div className={`${CARD} col-span-2`}>
|
|
170
|
+
<div className={BORDER} />
|
|
171
|
+
<div className={INNER}>
|
|
172
|
+
<div className="flex items-center gap-2 mb-2.5">
|
|
173
|
+
<div className="h-7 w-7 rounded-lg bg-[#9235F9]/10 flex items-center justify-center"><Search className="h-3.5 w-3.5 text-[#9235F9]" /></div>
|
|
174
|
+
<span className="text-xs font-bold">Research</span>
|
|
175
|
+
<ExampleBadge />
|
|
176
|
+
<span className="ml-auto text-[10px] font-bold text-muted-foreground bg-white/[0.06] px-2 py-0.5 rounded-full">3</span>
|
|
177
|
+
</div>
|
|
178
|
+
{[
|
|
179
|
+
{ t: 'Competitor pricing', s: 'Done', c: 'text-emerald-500 bg-emerald-500/10' },
|
|
180
|
+
{ t: 'Market trends Q1', s: 'Done', c: 'text-emerald-500 bg-emerald-500/10' },
|
|
181
|
+
{ t: 'User feedback', s: 'Review', c: 'text-orange-400 bg-orange-400/10' },
|
|
182
|
+
].map((r) => (
|
|
183
|
+
<div key={r.t} className="flex items-center justify-between py-1.5">
|
|
184
|
+
<span className="text-[11px]">{r.t}</span>
|
|
185
|
+
<span className={`text-[9px] font-bold px-1.5 py-0.5 rounded-full ${r.c}`}>{r.s}</span>
|
|
186
|
+
</div>
|
|
187
|
+
))}
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
|
|
191
|
+
</div>
|
|
192
|
+
</>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { NavLink } from 'react-router';
|
|
2
|
+
import { motion } from 'framer-motion';
|
|
2
3
|
import {
|
|
3
4
|
LayoutDashboard,
|
|
4
5
|
AppWindow,
|
|
@@ -26,13 +27,13 @@ export default function Sidebar({ userName, botName = 'Bloby', backendStatus = '
|
|
|
26
27
|
return (
|
|
27
28
|
<aside className="flex flex-col h-full w-64 bg-transparent p-5 pt-8">
|
|
28
29
|
{/* Logo */}
|
|
29
|
-
<div className="flex items-center gap-2.5 mb-8">
|
|
30
|
+
<div className="flex items-center gap-2.5 mb-8 shrink-0">
|
|
30
31
|
<img src="/morphy.png" alt={botName} className="h-7 w-auto" />
|
|
31
32
|
<span className="font-bold text-lg" style={{ fontFamily: "'Space Grotesk', sans-serif" }}>{botName}</span>
|
|
32
33
|
</div>
|
|
33
34
|
|
|
34
35
|
{/* Greeting */}
|
|
35
|
-
<div className="mb-10">
|
|
36
|
+
<div className="mb-10 shrink-0">
|
|
36
37
|
<h1 className="text-4xl font-bold leading-[1.1]">
|
|
37
38
|
{getGreeting()}{firstName ? ',' : ''}
|
|
38
39
|
</h1>
|
|
@@ -51,16 +52,19 @@ export default function Sidebar({ userName, botName = 'Bloby', backendStatus = '
|
|
|
51
52
|
)}
|
|
52
53
|
</div>
|
|
53
54
|
|
|
54
|
-
{/* Navigation
|
|
55
|
-
|
|
55
|
+
{/* Navigation — min-h-0 lets this flex child shrink below its content height
|
|
56
|
+
(flex items default to min-height:auto), so the list scrolls instead of
|
|
57
|
+
pushing the status pill past the card's clipped bottom edge. */}
|
|
58
|
+
<nav className="flex-1 min-h-0 overflow-y-auto space-y-0.5">
|
|
56
59
|
<NavItem to="/" icon={LayoutDashboard} label="Dashboard" onNavigate={onNavigate} />
|
|
57
|
-
<NavItem to="/app1" icon={AppWindow} label="App 1"
|
|
58
|
-
<NavItem to="/research" icon={Search} label="Research"
|
|
59
|
-
<NavItem to="/whatelse" icon={CircleHelp} label="What Else?"
|
|
60
|
+
<NavItem to="/app1" icon={AppWindow} label="App 1" onNavigate={onNavigate} />
|
|
61
|
+
<NavItem to="/research" icon={Search} label="Research" onNavigate={onNavigate} />
|
|
62
|
+
<NavItem to="/whatelse" icon={CircleHelp} label="What Else?" onNavigate={onNavigate} />
|
|
60
63
|
</nav>
|
|
61
64
|
|
|
62
|
-
{/* Backend status
|
|
63
|
-
|
|
65
|
+
{/* Backend status — shrink-0 + mt keeps it pinned and visible below the
|
|
66
|
+
scrollable nav, never clipped by the sidebar card's rounded edge. */}
|
|
67
|
+
<div className="rounded-xl bg-[#282828] px-3.5 py-2.5 flex items-center gap-2 shrink-0 mt-3">
|
|
64
68
|
<span
|
|
65
69
|
className={`h-2 w-2 rounded-full shrink-0 ${
|
|
66
70
|
backendStatus === 'healthy' ? 'bg-emerald-500' : 'bg-orange-400 animate-pulse'
|
|
@@ -74,38 +78,56 @@ export default function Sidebar({ userName, botName = 'Bloby', backendStatus = '
|
|
|
74
78
|
);
|
|
75
79
|
}
|
|
76
80
|
|
|
81
|
+
// Shared-layout spring — the active decoration slides between items on nav change.
|
|
82
|
+
const activeSpring = { type: 'spring' as const, stiffness: 420, damping: 34 };
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The active-item decoration — a single clean recessed dark fill (no stroke, no
|
|
86
|
+
* border, no color). The shared `layoutId` makes it glide from the
|
|
87
|
+
* previously-active item to the new one.
|
|
88
|
+
*/
|
|
89
|
+
function ActiveDecoration() {
|
|
90
|
+
return (
|
|
91
|
+
<motion.span
|
|
92
|
+
layoutId="nav-active"
|
|
93
|
+
className="absolute inset-0 rounded-[12px]"
|
|
94
|
+
style={{
|
|
95
|
+
background: '#0a0a0c',
|
|
96
|
+
boxShadow: 'inset 0 1px 4px rgba(0,0,0,0.6)',
|
|
97
|
+
}}
|
|
98
|
+
transition={activeSpring}
|
|
99
|
+
/>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
77
103
|
function NavItem({
|
|
78
104
|
to,
|
|
79
105
|
icon: Icon,
|
|
80
106
|
label,
|
|
81
|
-
sub,
|
|
82
107
|
onNavigate,
|
|
83
108
|
}: {
|
|
84
109
|
to: string;
|
|
85
|
-
icon: React.ComponentType<{ className?: string }>;
|
|
110
|
+
icon: React.ComponentType<{ className?: string; strokeWidth?: number }>;
|
|
86
111
|
label: string;
|
|
87
|
-
sub?: string;
|
|
88
112
|
onNavigate?: () => void;
|
|
89
113
|
}) {
|
|
90
114
|
return (
|
|
91
|
-
<NavLink
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
{sub && <p className="text-[10px] text-muted-foreground/50 font-normal leading-tight mt-0.5">{sub}</p>}
|
|
108
|
-
</div>
|
|
115
|
+
<NavLink to={to} end onClick={onNavigate} className="block">
|
|
116
|
+
{({ isActive }) => (
|
|
117
|
+
<div
|
|
118
|
+
className={cn(
|
|
119
|
+
'relative flex items-center gap-3 w-full px-3 py-2.5 rounded-[12px] text-sm transition-colors',
|
|
120
|
+
// cult-ui dark-theme text colors: active white, inactive #6b6b6d
|
|
121
|
+
isActive
|
|
122
|
+
? 'text-white font-medium'
|
|
123
|
+
: 'text-[#6b6b6d] hover:text-zinc-400 hover:bg-white/[0.03]',
|
|
124
|
+
)}
|
|
125
|
+
>
|
|
126
|
+
{isActive && <ActiveDecoration />}
|
|
127
|
+
<Icon className="relative z-10 h-[19px] w-[19px] shrink-0" strokeWidth={1.5} />
|
|
128
|
+
<span className="relative z-10">{label}</span>
|
|
129
|
+
</div>
|
|
130
|
+
)}
|
|
109
131
|
</NavLink>
|
|
110
132
|
);
|
|
111
133
|
}
|
|
@@ -46,18 +46,19 @@ export default function WorkspaceTour({ disabled = false }: { disabled?: boolean
|
|
|
46
46
|
},
|
|
47
47
|
];
|
|
48
48
|
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
49
|
+
// Chat step: highlight the bottom-right corner of the viewport where the
|
|
50
|
+
// Bloby chat bubble lives. We point at a fixed 100×100 anchor (rendered
|
|
51
|
+
// below) instead of the bubble element itself, so it works even in
|
|
52
|
+
// dev:workspace mode where the widget isn't injected.
|
|
53
|
+
steps.push({
|
|
54
|
+
element: '#tour-chat-anchor',
|
|
55
|
+
popover: {
|
|
56
|
+
title: 'Chat',
|
|
57
|
+
description: 'Your Bloby lives down here. Click here to talk with your Bloby.',
|
|
58
|
+
side: 'left',
|
|
59
|
+
align: 'end',
|
|
60
|
+
},
|
|
61
|
+
});
|
|
61
62
|
|
|
62
63
|
steps.push({
|
|
63
64
|
popover: {
|
|
@@ -70,7 +71,7 @@ export default function WorkspaceTour({ disabled = false }: { disabled?: boolean
|
|
|
70
71
|
<p><strong>Bloby does more than build apps.</strong> It can research topics, manage files, run commands, and help with just about anything. Just ask.</p>
|
|
71
72
|
<p><strong>Bloby wakes up on its own.</strong> Every 30 minutes, it checks in and can take action proactively. You can adjust this anytime.</p>
|
|
72
73
|
<p><strong>Schedule anything.</strong> Ask things like "Every day at 6am, send me a briefing" or "In 40 minutes, remind me to call the dentist." Bloby handles cron jobs and one-time reminders.</p>
|
|
73
|
-
<p><strong>Install it on your phone.</strong> Bloby
|
|
74
|
+
<p><strong>Install it on your phone.</strong> Bloby works just like a normal app — you can add it to your phone's home screen and get notifications, and it only pings you when something actually matters. Ask Bloby to explain how to install it on your phone.</p>
|
|
74
75
|
</div>
|
|
75
76
|
`,
|
|
76
77
|
},
|
|
@@ -81,7 +82,7 @@ export default function WorkspaceTour({ disabled = false }: { disabled?: boolean
|
|
|
81
82
|
animate: true,
|
|
82
83
|
allowClose: true,
|
|
83
84
|
overlayColor: '#000',
|
|
84
|
-
overlayOpacity: 0.
|
|
85
|
+
overlayOpacity: 0.92,
|
|
85
86
|
stagePadding: 12,
|
|
86
87
|
stageRadius: 16,
|
|
87
88
|
popoverClass: 'bloby-tour-popover',
|
|
@@ -100,5 +101,14 @@ export default function WorkspaceTour({ disabled = false }: { disabled?: boolean
|
|
|
100
101
|
return () => clearTimeout(timer);
|
|
101
102
|
}, [disabled]);
|
|
102
103
|
|
|
103
|
-
|
|
104
|
+
// Invisible, non-interactive anchor pinned to the bottom-right 100×100 of the
|
|
105
|
+
// viewport. The chat tour step highlights this, so the overlay cutout frames
|
|
106
|
+
// the corner where the Bloby chat bubble sits.
|
|
107
|
+
return (
|
|
108
|
+
<div
|
|
109
|
+
id="tour-chat-anchor"
|
|
110
|
+
aria-hidden
|
|
111
|
+
style={{ position: 'fixed', right: 0, bottom: 0, width: 100, height: 100, pointerEvents: 'none', zIndex: 0 }}
|
|
112
|
+
/>
|
|
113
|
+
);
|
|
104
114
|
}
|
|
@@ -47,6 +47,10 @@
|
|
|
47
47
|
font-size: 12px !important;
|
|
48
48
|
padding: 6px 16px !important;
|
|
49
49
|
white-space: nowrap !important;
|
|
50
|
+
/* driver.css defaults footer buttons to `text-shadow: 1px 1px 0 #fff`, which
|
|
51
|
+
is invisible on its light theme but renders as a ghosted duplicate of the
|
|
52
|
+
"← Back" text on our dark button. Kill it. */
|
|
53
|
+
text-shadow: none !important;
|
|
50
54
|
}
|
|
51
55
|
|
|
52
56
|
.bloby-tour-popover .driver-popover-prev-btn:hover {
|
|
@@ -67,6 +71,26 @@
|
|
|
67
71
|
color: #f5f5f5 !important;
|
|
68
72
|
}
|
|
69
73
|
|
|
74
|
+
/* Blur the dimmed background. driver.js shows the highlighted element through an
|
|
75
|
+
SVG-cutout overlay (z-index 10000) and never lifts the element itself, so a
|
|
76
|
+
backdrop blur on the overlay would blur the highlight too. Fix: blur the
|
|
77
|
+
overlay's backdrop, then lift the active element above the overlay so it
|
|
78
|
+
stays crisp. The popover (z-index 1e9) is already above both. */
|
|
79
|
+
.driver-overlay {
|
|
80
|
+
-webkit-backdrop-filter: blur(4px) !important;
|
|
81
|
+
backdrop-filter: blur(4px) !important;
|
|
82
|
+
}
|
|
83
|
+
.driver-active-element {
|
|
84
|
+
position: relative !important;
|
|
85
|
+
z-index: 10001 !important;
|
|
86
|
+
}
|
|
87
|
+
/* The chat-step anchor is pinned to the viewport corner with position:fixed; the
|
|
88
|
+
generic lift above would force it to position:relative and drop it back into
|
|
89
|
+
document flow. Keep it fixed — the z-index lift still applies. */
|
|
90
|
+
#tour-chat-anchor.driver-active-element {
|
|
91
|
+
position: fixed !important;
|
|
92
|
+
}
|
|
93
|
+
|
|
70
94
|
.bloby-tour-popover .driver-popover-arrow-side-left,
|
|
71
95
|
.bloby-tour-popover .driver-popover-arrow-side-right,
|
|
72
96
|
.bloby-tour-popover .driver-popover-arrow-side-top,
|
|
@@ -162,7 +162,7 @@ If the `mac` skill isn't installed, none of this exists — so only emit `<mac_p
|
|
|
162
162
|
|
|
163
163
|
## Spoken-text rules
|
|
164
164
|
|
|
165
|
-
- **One
|
|
165
|
+
- **One short sentence by default — a headline, not a report.** Aim for ~12 words / a few seconds; add a second sentence only if it carries something the card can't. It's audio — the human is mid-task, don't make them stand still for a paragraph.
|
|
166
166
|
- **No markdown, no bullet lists, no enumerations.** TTS reads symbols literally and it sounds awful.
|
|
167
167
|
- **Refer to the human by name** if you know it — personal, costs nothing.
|
|
168
168
|
- **Acknowledge a card/action when you send one** ("Here it is.", "Pinned it up top.", "It's the gear, top-right.") so the visual feels connected to the voice.
|
|
@@ -184,6 +184,15 @@ The rule is symmetric: if the **voice alone** is the right answer, send **no car
|
|
|
184
184
|
| "Where do I cancel?" | *"Bottom-right, Bruno."* | `spotlight` + `point` |
|
|
185
185
|
| "What time is it in Tokyo?" | *"It's 8:14 PM in Tokyo."* | **nothing** — voice is enough |
|
|
186
186
|
|
|
187
|
+
### 🚫 The #2 mistake: reading back what you just did
|
|
188
|
+
|
|
189
|
+
When you **performed an action** (saved a note, set a reminder, sent a message, updated the dashboard), **name what you did** in a beat — but **don't recite the specifics you stored**. Confirm the deed; skip the contents. The human asked you to do it; they don't need the meeting, the date, the time, and the color read back at them. If those specifics are worth seeing, put them on a **card** and keep the voice short.
|
|
190
|
+
|
|
191
|
+
**❌ BAD:** *"Done, Bruno. Stuck a note on your dashboard — meeting with Daniel, Monday the fifteenth at two PM. It's the rose one, you'll spot it."*
|
|
192
|
+
**✓ GOOD:** *"Done, Bruno. Sticky note's on your dashboard."* (add a `stat` / `info` / `text` card if the specifics should be visible)
|
|
193
|
+
|
|
194
|
+
Same root rule as #1: **the voice carries the headline, the screen carries the detail** — for answers *and* for actions.
|
|
195
|
+
|
|
187
196
|
---
|
|
188
197
|
|
|
189
198
|
## Examples
|
|
@@ -249,7 +258,7 @@ The rule is symmetric: if the **voice alone** is the right answer, send **no car
|
|
|
249
258
|
|
|
250
259
|
## What Not To Do
|
|
251
260
|
|
|
252
|
-
- ❌ **No long monologues.**
|
|
261
|
+
- ❌ **No long monologues.** A headline, not a report — one short sentence; if there's more, let the card carry it.
|
|
253
262
|
- ❌ **No reading the card aloud.** Voice + card complement, never duplicate.
|
|
254
263
|
- ⚠️ **Proactive `point`/`spotlight` is mapped against the current screen you can't see** — only use it when you truly know the location, else send a card.
|
|
255
264
|
- ❌ **No external assets** in custom HTML — no network in the notch view.
|
|
@@ -263,8 +272,8 @@ The rule is symmetric: if the **voice alone** is the right answer, send **no car
|
|
|
263
272
|
## Reply Checklist
|
|
264
273
|
|
|
265
274
|
1. Did the message actually start with `[Mac]`? If not, don't use this skill.
|
|
266
|
-
2. Spoken text
|
|
267
|
-
3. 🚫 Does my speech recite what's already in my card
|
|
275
|
+
2. Spoken text a short headline (one sentence by default), no markdown, no enumerations?
|
|
276
|
+
3. 🚫 Does my speech recite what's already in my card, **or read back an action I just performed**? Rewrite it as a short lead-in / acknowledgement only.
|
|
268
277
|
4. If acting on screen, did I read the coordinates off **this turn's screenshot**, and set `screen` if multi-display?
|
|
269
278
|
5. Is my `<mac_actions>` value **valid JSON** (an array of objects, each with a `type`)?
|
|
270
279
|
6. For a `card`: did I check **PRESETS.md** first, and is `preset` a real lowercase name with valid `data`?
|