| import React, { useState } from "react"; |
| import { AlertCircle, ExternalLink, Loader2 } from "lucide-react"; |
| import { Button } from "@/components/ui/button"; |
| import { Input } from "@/components/ui/input"; |
| import { useApi } from "@/contexts/ApiContext"; |
| import { useHfAuth } from "@/contexts/HfAuthContext"; |
|
|
| const HfAuthBanner: React.FC = () => { |
| const { auth, refetch } = useHfAuth(); |
| const { baseUrl, fetchWithHeaders } = useApi(); |
| const [token, setToken] = useState(""); |
| const [submitting, setSubmitting] = useState(false); |
| const [error, setError] = useState<string | null>(null); |
|
|
| if (auth.status === "authenticated" || auth.status === "loading") { |
| return null; |
| } |
|
|
| const handleSave = async () => { |
| const trimmed = token.trim(); |
| if (!trimmed) return; |
| setSubmitting(true); |
| setError(null); |
| try { |
| const r = await fetchWithHeaders(`${baseUrl}/hf-auth/login`, { |
| method: "POST", |
| headers: { "Content-Type": "application/json" }, |
| body: JSON.stringify({ token: trimmed }), |
| }); |
| if (!r.ok) { |
| const body = await r.json().catch(() => ({})); |
| throw new Error(body.detail || `HTTP ${r.status}`); |
| } |
| setToken(""); |
| await refetch(); |
| } catch (e) { |
| setError(e instanceof Error ? e.message : String(e)); |
| } finally { |
| setSubmitting(false); |
| } |
| }; |
|
|
| return ( |
| <div className="bg-amber-950/40 border border-amber-700/60 rounded-lg p-4 mb-6"> |
| <div className="flex items-start gap-3"> |
| <AlertCircle className="w-5 h-5 text-amber-400 flex-shrink-0 mt-0.5" /> |
| <div className="flex-1 space-y-3"> |
| <div> |
| <p className="text-sm text-amber-100 font-medium"> |
| Hugging Face access required for cloud training |
| </p> |
| <p className="text-xs text-amber-200/80 mt-1"> |
| Create a token at{" "} |
| <a |
| href="https://huggingface.co/settings/tokens" |
| target="_blank" |
| rel="noreferrer" |
| className="underline hover:text-amber-50 inline-flex items-center gap-1" |
| > |
| huggingface.co/settings/tokens |
| <ExternalLink className="w-3 h-3" /> |
| </a> |
| {" "}with <span className="font-mono">Write</span> access (so trained |
| policies can upload to your account), then paste it below. |
| </p> |
| </div> |
| <form |
| onSubmit={(e) => { |
| e.preventDefault(); |
| handleSave(); |
| }} |
| className="flex gap-2" |
| > |
| <Input |
| type="password" |
| placeholder="hf_..." |
| value={token} |
| onChange={(e) => setToken(e.target.value)} |
| className="bg-slate-900 border-slate-600 text-white placeholder:text-slate-500" |
| disabled={submitting} |
| autoComplete="off" |
| /> |
| <Button |
| type="submit" |
| disabled={submitting || !token.trim()} |
| className="bg-amber-600 hover:bg-amber-700 text-white" |
| > |
| {submitting ? ( |
| <> |
| <Loader2 className="w-4 h-4 mr-2 animate-spin" /> |
| Saving… |
| </> |
| ) : ( |
| "Save token" |
| )} |
| </Button> |
| </form> |
| {error && ( |
| <p className="text-xs text-red-300">{error}</p> |
| )} |
| </div> |
| </div> |
| </div> |
| ); |
| }; |
|
|
| export default HfAuthBanner; |
|
|