mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-26 14:32:22 +02:00
[eric] onboarding+chrome: clear pill/tool labels (keep thinking-bubble whimsy), parallel reveal launches, overlay sidebar hugs window edges
This commit is contained in:
@@ -853,9 +853,13 @@ const AppShell: React.FC = () => {
|
||||
// Zen/Arc compact mode: a detached, rounded panel that SLIDES in and out from the left edge
|
||||
// (sidePeek drives the transform both ways; it stays mounted so leaving glides it away, not vanish).
|
||||
...(sideOverlay ? {
|
||||
position: 'fixed', top: 10, left: 10, bottom: 10, zIndex: 1000002,
|
||||
borderRadius: '14px', overflow: 'hidden',
|
||||
boxShadow: '0 16px 48px rgba(0,0,0,0.34)', border: `1px solid ${c.border.medium}`,
|
||||
// Hug the window's top/left/bottom edges (same footprint as the docked sidebar) so the
|
||||
// dashboard's corner tint can't peek in the margins; only the dashboard-facing right corners
|
||||
// stay rounded. The OS clips the square outer corners to the window's own rounding, so the
|
||||
// window edge (the "last border") stays intact.
|
||||
position: 'fixed', top: 0, left: 0, bottom: 0, zIndex: 1000002,
|
||||
borderRadius: '0 14px 14px 0', overflow: 'hidden',
|
||||
boxShadow: '8px 0 32px rgba(0,0,0,0.28)', borderRight: `1px solid ${c.border.medium}`,
|
||||
transform: sidePeek ? 'translateX(0)' : 'translateX(-118%)',
|
||||
transition: 'transform 240ms cubic-bezier(0.22,1,0.36,1)',
|
||||
pointerEvents: sidePeek ? 'auto' : 'none',
|
||||
|
||||
@@ -34,11 +34,6 @@ function cadenceToSchedule(cadence: string): Record<string, unknown> {
|
||||
return { ...base, repeat_unit: 'week', on_days: [1] };
|
||||
}
|
||||
|
||||
// Gap between the demo launches. Right after connect the provider's OAuth token can still be mid-refresh,
|
||||
// and four simultaneous cold turns raced that refresh and all "hit a snag"; the first launch warms the
|
||||
// token, the rest ride it warm. Fires during the curtain beats, so the lead time is hidden.
|
||||
const LAUNCH_STAGGER_MS = 1200;
|
||||
|
||||
// How long finish() will wait for prep before staging the reveal. Prep is kicked early (theme beat) so it
|
||||
// has usually resolved; this only bites a user who outran it, and a brief wait for a COHERENT reveal beats
|
||||
// an instant one showing a previous run's stale greeting. Capped so it's never an open-ended spinner.
|
||||
@@ -153,28 +148,16 @@ export function useOnboardingV3Pipeline() {
|
||||
if (launchedRef.current || !prep || !prep.greeting || !launchCtxRef.current.connected) return;
|
||||
launchedRef.current = true;
|
||||
// The four auto-run showcase jobs, one per capability: build an app, dig the web, drive a real
|
||||
// browser, and set up a scheduled task. The lame file-audit is gone; the browser task shows the
|
||||
// agent controlling a real site live. Fired STAGGERED so the first turn warms the just-connected
|
||||
// provider token before the rest hit it (see LAUNCH_STAGGER_MS).
|
||||
const launches: Array<() => void> = [];
|
||||
if (prep.app_title && prep.app_prompt) {
|
||||
const t = prep.app_title, p = prep.app_prompt, r = prep.app_reason ?? '';
|
||||
launches.push(() => launchJob(t, p, 'app', r));
|
||||
}
|
||||
if (prep.research_title && prep.research_prompt) {
|
||||
const t = prep.research_title, p = prep.research_prompt, r = prep.research_reason ?? '';
|
||||
launches.push(() => launchJob(t, p, 'research', r));
|
||||
}
|
||||
if (prep.browser_title && prep.browser_prompt) {
|
||||
const t = prep.browser_title, p = prep.browser_prompt, r = prep.browser_reason ?? '';
|
||||
launches.push(() => launchJob(t, p, 'browser', r));
|
||||
}
|
||||
// browser, and set up a scheduled task. Each fires INDEPENDENTLY (own draft session, own async
|
||||
// launch, own error handling), so none waits on another and one failing never blocks the rest; a
|
||||
// real first-run's provider token is fresh (just connected), so parallel launch is safe.
|
||||
if (prep.app_title && prep.app_prompt) launchJob(prep.app_title, prep.app_prompt, 'app', prep.app_reason ?? '');
|
||||
if (prep.research_title && prep.research_prompt) launchJob(prep.research_title, prep.research_prompt, 'research', prep.research_reason ?? '');
|
||||
if (prep.browser_title && prep.browser_prompt) launchJob(prep.browser_title, prep.browser_prompt, 'browser', prep.browser_reason ?? '');
|
||||
// The scheduled task is a first-class part of the reveal (the "it automates for me" capability), so
|
||||
// guarantee one: use the model's automation when it emitted one (it sometimes drops the last JSON
|
||||
// field), else fall back to a safe, universally-useful weekly roundup.
|
||||
const auto = prep.automations[0] ?? SCHEDULE_FALLBACK;
|
||||
launches.push(() => createScheduledJob(auto));
|
||||
launches.forEach((fn, idx) => { if (idx === 0) fn(); else window.setTimeout(fn, idx * LAUNCH_STAGGER_MS); });
|
||||
createScheduledJob(prep.automations[0] ?? SCHEDULE_FALLBACK);
|
||||
});
|
||||
}, [launchJob, createScheduledJob]);
|
||||
|
||||
|
||||
@@ -155,22 +155,13 @@ const thinkingShimmerKeyframes = `
|
||||
}
|
||||
`;
|
||||
|
||||
// Pick a label deterministically per session-turn so the pill has variety without flickering between renders. Shared list with MessageBubble.
|
||||
function streamingLabelFor(seedKey: string | undefined): string {
|
||||
if (!seedKey) return THINKING_LABELS[0].live;
|
||||
let h = 0;
|
||||
for (let i = 0; i < seedKey.length; i++) {
|
||||
h = ((h << 5) - h + seedKey.charCodeAt(i)) | 0;
|
||||
}
|
||||
return THINKING_LABELS[Math.abs(h) % THINKING_LABELS.length].live;
|
||||
}
|
||||
|
||||
const ThinkingBubble: React.FC<{ label?: string | null; seedKey?: string }> = ({ label, seedKey }) => {
|
||||
const ThinkingBubble: React.FC<{ label?: string | null }> = ({ label }) => {
|
||||
const c = useClaudeTokens();
|
||||
const shimmerBase = c.text.tertiary;
|
||||
const shimmerHighlight = c.text.primary;
|
||||
// Aux-LLM label wins; otherwise pick a quirky verb keyed off seedKey so different sessions / turns show different verbs without flicker.
|
||||
const display = label ? `${label}…` : `${streamingLabelFor(seedKey)}…`;
|
||||
// Aux-LLM label wins; otherwise the pill stays plain "Thinking". The whimsical verbs read as personality
|
||||
// in the per-message thinking bubble (MessageBubble), but as a vague, confusing status on a working card.
|
||||
const display = label ? `${label}…` : `${THINKING_LABELS[0].live}…`;
|
||||
return (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'flex-start', my: 0.75 }}>
|
||||
<style>{thinkingShimmerKeyframes}</style>
|
||||
@@ -1779,7 +1770,6 @@ const AgentChat: React.FC<AgentChatProps> = ({ sessionId: sessionIdProp, onClose
|
||||
<Box sx={{ overflowAnchor: 'none' }}>
|
||||
<ThinkingBubble
|
||||
label={preSendActivityLabel || session.turn_label?.label}
|
||||
seedKey={`${session.id}:${session.messages?.length ?? 0}`}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
@@ -3,18 +3,12 @@ export interface ToolLabel {
|
||||
past: string;
|
||||
}
|
||||
|
||||
// djb2 hash; same seed always picks the same variant so rows don't flicker.
|
||||
function _stableIndex(seed: string | undefined, n: number): number {
|
||||
if (n <= 1 || !seed) return 0;
|
||||
let h = 5381;
|
||||
for (let i = 0; i < seed.length; i++) {
|
||||
h = ((h << 5) + h + seed.charCodeAt(i)) | 0;
|
||||
}
|
||||
return Math.abs(h) % n;
|
||||
}
|
||||
|
||||
// The clearest label of each verb pool. We used to seed-pick a random synonym for personality, but the
|
||||
// whimsical spread ("Rummaging the toolbox", "Eyeballing") read as vague status noise on a working card;
|
||||
// that variety lives on in the per-message thinking bubble instead. Index 0 is the plain literal by design.
|
||||
function _pick<T>(variants: T[], seed?: string): T {
|
||||
return variants[_stableIndex(seed, variants.length)];
|
||||
void seed;
|
||||
return variants[0];
|
||||
}
|
||||
|
||||
// Index 0 is the safe default; single-entry pools mean no seeded variation.
|
||||
@@ -142,17 +136,10 @@ const VARIANTS: Record<string, ToolLabel[]> = {
|
||||
{ present: 'Pulling up the task', past: 'Pulled up the task' },
|
||||
],
|
||||
toolsearch: [
|
||||
{ present: 'Looking through the toolbox', past: 'Looked through the toolbox' },
|
||||
{ present: 'Hunting for the right tool', past: 'Found a tool' },
|
||||
{ present: 'Browsing the toolbox', past: 'Browsed the toolbox' },
|
||||
{ present: 'Rummaging the toolbox', past: 'Rummaged the toolbox' },
|
||||
{ present: 'Digging through the toolbox', past: 'Dug through the toolbox' },
|
||||
{ present: 'Finding the right tool', past: 'Found the right tool' },
|
||||
],
|
||||
mcpsearch: [
|
||||
{ present: 'Looking through the toolbox', past: 'Looked through the toolbox' },
|
||||
{ present: 'Hunting for the right tool', past: 'Found a tool' },
|
||||
{ present: 'Browsing the toolbox', past: 'Browsed the toolbox' },
|
||||
{ present: 'Rummaging the toolbox', past: 'Rummaged the toolbox' },
|
||||
{ present: 'Finding the right tool', past: 'Found the right tool' },
|
||||
],
|
||||
// getToolLabelWithInput has the brand-aware version; this is the seedless fallback.
|
||||
mcpactivate: [
|
||||
@@ -164,7 +151,6 @@ const VARIANTS: Record<string, ToolLabel[]> = {
|
||||
],
|
||||
mcplist: [
|
||||
{ present: 'Listing tools', past: 'Listed tools' },
|
||||
{ present: 'Browsing the toolbox', past: 'Browsed the toolbox' },
|
||||
],
|
||||
settingsread: [
|
||||
{ present: 'Checking your settings', past: 'Checked your settings' },
|
||||
|
||||
Reference in New Issue
Block a user