bloby-bot 0.37.1 → 0.39.1
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/package.json +1 -1
- package/supervisor/harnesses/codex.ts +34 -0
- package/worker/prompts/bloby-system-prompt.txt +2 -2
- package/workspace/client/index.html +3 -0
- package/workspace/client/public/.well-known/assetlinks.json +8 -0
- package/workspace/client/public/bloby-cyberpunk.png +0 -0
- package/workspace/client/public/brand/blackrock.svg +8 -0
- package/workspace/client/public/kid-breakfast.png +0 -0
- package/workspace/client/public/wallpapers/bg.jpg +0 -0
- package/workspace/client/public/wallpapers/crypto_bg.png +0 -0
- package/workspace/client/public/wallpapers/wp-dusk.jpg +0 -0
- package/workspace/client/public/wallpapers/wp-mountain.jpg +0 -0
- package/workspace/client/public/wallpapers/wp-ocean.jpg +0 -0
- package/workspace/client/src/App.tsx +20 -9
- package/workspace/client/src/components/Dashboard/AiChatPage.tsx +145 -0
- package/workspace/client/src/components/Dashboard/CryptoPage.tsx +470 -0
- package/workspace/client/src/components/Dashboard/DashboardPage.tsx +117 -113
- package/workspace/client/src/components/Dashboard/WishlistPage.tsx +464 -0
- package/workspace/client/src/components/Layout/DashboardLayout.tsx +32 -34
- package/workspace/client/src/components/Layout/MiniSidebar.tsx +64 -0
- package/workspace/client/src/components/Layout/MobileNav.tsx +103 -6
- package/workspace/client/src/components/Layout/Sidebar.tsx +11 -10
- package/workspace/client/src/components/Lock/PinInput.tsx +107 -0
- package/workspace/client/src/components/Lock/WorkspaceLock.tsx +484 -0
- package/workspace/client/src/components/StickyNotes/StickyNotesOverlay.tsx +396 -0
- package/workspace/client/src/components/StickyNotes/StickyNotesSettingsPage.tsx +427 -0
- package/workspace/client/src/components/Wallpaper/WallpaperBackground.tsx +12 -0
- package/workspace/client/src/components/Wallpaper/WallpaperContext.tsx +160 -0
- package/workspace/client/src/components/Wallpaper/WallpaperPicker.tsx +67 -0
- package/workspace/client/src/styles/globals.css +89 -4
|
@@ -1,30 +1,127 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { NavLink } from 'react-router';
|
|
3
|
+
import { Menu, LayoutDashboard, StickyNote, MessageCircle, Heart, Settings, Bitcoin, X } from 'lucide-react';
|
|
3
4
|
import {
|
|
4
5
|
Sheet,
|
|
5
6
|
SheetContent,
|
|
6
7
|
SheetTitle,
|
|
7
8
|
} from '@/components/ui/sheet';
|
|
8
|
-
import
|
|
9
|
+
import WallpaperPicker from '../Wallpaper/WallpaperPicker';
|
|
10
|
+
import { cn } from '@/lib/utils';
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
interface Props {
|
|
13
|
+
userName?: string;
|
|
14
|
+
botName?: string;
|
|
15
|
+
backendStatus?: 'healthy' | 'restarting';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default function MobileNav({ userName, botName = 'Bloby', backendStatus = 'healthy' }: Props) {
|
|
11
19
|
const [open, setOpen] = useState(false);
|
|
20
|
+
const [pickerOpen, setPickerOpen] = useState(false);
|
|
21
|
+
const firstName = userName?.split(/\s+/)[0] || '';
|
|
22
|
+
|
|
23
|
+
const close = () => setOpen(false);
|
|
12
24
|
|
|
13
25
|
return (
|
|
14
26
|
<>
|
|
15
27
|
<button
|
|
16
28
|
onClick={() => setOpen(true)}
|
|
17
|
-
className="flex items-center justify-center h-10 w-10 rounded-lg text-
|
|
29
|
+
className="flex items-center justify-center h-10 w-10 rounded-lg text-white/80 hover:text-white transition-colors md:hidden"
|
|
18
30
|
>
|
|
19
31
|
<Menu className="h-5 w-5" />
|
|
20
32
|
<span className="sr-only">Open navigation</span>
|
|
21
33
|
</button>
|
|
22
34
|
<Sheet open={open} onOpenChange={setOpen}>
|
|
23
|
-
<SheetContent side="left" className="p-0 w-
|
|
35
|
+
<SheetContent side="left" className="p-0 w-72 bg-transparent border-none" showCloseButton={false}>
|
|
24
36
|
<SheetTitle className="sr-only">Navigation</SheetTitle>
|
|
25
|
-
<
|
|
37
|
+
<div className="h-full p-3">
|
|
38
|
+
<div className="glass-card h-full p-5 flex flex-col" style={{ borderRadius: 24 }}>
|
|
39
|
+
<div className="flex items-center justify-between mb-6">
|
|
40
|
+
<div>
|
|
41
|
+
<p className="text-xs text-white/60">Workspace</p>
|
|
42
|
+
<p className="text-lg font-semibold tracking-tight">{botName}</p>
|
|
43
|
+
</div>
|
|
44
|
+
<button
|
|
45
|
+
type="button"
|
|
46
|
+
onClick={close}
|
|
47
|
+
className="h-9 w-9 rounded-full glass-pill flex items-center justify-center text-white/80"
|
|
48
|
+
aria-label="Close navigation"
|
|
49
|
+
>
|
|
50
|
+
<X className="h-4 w-4" />
|
|
51
|
+
</button>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
{firstName && (
|
|
55
|
+
<p className="text-sm text-white/70 mb-4">Hi, {firstName}.</p>
|
|
56
|
+
)}
|
|
57
|
+
|
|
58
|
+
<nav className="flex-1 space-y-1">
|
|
59
|
+
<Item to="/" icon={LayoutDashboard} label="Dashboard" onNavigate={close} />
|
|
60
|
+
<Item to="/sticky-notes" icon={StickyNote} label="Sticky Notes" onNavigate={close} />
|
|
61
|
+
<Item to="/aichat" icon={MessageCircle} label="AI Chat" onNavigate={close} />
|
|
62
|
+
<Item to="/wishlist" icon={Heart} label="Wishlist" onNavigate={close} />
|
|
63
|
+
<Item to="/crypto" icon={Bitcoin} label="Crypto" onNavigate={close} />
|
|
64
|
+
|
|
65
|
+
<button
|
|
66
|
+
type="button"
|
|
67
|
+
onClick={() => {
|
|
68
|
+
setPickerOpen(true);
|
|
69
|
+
close();
|
|
70
|
+
}}
|
|
71
|
+
className="w-full mt-2 flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm text-white/80 hover:text-white hover:bg-white/10 transition"
|
|
72
|
+
>
|
|
73
|
+
<Settings className="h-[18px] w-[18px]" />
|
|
74
|
+
<span>Wallpaper</span>
|
|
75
|
+
</button>
|
|
76
|
+
</nav>
|
|
77
|
+
|
|
78
|
+
<div className="rounded-xl glass-pill px-3.5 py-2.5 flex items-center gap-2 mt-4">
|
|
79
|
+
<span
|
|
80
|
+
className={`h-2 w-2 rounded-full shrink-0 ${
|
|
81
|
+
backendStatus === 'healthy' ? 'bg-emerald-400' : 'bg-orange-400 animate-pulse'
|
|
82
|
+
}`}
|
|
83
|
+
/>
|
|
84
|
+
<span className="text-xs text-white/70">
|
|
85
|
+
Workspace: {backendStatus === 'healthy' ? 'Live' : 'Restarting'}
|
|
86
|
+
</span>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
26
90
|
</SheetContent>
|
|
27
91
|
</Sheet>
|
|
92
|
+
|
|
93
|
+
<WallpaperPicker open={pickerOpen} onClose={() => setPickerOpen(false)} />
|
|
28
94
|
</>
|
|
29
95
|
);
|
|
30
96
|
}
|
|
97
|
+
|
|
98
|
+
function Item({
|
|
99
|
+
to,
|
|
100
|
+
icon: Icon,
|
|
101
|
+
label,
|
|
102
|
+
onNavigate,
|
|
103
|
+
}: {
|
|
104
|
+
to: string;
|
|
105
|
+
icon: React.ComponentType<{ className?: string }>;
|
|
106
|
+
label: string;
|
|
107
|
+
onNavigate: () => void;
|
|
108
|
+
}) {
|
|
109
|
+
return (
|
|
110
|
+
<NavLink
|
|
111
|
+
to={to}
|
|
112
|
+
end
|
|
113
|
+
onClick={onNavigate}
|
|
114
|
+
className={({ isActive }) =>
|
|
115
|
+
cn(
|
|
116
|
+
'flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm transition',
|
|
117
|
+
isActive
|
|
118
|
+
? 'bg-white/15 text-white font-medium'
|
|
119
|
+
: 'text-white/70 hover:text-white hover:bg-white/10',
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
>
|
|
123
|
+
<Icon className="h-[18px] w-[18px]" />
|
|
124
|
+
<span>{label}</span>
|
|
125
|
+
</NavLink>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { NavLink } from 'react-router';
|
|
2
2
|
import {
|
|
3
3
|
LayoutDashboard,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
StickyNote,
|
|
5
|
+
MessageCircle,
|
|
6
|
+
Heart,
|
|
7
|
+
Bitcoin,
|
|
7
8
|
} from 'lucide-react';
|
|
8
9
|
import { cn } from '@/lib/utils';
|
|
9
10
|
|
|
@@ -26,9 +27,8 @@ 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
|
|
30
|
-
<img src="/
|
|
31
|
-
<span className="font-bold text-lg" style={{ fontFamily: "'Space Grotesk', sans-serif" }}>{botName}</span>
|
|
30
|
+
<div className="flex items-center mb-8">
|
|
31
|
+
<img src="/brand/blackrock.svg" alt="BlackRock" className="h-6 w-auto" />
|
|
32
32
|
</div>
|
|
33
33
|
|
|
34
34
|
{/* Greeting */}
|
|
@@ -40,7 +40,7 @@ export default function Sidebar({ userName, botName = 'Bloby', backendStatus = '
|
|
|
40
40
|
<h2
|
|
41
41
|
className="text-4xl font-bold mt-0.5 tracking-tight leading-[1.08] w-fit"
|
|
42
42
|
style={{
|
|
43
|
-
backgroundImage: 'linear-gradient(to right, #
|
|
43
|
+
backgroundImage: 'linear-gradient(to right, #ED1C24, #FF6B35, #C41E3A)',
|
|
44
44
|
WebkitBackgroundClip: 'text',
|
|
45
45
|
WebkitTextFillColor: 'transparent',
|
|
46
46
|
backgroundClip: 'text',
|
|
@@ -54,9 +54,10 @@ export default function Sidebar({ userName, botName = 'Bloby', backendStatus = '
|
|
|
54
54
|
{/* Navigation */}
|
|
55
55
|
<nav className="flex-1 space-y-0.5">
|
|
56
56
|
<NavItem to="/" icon={LayoutDashboard} label="Dashboard" onNavigate={onNavigate} />
|
|
57
|
-
<NavItem to="/
|
|
58
|
-
<NavItem to="/
|
|
59
|
-
<NavItem to="/
|
|
57
|
+
<NavItem to="/sticky-notes" icon={StickyNote} label="Sticky Notes" onNavigate={onNavigate} />
|
|
58
|
+
<NavItem to="/aichat" icon={MessageCircle} label="AI Chat" onNavigate={onNavigate} />
|
|
59
|
+
<NavItem to="/wishlist" icon={Heart} label="Wishlist" onNavigate={onNavigate} />
|
|
60
|
+
<NavItem to="/crypto" icon={Bitcoin} label="Crypto" onNavigate={onNavigate} />
|
|
60
61
|
</nav>
|
|
61
62
|
|
|
62
63
|
{/* Backend status */}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { useRef, useState, useEffect } from 'react';
|
|
2
|
+
import { cn } from '@/lib/utils';
|
|
3
|
+
|
|
4
|
+
interface PinInputProps {
|
|
5
|
+
value: string;
|
|
6
|
+
onChange: (value: string) => void;
|
|
7
|
+
onComplete?: (value: string) => void;
|
|
8
|
+
error?: boolean;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
autoFocus?: boolean;
|
|
11
|
+
length?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function PinInput({
|
|
15
|
+
value,
|
|
16
|
+
onChange,
|
|
17
|
+
onComplete,
|
|
18
|
+
error = false,
|
|
19
|
+
disabled = false,
|
|
20
|
+
autoFocus = true,
|
|
21
|
+
length = 6,
|
|
22
|
+
}: PinInputProps) {
|
|
23
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
24
|
+
const [focused, setFocused] = useState(false);
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (autoFocus && inputRef.current) {
|
|
28
|
+
const t = setTimeout(() => inputRef.current?.focus(), 120);
|
|
29
|
+
return () => clearTimeout(t);
|
|
30
|
+
}
|
|
31
|
+
}, [autoFocus]);
|
|
32
|
+
|
|
33
|
+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
34
|
+
const raw = e.target.value.replace(/\D/g, '').slice(0, length);
|
|
35
|
+
onChange(raw);
|
|
36
|
+
if (raw.length === length && onComplete) {
|
|
37
|
+
setTimeout(() => onComplete(raw), 180);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
42
|
+
if (
|
|
43
|
+
!/^\d$/.test(e.key) &&
|
|
44
|
+
!['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab'].includes(e.key) &&
|
|
45
|
+
!e.metaKey && !e.ctrlKey
|
|
46
|
+
) {
|
|
47
|
+
e.preventDefault();
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<div
|
|
53
|
+
className={cn('relative cursor-text select-none', error && 'animate-shake')}
|
|
54
|
+
onClick={() => inputRef.current?.focus()}
|
|
55
|
+
>
|
|
56
|
+
{/* Hidden input — triggers mobile numeric keyboard */}
|
|
57
|
+
<input
|
|
58
|
+
ref={inputRef}
|
|
59
|
+
type="text"
|
|
60
|
+
inputMode="numeric"
|
|
61
|
+
pattern="[0-9]*"
|
|
62
|
+
autoComplete="one-time-code"
|
|
63
|
+
maxLength={length}
|
|
64
|
+
value={value}
|
|
65
|
+
onChange={handleChange}
|
|
66
|
+
onKeyDown={handleKeyDown}
|
|
67
|
+
onFocus={() => setFocused(true)}
|
|
68
|
+
onBlur={() => setFocused(false)}
|
|
69
|
+
disabled={disabled}
|
|
70
|
+
className="absolute inset-0 w-full h-full opacity-0 cursor-text"
|
|
71
|
+
style={{ caretColor: 'transparent', fontSize: '16px' }}
|
|
72
|
+
aria-label="PIN input"
|
|
73
|
+
/>
|
|
74
|
+
|
|
75
|
+
{/* Visual cells */}
|
|
76
|
+
<div className="flex gap-[6px] sm:gap-2.5">
|
|
77
|
+
{Array.from({ length }).map((_, i) => {
|
|
78
|
+
const isFilled = i < value.length;
|
|
79
|
+
const isCursor = focused && i === value.length && !disabled;
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<div
|
|
83
|
+
key={i}
|
|
84
|
+
className={cn(
|
|
85
|
+
'w-[38px] h-[48px] sm:w-[44px] sm:h-[54px] rounded-lg border-2 flex items-center justify-center transition-all duration-200',
|
|
86
|
+
'bg-[#161616]',
|
|
87
|
+
isCursor &&
|
|
88
|
+
'border-[rgba(175,39,227,0.5)] shadow-[0_0_0_3px_rgba(175,39,227,0.1),0_0_24px_-6px_rgba(175,39,227,0.2)]',
|
|
89
|
+
isFilled && !isCursor && 'border-white/[0.12]',
|
|
90
|
+
!isFilled && !isCursor && 'border-[#252525]',
|
|
91
|
+
error && 'border-destructive/40',
|
|
92
|
+
disabled && 'opacity-40'
|
|
93
|
+
)}
|
|
94
|
+
>
|
|
95
|
+
{isFilled && (
|
|
96
|
+
<div className="w-2.5 h-2.5 rounded-full bg-foreground animate-scale-in" />
|
|
97
|
+
)}
|
|
98
|
+
{isCursor && (
|
|
99
|
+
<div className="w-[2px] h-5 rounded-full bg-white/30 animate-pulse" />
|
|
100
|
+
)}
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
})}
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
}
|