mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-22 20:52:23 +02:00
[eric] onboarding: sidebar can't cover the flow, managed pro lane isn't a user connection, aux-less prep re-runs on real connect; tools pane header slimmed
This commit is contained in:
@@ -75,3 +75,5 @@ class PrepResponse(BaseModel):
|
||||
automations: List[PersonalizedAutomation] = Field(default_factory=list)
|
||||
# The hero's two-level menu (4 categories x 4 tailored starters); None only if prep never ran.
|
||||
menu: Optional[PersonalizedMenu] = None
|
||||
# False = scan-grounded fallback (no aux lane at call time); lets the pipeline re-run prep once a real connect lands.
|
||||
used_llm: bool = False
|
||||
|
||||
@@ -390,6 +390,7 @@ async def build_prep(settings: AppSettings, request: PrepRequest) -> PrepRespons
|
||||
parsed = parse_prep(safe_resp_text(resp))
|
||||
if parsed is not None:
|
||||
parsed.menu = await menu_task
|
||||
parsed.used_llm = True
|
||||
return parsed
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -146,6 +146,8 @@ const AppShell: React.FC = () => {
|
||||
const modelsLoaded = useAppSelector((s) => s.models.loaded);
|
||||
// "Connected" = the user's OWN model (key/sub/pro/custom), NOT a non-empty /models list: the free-trial Haiku is always in that list now, so a byProvider-length check would falsely read as connected and hide the out-of-runs banner.
|
||||
const hasModelConnected = useAppSelector(selectHasModelConnected);
|
||||
// While onboarding owns the window, the floating sidebar (and its hover-peek strip) must not exist; both out-z the overlay.
|
||||
const v3FlowActive = useAppSelector((st) => st.onboardingV3.flowActive);
|
||||
// During an active free trial the user CAN run things, so a red "no model connected" warning is misleading and discouraging (it sits right above the working starter chips). The trial flips connection_mode back to own_key the moment it's spent, so this banner returns then, landing the connect-a-model nudge after the win, not before it.
|
||||
const freeTrialActive = useAppSelector((s) => {
|
||||
const d = s.settings.data as any;
|
||||
@@ -701,7 +703,7 @@ const AppShell: React.FC = () => {
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', height: '100vh', bgcolor: c.bg.secondary }}>
|
||||
{sidebarAway && !sidePeek && (
|
||||
{sidebarAway && !sidePeek && !v3FlowActive && (
|
||||
<Box onMouseEnter={() => { cancelPeekClose(); setSidePeek(true); }} sx={{ position: 'fixed', top: 0, left: 0, bottom: 0, width: 14, zIndex: 2147483000, pointerEvents: 'auto' }} />
|
||||
)}
|
||||
{/* Top bar dropped (Arc/Zen): a zero-height anchor left only to float the agent-activity island at top-center; the island renders nothing when idle. */}
|
||||
@@ -933,9 +935,9 @@ const AppShell: React.FC = () => {
|
||||
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%)',
|
||||
transform: sidePeek && !v3FlowActive ? 'translateX(0)' : 'translateX(-118%)',
|
||||
transition: 'transform 240ms cubic-bezier(0.22,1,0.36,1)',
|
||||
pointerEvents: sidePeek ? 'auto' : 'none',
|
||||
pointerEvents: sidePeek && !v3FlowActive ? 'auto' : 'none',
|
||||
} : fsActive ? {
|
||||
// Fullscreen with the sidebar pinned: the panel sits beside a full-bleed surface, so its
|
||||
// dashboard-facing right corners curve like a pill; normal docked mode stays square.
|
||||
|
||||
@@ -130,7 +130,7 @@ const OnboardingV3Root: React.FC = () => {
|
||||
kickIdentity();
|
||||
kickScan(true);
|
||||
if (connectedProvider) kickUsageRead(connectedProvider, true);
|
||||
kickPrep(picks);
|
||||
kickPrep(picks, true);
|
||||
}, [kickIdentity, kickScan, kickUsageRead, kickPrep, connectedProvider, picks]);
|
||||
|
||||
// Backstop: onConnected fires prep for subscription/api-key connects; this covers any path where it
|
||||
|
||||
@@ -38,6 +38,7 @@ export interface PrepResponse {
|
||||
browser_reason: string;
|
||||
automations: PersonalizedAutomation[];
|
||||
menu?: PersonalizedMenu | null;
|
||||
used_llm?: boolean;
|
||||
}
|
||||
|
||||
export async function fetchIdentity(): Promise<ProviderIdentity[]> {
|
||||
|
||||
@@ -61,8 +61,16 @@ export function useOnboardingV3Pipeline() {
|
||||
: Promise.resolve(null);
|
||||
}, []);
|
||||
|
||||
const kickPrep = useCallback((pickedApps: string[]) => {
|
||||
if (prepRef.current) return;
|
||||
const kickPrep = useCallback((pickedApps: string[], rerunIfGrounded = false) => {
|
||||
// A real connect landing AFTER an aux-less prep re-runs it once, so a slow OAuth never locks the
|
||||
// reveal to the scan-grounded template (the prep raced the connect and had no model to call).
|
||||
if (prepRef.current) {
|
||||
if (!rerunIfGrounded) return;
|
||||
const prev = prepReadyRef.current;
|
||||
if (prev === null || prev.used_llm) return;
|
||||
prepRef.current = null;
|
||||
prepReadyRef.current = null;
|
||||
}
|
||||
const scanPromise = scanRef.current ?? Promise.resolve(null);
|
||||
const usagePromise = usageReadRef.current ?? Promise.resolve();
|
||||
prepRef.current = Promise.all([scanPromise, usagePromise])
|
||||
|
||||
@@ -104,19 +104,17 @@ const Tools: React.FC = () => {
|
||||
}, [regServersRaw, a.regSource, CURATED_MCP_NAMES]);
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 3, height: '100%', overflow: 'auto' }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 3 }}>
|
||||
<Box>
|
||||
<Typography variant="h5" sx={{ color: c.text.primary, fontWeight: 700, mb: 0.5 }}>Tool Library</Typography>
|
||||
<Typography sx={{ color: c.text.tertiary, fontSize: '0.875rem' }}>Define and manage custom tools for your Claude Code agents.</Typography>
|
||||
</Box>
|
||||
<Box sx={{ px: 3, pt: 1, pb: 3, height: '100%', overflow: 'auto' }}>
|
||||
{/* The pane header already says "Tools"; a slim action row beats a second page title. */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', mb: 1.5 }}>
|
||||
<Box>
|
||||
<Button
|
||||
size="small"
|
||||
variant="contained"
|
||||
startIcon={<AddIcon />}
|
||||
endIcon={<KeyboardArrowDownIcon sx={{ fontSize: 18 }} />}
|
||||
startIcon={<AddIcon sx={{ fontSize: 16 }} />}
|
||||
endIcon={<KeyboardArrowDownIcon sx={{ fontSize: 16 }} />}
|
||||
onClick={handleMenuOpen}
|
||||
sx={{ bgcolor: c.accent.primary, '&:hover': { bgcolor: c.accent.pressed }, textTransform: 'none', borderRadius: 2 }}
|
||||
sx={{ bgcolor: c.accent.primary, '&:hover': { bgcolor: c.accent.pressed }, textTransform: 'none', borderRadius: 2, fontSize: '0.8125rem' }}
|
||||
>
|
||||
New Tool
|
||||
</Button>
|
||||
|
||||
@@ -117,8 +117,10 @@ export function selectSubscriptionConnections(
|
||||
): SubscriptionConnection[] {
|
||||
const providers = state.subscriptions.status?.providers;
|
||||
if (!providers) return EMPTY_CONNECTIONS;
|
||||
if (Array.isArray(providers)) return providers;
|
||||
return providers.connections ?? EMPTY_CONNECTIONS;
|
||||
const rows = Array.isArray(providers) ? providers : (providers.connections ?? EMPTY_CONNECTIONS);
|
||||
// The free-trial/Pro lane is an OpenSwarm-managed router row, not a connection the USER made; counting it made a fresh trial look "connected" (dead Connect-beat rows, premature onConnected, phantom green rings).
|
||||
const filtered = rows.filter((p) => !/\(OpenSwarm-managed\)/.test(p.name ?? ''));
|
||||
return filtered.length === rows.length ? rows : filtered;
|
||||
}
|
||||
|
||||
export function hasAnyActiveSubscription(state: WithSubscriptions): boolean {
|
||||
|
||||
Reference in New Issue
Block a user