bloby-bot 0.47.8 → 0.47.10
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-bloby/assets/{bloby-E-QLmQDW.js → bloby-6SStf2s3.js} +4 -4
- package/dist-bloby/assets/globals-BjVrJod9.css +2 -0
- package/dist-bloby/assets/globals-Co_gc73k.js +21 -0
- package/dist-bloby/assets/{highlighted-body-OFNGDK62-CTiboTVa.js → highlighted-body-OFNGDK62-im0WzKGL.js} +1 -1
- package/dist-bloby/assets/mermaid-GHXKKRXX-DfO3-4oI.js +1 -0
- package/dist-bloby/assets/{onboard-C1uMxuk2.js → onboard-BJOgPIim.js} +1 -1
- package/dist-bloby/bloby.html +3 -3
- package/dist-bloby/onboard.html +3 -3
- package/package.json +4 -4
- package/supervisor/chat/OnboardWizard.tsx +189 -195
- package/supervisor/harnesses/pi/providers/stream-anthropic.ts +318 -0
- package/supervisor/harnesses/pi/providers/stream-openai-completions.ts +328 -0
- package/supervisor/harnesses/pi/providers/stream.ts +4 -2
- package/supervisor/index.ts +2 -0
- package/supervisor/public/bloby.png +0 -0
- package/supervisor/public/pi-logo.svg +20 -0
- package/workspace/client/public/icons/pi.svg +20 -0
- package/dist-bloby/assets/globals-Ci0CEj1X.js +0 -18
- package/dist-bloby/assets/globals-DriF_8Q_.css +0 -2
- package/dist-bloby/assets/mermaid-GHXKKRXX-CgVqYCFU.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{c as e,r as t,t as n}from"./jsx-runtime-C0W9Wf2W.js";import{n as r,r as i,t as a}from"./bloby-
|
|
1
|
+
import{c as e,r as t,t as n}from"./jsx-runtime-C0W9Wf2W.js";import{n as r,r as i,t as a}from"./bloby-6SStf2s3.js";var o=e(t(),1),s=n(),c=({code:e,language:t,raw:n,className:c,startLine:l,lineNumbers:u,...d})=>{let{shikiTheme:f}=(0,o.useContext)(i),p=r(),[m,h]=(0,o.useState)(n);return(0,o.useEffect)(()=>{if(!p){h(n);return}let r=p.highlight({code:e,language:t,themes:f},e=>{h(e)});r&&h(r)},[e,t,f,p,n]),(0,s.jsx)(a,{className:c,language:t,lineNumbers:u,result:m,startLine:l,...d})};export{c as HighlightedCodeBlockBody};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{i as e}from"./bloby-6SStf2s3.js";export{e as Mermaid};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{c as e,r as t,t as n}from"./jsx-runtime-C0W9Wf2W.js";import{p as r,t as i}from"./globals-
|
|
1
|
+
import{c as e,r as t,t as n}from"./jsx-runtime-C0W9Wf2W.js";import{p as r,t as i}from"./globals-Co_gc73k.js";var a=e(t(),1),o=e(r(),1),s=n();function c(){return(0,s.jsx)(i,{onComplete:()=>{window.parent?.postMessage({type:`bloby:onboard-complete`},`*`)},isInitialSetup:!0})}o.createRoot(document.getElementById(`root`)).render((0,s.jsx)(a.StrictMode,{children:(0,s.jsx)(c,{})}));
|
package/dist-bloby/bloby.html
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, interactive-widget=resizes-content" />
|
|
6
6
|
<title>Bloby Chat</title>
|
|
7
|
-
<script type="module" crossorigin src="/bloby/assets/bloby-
|
|
7
|
+
<script type="module" crossorigin src="/bloby/assets/bloby-6SStf2s3.js"></script>
|
|
8
8
|
<link rel="modulepreload" crossorigin href="/bloby/assets/jsx-runtime-C0W9Wf2W.js">
|
|
9
|
-
<link rel="modulepreload" crossorigin href="/bloby/assets/globals-
|
|
10
|
-
<link rel="stylesheet" crossorigin href="/bloby/assets/globals-
|
|
9
|
+
<link rel="modulepreload" crossorigin href="/bloby/assets/globals-Co_gc73k.js">
|
|
10
|
+
<link rel="stylesheet" crossorigin href="/bloby/assets/globals-BjVrJod9.css">
|
|
11
11
|
<link rel="stylesheet" crossorigin href="/bloby/assets/bloby-DkK0ymA2.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body class="bg-background text-foreground">
|
package/dist-bloby/onboard.html
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, interactive-widget=resizes-content" />
|
|
6
6
|
<title>Bloby Setup</title>
|
|
7
|
-
<script type="module" crossorigin src="/bloby/assets/onboard-
|
|
7
|
+
<script type="module" crossorigin src="/bloby/assets/onboard-BJOgPIim.js"></script>
|
|
8
8
|
<link rel="modulepreload" crossorigin href="/bloby/assets/jsx-runtime-C0W9Wf2W.js">
|
|
9
|
-
<link rel="modulepreload" crossorigin href="/bloby/assets/globals-
|
|
10
|
-
<link rel="stylesheet" crossorigin href="/bloby/assets/globals-
|
|
9
|
+
<link rel="modulepreload" crossorigin href="/bloby/assets/globals-Co_gc73k.js">
|
|
10
|
+
<link rel="stylesheet" crossorigin href="/bloby/assets/globals-BjVrJod9.css">
|
|
11
11
|
</head>
|
|
12
12
|
<body class="bg-background text-foreground">
|
|
13
13
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bloby-bot",
|
|
3
|
-
"version": "0.47.
|
|
3
|
+
"version": "0.47.10",
|
|
4
4
|
"releaseNotes": [
|
|
5
|
-
"1.
|
|
6
|
-
"2.
|
|
7
|
-
"3.
|
|
5
|
+
"1. Something great..",
|
|
6
|
+
"2. ",
|
|
7
|
+
"3. ",
|
|
8
8
|
"4. "
|
|
9
9
|
],
|
|
10
10
|
"description": "Self-hosted, self-evolving AI agent with its own dashboard.",
|
|
@@ -46,9 +46,10 @@ const ACCESS_LABELS: Record<AccessMethod, string> = {
|
|
|
46
46
|
/* ── Provider config ── */
|
|
47
47
|
|
|
48
48
|
const PROVIDERS = [
|
|
49
|
-
{ id: 'pi', name: '
|
|
50
|
-
{ id: '
|
|
51
|
-
{ id: '
|
|
49
|
+
{ id: 'pi', name: 'Pi', subtitle: 'Bring your own\nmodel', icon: '/pi-logo.svg', comingSoon: false },
|
|
50
|
+
{ id: 'bloby', name: 'Bloby', subtitle: 'Coming Soon..', icon: '/bloby.png', comingSoon: true },
|
|
51
|
+
{ id: 'anthropic', name: 'Claude', subtitle: 'by\nAnthropic', icon: '/icons/claude.png', comingSoon: false },
|
|
52
|
+
{ id: 'openai', name: 'OpenAI Codex', subtitle: 'ChatGPT\nPlus / Pro', icon: '/icons/codex.png', comingSoon: false },
|
|
52
53
|
] as const;
|
|
53
54
|
|
|
54
55
|
const MODELS: Record<string, { id: string; label: string }[]> = {
|
|
@@ -112,17 +113,17 @@ function ModelDropdown({ models, value, onChange }: { models: { id: string; labe
|
|
|
112
113
|
const selected = models.find((m) => m.id === value);
|
|
113
114
|
|
|
114
115
|
return (
|
|
115
|
-
<div className="relative">
|
|
116
|
+
<div className="relative min-w-0">
|
|
116
117
|
<button
|
|
117
118
|
ref={btnRef}
|
|
118
119
|
type="button"
|
|
119
120
|
onClick={() => setOpen((o) => !o)}
|
|
120
|
-
className="w-full flex items-center justify-between bg-white/[0.03] border border-white/[0.08] text-white rounded-xl px-4 py-2.5 text-[13px] outline-none hover:border-white/15 focus:border-[#AF27E3]/30 transition-colors"
|
|
121
|
+
className="w-full flex items-center justify-between gap-2 bg-white/[0.03] border border-white/[0.08] text-white rounded-xl px-4 py-2.5 text-[13px] outline-none hover:border-white/15 focus:border-[#AF27E3]/30 transition-colors overflow-hidden"
|
|
121
122
|
>
|
|
122
|
-
<span className={selected ? 'text-white' : 'text-white/20'}>
|
|
123
|
+
<span className={`min-w-0 truncate text-left ${selected ? 'text-white' : 'text-white/20'}`}>
|
|
123
124
|
{selected ? selected.label : 'Choose a model...'}
|
|
124
125
|
</span>
|
|
125
|
-
<ChevronDown className={`h-4 w-4 text-white/30 transition-transform ${open ? 'rotate-180' : ''}`} />
|
|
126
|
+
<ChevronDown className={`h-4 w-4 shrink-0 text-white/30 transition-transform ${open ? 'rotate-180' : ''}`} />
|
|
126
127
|
</button>
|
|
127
128
|
{open && pos && createPortal(
|
|
128
129
|
<div
|
|
@@ -196,8 +197,6 @@ export default function OnboardWizard({ onComplete, isInitialSetup = false, onSa
|
|
|
196
197
|
const [piShowKey, setPiShowKey] = useState(false);
|
|
197
198
|
const [piConnecting, setPiConnecting] = useState(false);
|
|
198
199
|
const [piError, setPiError] = useState<string | undefined>();
|
|
199
|
-
const [piTestRunning, setPiTestRunning] = useState(false);
|
|
200
|
-
const [piTestResult, setPiTestResult] = useState<{ ok: boolean; text?: string; error?: string } | null>(null);
|
|
201
200
|
const [piSavedStatus, setPiSavedStatus] = useState<{ subProvider?: string; modelId?: string; baseUrl?: string } | null>(null);
|
|
202
201
|
|
|
203
202
|
// Anthropic/Claude-specific
|
|
@@ -280,6 +279,10 @@ export default function OnboardWizard({ onComplete, isInitialSetup = false, onSa
|
|
|
280
279
|
const [portalOldPassError, setPortalOldPassError] = useState('');
|
|
281
280
|
const [portalOldPassVerified, setPortalOldPassVerified] = useState(false);
|
|
282
281
|
const [portalVerifying, setPortalVerifying] = useState(false);
|
|
282
|
+
// When the portal already has credentials, password fields are hidden by
|
|
283
|
+
// default so the user doesn't think they must re-authenticate to proceed.
|
|
284
|
+
// Toggling this opens the change-password sub-form.
|
|
285
|
+
const [portalChangeMode, setPortalChangeMode] = useState(false);
|
|
283
286
|
|
|
284
287
|
// Whisper (step 5)
|
|
285
288
|
const [whisperEnabled, setWhisperEnabled] = useState(false);
|
|
@@ -601,7 +604,6 @@ export default function OnboardWizard({ onComplete, isInitialSetup = false, onSa
|
|
|
601
604
|
setOpenaiError(undefined);
|
|
602
605
|
// Pi flow cleanup is per-sub-provider; the load effect handles re-hydration.
|
|
603
606
|
setPiError(undefined);
|
|
604
|
-
setPiTestResult(null);
|
|
605
607
|
};
|
|
606
608
|
|
|
607
609
|
/* ── Bloby (pi) handlers ── */
|
|
@@ -648,7 +650,6 @@ export default function OnboardWizard({ onComplete, isInitialSetup = false, onSa
|
|
|
648
650
|
setPiBaseUrl(next?.baseUrl || '');
|
|
649
651
|
setPiModelId(next?.defaultModel || '');
|
|
650
652
|
setPiError(undefined);
|
|
651
|
-
setPiTestResult(null);
|
|
652
653
|
// Picking a new sub-provider invalidates the previously saved auth.
|
|
653
654
|
if (authState.pi === 'connected') {
|
|
654
655
|
setAuthState((s) => ({ ...s, pi: 'idle' }));
|
|
@@ -659,7 +660,6 @@ export default function OnboardWizard({ onComplete, isInitialSetup = false, onSa
|
|
|
659
660
|
const handlePiConnect = async () => {
|
|
660
661
|
if (!piSubProvider) return;
|
|
661
662
|
setPiError(undefined);
|
|
662
|
-
setPiTestResult(null);
|
|
663
663
|
setPiConnecting(true);
|
|
664
664
|
try {
|
|
665
665
|
const payload = {
|
|
@@ -703,28 +703,9 @@ export default function OnboardWizard({ onComplete, isInitialSetup = false, onSa
|
|
|
703
703
|
try { await fetch('/api/auth/pi', { method: 'DELETE' }); } catch {}
|
|
704
704
|
setAuthState((s) => ({ ...s, pi: 'idle' }));
|
|
705
705
|
setPiSavedStatus(null);
|
|
706
|
-
setPiTestResult(null);
|
|
707
706
|
setModel('');
|
|
708
707
|
};
|
|
709
708
|
|
|
710
|
-
const handlePiTestCompletion = async () => {
|
|
711
|
-
setPiTestRunning(true);
|
|
712
|
-
setPiTestResult(null);
|
|
713
|
-
try {
|
|
714
|
-
const res = await fetch('/api/auth/pi/completion', {
|
|
715
|
-
method: 'POST',
|
|
716
|
-
headers: { 'Content-Type': 'application/json' },
|
|
717
|
-
body: JSON.stringify({}),
|
|
718
|
-
});
|
|
719
|
-
const data = await res.json();
|
|
720
|
-
setPiTestResult({ ok: !!data?.ok, text: data?.text, error: data?.error });
|
|
721
|
-
} catch (err: any) {
|
|
722
|
-
setPiTestResult({ ok: false, error: err?.message || 'Request failed' });
|
|
723
|
-
} finally {
|
|
724
|
-
setPiTestRunning(false);
|
|
725
|
-
}
|
|
726
|
-
};
|
|
727
|
-
|
|
728
709
|
/* ── Auth handlers: Anthropic/Claude ── */
|
|
729
710
|
|
|
730
711
|
const openExternal = (url: string) => {
|
|
@@ -1719,29 +1700,65 @@ export default function OnboardWizard({ onComplete, isInitialSetup = false, onSa
|
|
|
1719
1700
|
transition={{ duration: 0.15 }}
|
|
1720
1701
|
>
|
|
1721
1702
|
<h1 className="text-xl font-bold text-white tracking-tight">
|
|
1722
|
-
Set a password
|
|
1703
|
+
{portalExists ? 'Password & 2FA' : 'Set a password'}
|
|
1723
1704
|
</h1>
|
|
1724
1705
|
<p className="text-white/40 text-[13px] mt-1.5 leading-relaxed">
|
|
1725
|
-
|
|
1706
|
+
{portalExists
|
|
1707
|
+
? "Your portal password is already set — you can keep it as-is or change it below."
|
|
1708
|
+
: "You'll need this password to access your agent's chat. Keep it safe — anyone with your URL will need it to log in."}
|
|
1726
1709
|
</p>
|
|
1727
1710
|
|
|
1728
|
-
{
|
|
1729
|
-
|
|
1730
|
-
|
|
1711
|
+
{/* ── Existing-password collapsed state: just a "Change password" pill ── */}
|
|
1712
|
+
{portalExists && !portalChangeMode && (
|
|
1713
|
+
<div className="mt-5 flex items-center justify-between bg-white/[0.02] border border-white/[0.06] rounded-xl px-4 py-3">
|
|
1714
|
+
<div>
|
|
1715
|
+
<p className="text-white/70 text-[13px] font-medium">Portal password</p>
|
|
1716
|
+
<p className="text-white/30 text-[11px] mt-0.5">Already configured — click to change it.</p>
|
|
1717
|
+
</div>
|
|
1718
|
+
<button
|
|
1719
|
+
type="button"
|
|
1720
|
+
onClick={() => {
|
|
1721
|
+
setPortalChangeMode(true);
|
|
1722
|
+
setPortalOldPass('');
|
|
1723
|
+
setPortalOldPassError('');
|
|
1724
|
+
setPortalOldPassVerified(false);
|
|
1725
|
+
setPortalPass('');
|
|
1726
|
+
setPortalPassConfirm('');
|
|
1727
|
+
}}
|
|
1728
|
+
className="shrink-0 px-3.5 py-2 bg-white/[0.04] hover:bg-white/[0.08] text-white/70 hover:text-white text-[12px] font-medium rounded-lg transition-colors"
|
|
1729
|
+
>
|
|
1730
|
+
Change password
|
|
1731
|
+
</button>
|
|
1731
1732
|
</div>
|
|
1732
1733
|
)}
|
|
1733
1734
|
|
|
1734
|
-
{
|
|
1735
|
+
{/* ── Existing-password expanded: current password verify step ── */}
|
|
1736
|
+
{portalExists && portalChangeMode && (
|
|
1735
1737
|
<div className="mt-5">
|
|
1736
|
-
<
|
|
1738
|
+
<div className="flex items-center justify-between mb-1.5">
|
|
1739
|
+
<label className="text-[12px] text-white/40 font-medium">Current password</label>
|
|
1740
|
+
<button
|
|
1741
|
+
type="button"
|
|
1742
|
+
onClick={() => {
|
|
1743
|
+
setPortalChangeMode(false);
|
|
1744
|
+
setPortalOldPass('');
|
|
1745
|
+
setPortalOldPassError('');
|
|
1746
|
+
setPortalOldPassVerified(false);
|
|
1747
|
+
setPortalPass('');
|
|
1748
|
+
setPortalPassConfirm('');
|
|
1749
|
+
}}
|
|
1750
|
+
className="text-white/30 hover:text-white/60 text-[11px] transition-colors"
|
|
1751
|
+
>
|
|
1752
|
+
Cancel
|
|
1753
|
+
</button>
|
|
1754
|
+
</div>
|
|
1737
1755
|
<div className="flex items-center gap-2">
|
|
1738
1756
|
<input
|
|
1739
1757
|
type="password"
|
|
1740
1758
|
value={portalOldPass}
|
|
1741
1759
|
onChange={(e) => { setPortalOldPass(e.target.value); setPortalOldPassError(''); setPortalOldPassVerified(false); }}
|
|
1742
|
-
placeholder="Enter current password
|
|
1760
|
+
placeholder="Enter your current password"
|
|
1743
1761
|
autoComplete="current-password"
|
|
1744
|
-
autoFocus
|
|
1745
1762
|
className={inputCls + ' flex-1'}
|
|
1746
1763
|
/>
|
|
1747
1764
|
{portalOldPass.length > 0 && !portalOldPassVerified && (
|
|
@@ -1785,7 +1802,8 @@ export default function OnboardWizard({ onComplete, isInitialSetup = false, onSa
|
|
|
1785
1802
|
</div>
|
|
1786
1803
|
)}
|
|
1787
1804
|
|
|
1788
|
-
{
|
|
1805
|
+
{/* ── New-password fields: first-time onboard OR verified change-mode ── */}
|
|
1806
|
+
{(!portalExists || (portalChangeMode && portalOldPassVerified)) && (
|
|
1789
1807
|
<>
|
|
1790
1808
|
<div className={portalExists ? 'mt-3' : 'mt-5'}>
|
|
1791
1809
|
<label className="text-[12px] text-white/40 font-medium mb-1.5 block">
|
|
@@ -1839,6 +1857,16 @@ export default function OnboardWizard({ onComplete, isInitialSetup = false, onSa
|
|
|
1839
1857
|
setTotpEnabled(true);
|
|
1840
1858
|
setTotpError('');
|
|
1841
1859
|
setTotpCode('');
|
|
1860
|
+
// Enabling 2FA on an existing portal requires the current
|
|
1861
|
+
// password to authenticate the setup call. If the user hasn't
|
|
1862
|
+
// entered it yet, open change-mode so the current-password
|
|
1863
|
+
// field becomes visible and surface a clear error.
|
|
1864
|
+
if (portalExists && !portalOldPassVerified) {
|
|
1865
|
+
setPortalChangeMode(true);
|
|
1866
|
+
setTotpError('Enter your current portal password above first, then toggle 2FA again.');
|
|
1867
|
+
setTotpEnabled(false);
|
|
1868
|
+
return;
|
|
1869
|
+
}
|
|
1842
1870
|
// Fetch QR if not already loaded
|
|
1843
1871
|
if (!totpQrUri) {
|
|
1844
1872
|
try {
|
|
@@ -2217,15 +2245,15 @@ export default function OnboardWizard({ onComplete, isInitialSetup = false, onSa
|
|
|
2217
2245
|
>
|
|
2218
2246
|
<div className="flex flex-col items-center gap-1.5 py-0.5">
|
|
2219
2247
|
{p.icon ? (
|
|
2220
|
-
<img src={p.icon} alt={p.name} className="w-
|
|
2248
|
+
<img src={p.icon} alt={p.name} className="w-9 h-9 object-contain" />
|
|
2221
2249
|
) : (
|
|
2222
|
-
<div className="w-
|
|
2250
|
+
<div className="w-9 h-9 rounded-lg bg-white/[0.06] flex items-center justify-center text-white/50 text-sm font-bold">
|
|
2223
2251
|
O
|
|
2224
2252
|
</div>
|
|
2225
2253
|
)}
|
|
2226
2254
|
<div className="text-center">
|
|
2227
2255
|
<div className="text-[13px] font-medium text-white">{p.name}</div>
|
|
2228
|
-
<div className="text-[10px] text-white/30">{p.subtitle}</div>
|
|
2256
|
+
<div className="text-[10px] text-white/30 whitespace-pre-line leading-tight">{p.subtitle}</div>
|
|
2229
2257
|
</div>
|
|
2230
2258
|
</div>
|
|
2231
2259
|
{authState[p.id] === 'connected' ? (
|
|
@@ -2241,180 +2269,146 @@ export default function OnboardWizard({ onComplete, isInitialSetup = false, onSa
|
|
|
2241
2269
|
|
|
2242
2270
|
<div className="border-t border-white/[0.06] mt-4 mb-3" />
|
|
2243
2271
|
|
|
2244
|
-
{/* ── Auth flow:
|
|
2272
|
+
{/* ── Auth flow: Pi (bring your own model) ── */}
|
|
2245
2273
|
{provider === 'pi' && (
|
|
2246
|
-
<div className="space-y-
|
|
2274
|
+
<div className="space-y-2">
|
|
2247
2275
|
{authState.pi === 'connected' && piSavedStatus ? (
|
|
2248
|
-
|
|
2249
|
-
<
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
</p>
|
|
2254
|
-
</div>
|
|
2255
|
-
|
|
2256
|
-
<div className="bg-white/[0.03] border border-white/[0.06] rounded-xl p-3.5 space-y-2.5">
|
|
2257
|
-
<p className="text-[12px] text-white/55">
|
|
2258
|
-
Send a quick test request to confirm the model is reachable.
|
|
2259
|
-
</p>
|
|
2260
|
-
<button
|
|
2261
|
-
onClick={handlePiTestCompletion}
|
|
2262
|
-
disabled={piTestRunning}
|
|
2263
|
-
className="w-full py-2 px-4 bg-white/[0.06] hover:bg-white/[0.1] text-white text-[12px] font-medium rounded-xl transition-colors flex items-center justify-center gap-2 disabled:opacity-60"
|
|
2264
|
-
>
|
|
2265
|
-
{piTestRunning ? (
|
|
2266
|
-
<><LoaderCircle className="h-3.5 w-3.5 animate-spin" />Sending test request…</>
|
|
2267
|
-
) : (
|
|
2268
|
-
<>Send test request<ArrowRight className="h-3.5 w-3.5 opacity-60" /></>
|
|
2269
|
-
)}
|
|
2270
|
-
</button>
|
|
2271
|
-
{piTestResult && piTestResult.ok && (
|
|
2272
|
-
<div className="bg-emerald-500/5 border border-emerald-500/15 rounded-lg px-3 py-2">
|
|
2273
|
-
<p className="text-[11px] text-emerald-400/70 mb-1">Model reply</p>
|
|
2274
|
-
<p className="text-[12px] text-white/85 whitespace-pre-wrap">{piTestResult.text}</p>
|
|
2275
|
-
</div>
|
|
2276
|
-
)}
|
|
2277
|
-
{piTestResult && !piTestResult.ok && (
|
|
2278
|
-
<div className="bg-red-500/8 border border-red-500/15 rounded-lg px-3 py-2">
|
|
2279
|
-
<p className="text-[12px] text-red-400/90">{piTestResult.error}</p>
|
|
2280
|
-
</div>
|
|
2281
|
-
)}
|
|
2282
|
-
</div>
|
|
2283
|
-
|
|
2276
|
+
<div className="flex items-center justify-between bg-emerald-500/8 border border-emerald-500/15 rounded-lg px-3 py-2">
|
|
2277
|
+
<p className="text-emerald-400/90 text-[12px] truncate">
|
|
2278
|
+
Connected — {piSubProviders.find((p) => p.id === piSavedStatus.subProvider)?.name || piSavedStatus.subProvider}
|
|
2279
|
+
{piSavedStatus.modelId ? <> · <span className="font-mono">{piSavedStatus.modelId}</span></> : null}
|
|
2280
|
+
</p>
|
|
2284
2281
|
<button
|
|
2285
2282
|
onClick={handlePiDisconnect}
|
|
2286
|
-
className="
|
|
2283
|
+
className="text-white/30 hover:text-white/60 text-[11px] flex items-center gap-1 shrink-0 ml-2"
|
|
2287
2284
|
>
|
|
2288
2285
|
<RefreshCw className="h-3 w-3" />
|
|
2289
|
-
|
|
2286
|
+
Change
|
|
2290
2287
|
</button>
|
|
2291
|
-
|
|
2288
|
+
</div>
|
|
2292
2289
|
) : (
|
|
2293
2290
|
<>
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
<div
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
piSubProvider === sp.id
|
|
2304
|
-
? 'border-[#AF27E3]/50 bg-[#AF27E3]/10'
|
|
2305
|
-
: 'border-white/[0.08] bg-white/[0.02] hover:bg-white/[0.04]'
|
|
2306
|
-
}`}
|
|
2307
|
-
>
|
|
2308
|
-
<p className="text-[12.5px] text-white font-medium leading-tight">{sp.name}</p>
|
|
2309
|
-
<p className="text-[10.5px] text-white/40 mt-0.5 leading-tight">{sp.subtitle}</p>
|
|
2310
|
-
{piSubProvider === sp.id && (
|
|
2311
|
-
<div className="absolute top-1.5 right-1.5 w-1.5 h-1.5 rounded-full bg-gradient-brand" />
|
|
2312
|
-
)}
|
|
2313
|
-
</button>
|
|
2314
|
-
))}
|
|
2291
|
+
{/* Two-column compact row: provider dropdown + model picker */}
|
|
2292
|
+
<div className="grid grid-cols-2 gap-2">
|
|
2293
|
+
<div>
|
|
2294
|
+
<label className="text-[11px] text-white/40 font-medium mb-1 block">Provider</label>
|
|
2295
|
+
<ModelDropdown
|
|
2296
|
+
models={piSubProviders.map((sp) => ({ id: sp.id, label: sp.name }))}
|
|
2297
|
+
value={piSubProvider}
|
|
2298
|
+
onChange={choosePiSubProvider}
|
|
2299
|
+
/>
|
|
2315
2300
|
</div>
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
</div>
|
|
2301
|
+
<div>
|
|
2302
|
+
<label className="text-[11px] text-white/40 font-medium mb-1 block">Model</label>
|
|
2303
|
+
{selectedPiSub && Array.isArray(selectedPiSub.models) ? (
|
|
2304
|
+
<ModelDropdown
|
|
2305
|
+
models={selectedPiSub.models}
|
|
2306
|
+
value={piModelId}
|
|
2307
|
+
onChange={setPiModelId}
|
|
2308
|
+
/>
|
|
2309
|
+
) : (
|
|
2310
|
+
<input
|
|
2311
|
+
type="text"
|
|
2312
|
+
value={piModelId}
|
|
2313
|
+
onChange={(e) => setPiModelId(e.target.value)}
|
|
2314
|
+
placeholder={selectedPiSub?.defaultModel || 'model-id'}
|
|
2315
|
+
className={inputSmCls + ' font-mono'}
|
|
2316
|
+
/>
|
|
2333
2317
|
)}
|
|
2318
|
+
</div>
|
|
2319
|
+
</div>
|
|
2334
2320
|
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
>
|
|
2345
|
-
Get a key <ExternalLink className="h-3 w-3" />
|
|
2346
|
-
</button>
|
|
2347
|
-
)}
|
|
2348
|
-
</label>
|
|
2349
|
-
<div className="relative">
|
|
2350
|
-
<input
|
|
2351
|
-
type={piShowKey ? 'text' : 'password'}
|
|
2352
|
-
value={piApiKey}
|
|
2353
|
-
onChange={(e) => setPiApiKey(e.target.value)}
|
|
2354
|
-
onKeyDown={(e) => e.key === 'Enter' && handlePiConnect()}
|
|
2355
|
-
placeholder="sk-..."
|
|
2356
|
-
className={inputSmCls + ' pr-10 font-mono'}
|
|
2357
|
-
/>
|
|
2358
|
-
<button
|
|
2359
|
-
type="button"
|
|
2360
|
-
onClick={() => setPiShowKey((v) => !v)}
|
|
2361
|
-
className="absolute right-3 top-1/2 -translate-y-1/2 text-white/20 hover:text-white/50 transition-colors"
|
|
2362
|
-
>
|
|
2363
|
-
{piShowKey ? <EyeOff className="h-3.5 w-3.5" /> : <Eye className="h-3.5 w-3.5" />}
|
|
2364
|
-
</button>
|
|
2365
|
-
</div>
|
|
2366
|
-
</div>
|
|
2367
|
-
)}
|
|
2321
|
+
{selectedPiSub?.needsBaseUrl && (
|
|
2322
|
+
<input
|
|
2323
|
+
type="text"
|
|
2324
|
+
value={piBaseUrl}
|
|
2325
|
+
onChange={(e) => setPiBaseUrl(e.target.value)}
|
|
2326
|
+
placeholder={selectedPiSub.baseUrl || 'https://example.com/v1'}
|
|
2327
|
+
className={inputSmCls + ' font-mono'}
|
|
2328
|
+
/>
|
|
2329
|
+
)}
|
|
2368
2330
|
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2331
|
+
{selectedPiSub?.needsApiKey && (
|
|
2332
|
+
<div className="relative">
|
|
2333
|
+
<input
|
|
2334
|
+
type={piShowKey ? 'text' : 'password'}
|
|
2335
|
+
value={piApiKey}
|
|
2336
|
+
onChange={(e) => setPiApiKey(e.target.value)}
|
|
2337
|
+
onKeyDown={(e) => e.key === 'Enter' && handlePiConnect()}
|
|
2338
|
+
placeholder="API key…"
|
|
2339
|
+
className={inputSmCls + ' pr-16 font-mono'}
|
|
2340
|
+
/>
|
|
2341
|
+
<div className="absolute right-2 top-1/2 -translate-y-1/2 flex items-center gap-1.5">
|
|
2342
|
+
{selectedPiSub.apiKeyUrl && (
|
|
2343
|
+
<button
|
|
2344
|
+
type="button"
|
|
2345
|
+
onClick={() => openExternal(selectedPiSub.apiKeyUrl!)}
|
|
2346
|
+
className="text-white/25 hover:text-white/55 transition-colors"
|
|
2347
|
+
title="Get a key"
|
|
2348
|
+
>
|
|
2349
|
+
<ExternalLink className="h-3.5 w-3.5" />
|
|
2350
|
+
</button>
|
|
2385
2351
|
)}
|
|
2352
|
+
<button
|
|
2353
|
+
type="button"
|
|
2354
|
+
onClick={() => setPiShowKey((v) => !v)}
|
|
2355
|
+
className="text-white/25 hover:text-white/55 transition-colors"
|
|
2356
|
+
>
|
|
2357
|
+
{piShowKey ? <EyeOff className="h-3.5 w-3.5" /> : <Eye className="h-3.5 w-3.5" />}
|
|
2358
|
+
</button>
|
|
2386
2359
|
</div>
|
|
2360
|
+
</div>
|
|
2361
|
+
)}
|
|
2387
2362
|
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
</div>
|
|
2392
|
-
)}
|
|
2393
|
-
|
|
2394
|
-
<button
|
|
2395
|
-
onClick={handlePiConnect}
|
|
2396
|
-
disabled={
|
|
2397
|
-
piConnecting ||
|
|
2398
|
-
!piSubProvider ||
|
|
2399
|
-
(selectedPiSub.needsApiKey && !piApiKey.trim()) ||
|
|
2400
|
-
(selectedPiSub.needsBaseUrl && !piBaseUrl.trim()) ||
|
|
2401
|
-
!piModelId.trim()
|
|
2402
|
-
}
|
|
2403
|
-
className="w-full py-2.5 px-4 bg-gradient-brand hover:opacity-90 text-white text-[13px] font-medium rounded-xl transition-colors flex items-center justify-center gap-2 disabled:opacity-40"
|
|
2404
|
-
>
|
|
2405
|
-
{piConnecting ? (
|
|
2406
|
-
<><LoaderCircle className="h-3.5 w-3.5 animate-spin" />Connecting…</>
|
|
2407
|
-
) : (
|
|
2408
|
-
<>Test & connect<ArrowRight className="h-3.5 w-3.5 opacity-60" /></>
|
|
2409
|
-
)}
|
|
2410
|
-
</button>
|
|
2363
|
+
{piError && (
|
|
2364
|
+
<div className="bg-red-500/8 border border-red-500/15 rounded-lg px-3 py-2">
|
|
2365
|
+
<p className="text-[12px] text-red-400/90 break-words">{piError}</p>
|
|
2411
2366
|
</div>
|
|
2412
2367
|
)}
|
|
2368
|
+
|
|
2369
|
+
<button
|
|
2370
|
+
onClick={handlePiConnect}
|
|
2371
|
+
disabled={
|
|
2372
|
+
piConnecting ||
|
|
2373
|
+
!piSubProvider ||
|
|
2374
|
+
(!!selectedPiSub?.needsApiKey && !piApiKey.trim()) ||
|
|
2375
|
+
(!!selectedPiSub?.needsBaseUrl && !piBaseUrl.trim()) ||
|
|
2376
|
+
!piModelId.trim()
|
|
2377
|
+
}
|
|
2378
|
+
className="w-full py-2.5 px-4 bg-gradient-brand hover:opacity-90 text-white text-[13px] font-medium rounded-xl transition-colors flex items-center justify-center gap-2 disabled:opacity-40"
|
|
2379
|
+
>
|
|
2380
|
+
{piConnecting ? (
|
|
2381
|
+
<><LoaderCircle className="h-3.5 w-3.5 animate-spin" />Connecting…</>
|
|
2382
|
+
) : (
|
|
2383
|
+
<>Test & connect<ArrowRight className="h-3.5 w-3.5 opacity-60" /></>
|
|
2384
|
+
)}
|
|
2385
|
+
</button>
|
|
2413
2386
|
</>
|
|
2414
2387
|
)}
|
|
2415
2388
|
</div>
|
|
2416
2389
|
)}
|
|
2417
2390
|
|
|
2391
|
+
{/* ── Auth flow: Bloby (coming soon placeholder) ── */}
|
|
2392
|
+
{provider === 'bloby' && (
|
|
2393
|
+
<div className="space-y-3">
|
|
2394
|
+
<p className="text-[12px] text-white/40 leading-relaxed">
|
|
2395
|
+
Bloby (managed) is on the way. Sign in with Google once it ships and your bot will be hosted, updated, and billed by us — no API keys to manage.
|
|
2396
|
+
</p>
|
|
2397
|
+
<button
|
|
2398
|
+
type="button"
|
|
2399
|
+
disabled
|
|
2400
|
+
title="Coming soon"
|
|
2401
|
+
className="w-full py-2.5 px-4 bg-white/[0.04] border border-white/[0.06] text-white/35 text-[13px] font-medium rounded-xl flex items-center justify-center gap-2 cursor-not-allowed"
|
|
2402
|
+
>
|
|
2403
|
+
<svg viewBox="0 0 24 24" className="h-4 w-4 opacity-50" fill="currentColor">
|
|
2404
|
+
<path d="M21.35 11.1h-9.17v2.96h5.27c-.23 1.39-1.62 4.08-5.27 4.08-3.17 0-5.76-2.62-5.76-5.85s2.59-5.85 5.76-5.85c1.81 0 3.02.77 3.71 1.43l2.53-2.43C16.85 3.91 14.74 3 12.18 3 7.03 3 2.86 7.17 2.86 12.29s4.17 9.29 9.32 9.29c5.38 0 8.94-3.79 8.94-9.12 0-.61-.07-1.08-.17-1.55Z"/>
|
|
2405
|
+
</svg>
|
|
2406
|
+
Login with Google
|
|
2407
|
+
</button>
|
|
2408
|
+
<p className="text-[10.5px] text-white/25 text-center">Not available yet.</p>
|
|
2409
|
+
</div>
|
|
2410
|
+
)}
|
|
2411
|
+
|
|
2418
2412
|
{/* ── Auth flow: Anthropic ── */}
|
|
2419
2413
|
{provider === 'anthropic' && (
|
|
2420
2414
|
<div className="space-y-2.5">
|