Spaces:
Running
Running
File size: 5,158 Bytes
0ac416b 10a1123 0ac416b aab2748 86da9e3 aab2748 0ac416b aab2748 0ac416b 86da9e3 0ac416b 2d3ee50 0ac416b 10a1123 0ac416b 2d3ee50 0ac416b 10a1123 aab2748 0ac416b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | import { Button } from "@cloudflare/kumo";
import { ArrowSquareOutIcon, ArrowClockwiseIcon, SpinnerGapIcon } from "@phosphor-icons/react";
import flowMark from "../assets/flow-mark.svg";
import hfLogo from "../assets/hf-logo.svg";
import { useEffect, useState } from "react";
import { pendingShare } from "../lib/deepLink";
import { isEmbedded, newTabUrl } from "../lib/hfAuth";
import { fetchShare } from "../lib/share";
import { useAuthStore } from "../store/useAuthStore";
import { useSyncStore } from "../store/useSyncStore";
const LOGO = <img src={hfLogo} alt="" aria-hidden className="h-[18px] w-[18px]" draggable={false} />;
/** Someone opened a share link while signed out: say what it is before asking them to sign in. */
function SharePreview() {
const [info, setInfo] = useState<{ owner: string; title: string } | null>(null);
useEffect(() => {
const id = pendingShare();
if (!id) return;
let live = true;
void fetchShare(id)
.then((s) => live && setInfo({ owner: s.owner, title: s.title }))
.catch(() => undefined);
return () => {
live = false;
};
}, []);
if (!info) return null;
return (
<p className="gate__msg gate__share">
<strong>@{info.owner}</strong> shared “{info.title}” with you. Sign in to open your own copy.
</p>
);
}
/**
* Everything before the editor: signed out, signing in (embedded: in a new tab), and loading the
* account's storage. Flow keeps workflows only in the user's Hugging Face bucket, so there is no
* signed-out editor.
*/
export function SignInGate() {
const user = useAuthStore((s) => s.user);
const busy = useAuthStore((s) => s.busy);
const configured = useAuthStore((s) => s.configured);
const pending = useAuthStore((s) => s.pending);
const error = useAuthStore((s) => s.error);
const bootError = useSyncStore((s) => s.bootError);
const auth = useAuthStore.getState();
let body: React.ReactNode;
if (user && bootError) {
body = (
<>
<p className="gate__msg" role="alert">
{bootError}
</p>
<div className="gate__actions">
<Button variant="primary" icon={ArrowClockwiseIcon} onClick={() => void useSyncStore.getState().boot()}>
Retry
</Button>
<Button variant="secondary" onClick={() => auth.login()}>
Sign in again
</Button>
</div>
<button type="button" className="gate__link" onClick={() => auth.logout()}>
Sign out
</button>
</>
);
} else if (user || busy) {
body = (
<p className="gate__msg gate__loading" aria-live="polite">
<SpinnerGapIcon size={16} className="animate-spin" />
{user ? "Loading your workflows…" : "Signing in…"}
</p>
);
} else if (!configured) {
body = <p className="gate__msg">Sign-in isn't configured for this deployment (no OAuth client id).</p>;
} else if (pending === "tab") {
body = (
<>
<p className="gate__msg">
<strong>Signed in in the new tab?</strong> Continue there: it's the full app, and your work saves to your account either way.
</p>
<div className="gate__actions">
<Button variant="primary" icon={ArrowClockwiseIcon} onClick={() => void auth.finishLogin()}>
Use it here too
</Button>
<Button variant="secondary" icon={ArrowSquareOutIcon} onClick={() => auth.openApp()}>
Open Flow in a new tab
</Button>
</div>
{error && (
<p className="gate__err" role="alert">
{error}
</p>
)}
<p className="gate__fine">
Didn't see a tab?{" "}
<a className="gate__link" href={newTabUrl()} target="_blank" rel="noopener">
Open the sign-in tab
</a>{" "}
·{" "}
<button type="button" className="gate__link" onClick={() => auth.cancelLogin()}>
Cancel
</button>
</p>
</>
);
} else {
body = (
<>
<Button variant="primary" className="gate__cta" icon={LOGO} onClick={() => auth.login()}>
Sign in with Hugging Face
</Button>
{error && (
<p className="gate__err" role="alert">
{error}
</p>
)}
<p className="gate__fine">
{isEmbedded ? "Opens Flow in a new tab to sign in. " : ""}Flow is still in beta and hasn't launched yet. It's an independent tool, not made by Hugging Face. Runs use your own quota; workflows save to a private bucket on your account.
</p>
</>
);
}
return (
<div className="gate">
<div className="gate__card">
<div className="gate__brand">
<img src={flowMark} alt="" aria-hidden className="h-7 w-7" draggable={false} />
<h1 className="gate__title">Flow</h1>
<span className="beta-tag">Beta</span>
</div>
<p className="gate__lede">A visual editor for chaining Hugging Face Spaces into workflows and running them from one canvas.</p>
{!user && <SharePreview />}
{body}
</div>
</div>
);
}
|