mirror of
https://github.com/meta-llama/llama-stack.git
synced 2025-07-16 01:53:10 +00:00
feat(auth,ui): support github sign-in in the UI (#2545)
# What does this PR do? Uses NextAuth to add github sign in support. ## Test Plan Start server with auth configured as in https://github.com/meta-llama/llama-stack/pull/2509 https://github.com/user-attachments/assets/61ff7442-f601-4b39-8686-5d0afb3b45ac
This commit is contained in:
parent
c8bac888af
commit
daf660c4ea
23 changed files with 577 additions and 81 deletions
24
llama_stack/ui/hooks/use-auth-client.ts
Normal file
24
llama_stack/ui/hooks/use-auth-client.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
import { useSession } from "next-auth/react";
|
||||
import { useMemo } from "react";
|
||||
import LlamaStackClient from "llama-stack-client";
|
||||
|
||||
export function useAuthClient() {
|
||||
const { data: session } = useSession();
|
||||
|
||||
const client = useMemo(() => {
|
||||
const clientHostname =
|
||||
typeof window !== "undefined" ? window.location.origin : "";
|
||||
|
||||
const options: any = {
|
||||
baseURL: `${clientHostname}/api`,
|
||||
};
|
||||
|
||||
if (session?.accessToken) {
|
||||
options.apiKey = session.accessToken;
|
||||
}
|
||||
|
||||
return new LlamaStackClient(options);
|
||||
}, [session?.accessToken]);
|
||||
|
||||
return client;
|
||||
}
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
import { useState, useCallback, useEffect, useRef } from "react";
|
||||
import { PaginationStatus, UsePaginationOptions } from "@/lib/types";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { useAuthClient } from "@/hooks/use-auth-client";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
interface PaginationState<T> {
|
||||
data: T[];
|
||||
|
@ -28,13 +31,18 @@ export interface PaginationReturn<T> {
|
|||
}
|
||||
|
||||
interface UsePaginationParams<T> extends UsePaginationOptions {
|
||||
fetchFunction: (params: {
|
||||
after?: string;
|
||||
limit: number;
|
||||
model?: string;
|
||||
order?: string;
|
||||
}) => Promise<PaginationResponse<T>>;
|
||||
fetchFunction: (
|
||||
client: ReturnType<typeof useAuthClient>,
|
||||
params: {
|
||||
after?: string;
|
||||
limit: number;
|
||||
model?: string;
|
||||
order?: string;
|
||||
},
|
||||
) => Promise<PaginationResponse<T>>;
|
||||
errorMessagePrefix: string;
|
||||
enabled?: boolean;
|
||||
useAuth?: boolean;
|
||||
}
|
||||
|
||||
export function usePagination<T>({
|
||||
|
@ -43,7 +51,12 @@ export function usePagination<T>({
|
|||
order = "desc",
|
||||
fetchFunction,
|
||||
errorMessagePrefix,
|
||||
enabled = true,
|
||||
useAuth = true,
|
||||
}: UsePaginationParams<T>): PaginationReturn<T> {
|
||||
const { status: sessionStatus } = useSession();
|
||||
const client = useAuthClient();
|
||||
const router = useRouter();
|
||||
const [state, setState] = useState<PaginationState<T>>({
|
||||
data: [],
|
||||
status: "loading",
|
||||
|
@ -74,7 +87,7 @@ export function usePagination<T>({
|
|||
error: null,
|
||||
}));
|
||||
|
||||
const response = await fetchFunction({
|
||||
const response = await fetchFunction(client, {
|
||||
after: after || undefined,
|
||||
limit: fetchLimit,
|
||||
...(model && { model }),
|
||||
|
@ -91,6 +104,17 @@ export function usePagination<T>({
|
|||
status: "idle",
|
||||
}));
|
||||
} catch (err) {
|
||||
// Check if it's a 401 unauthorized error
|
||||
if (
|
||||
err &&
|
||||
typeof err === "object" &&
|
||||
"status" in err &&
|
||||
err.status === 401
|
||||
) {
|
||||
router.push("/auth/signin");
|
||||
return;
|
||||
}
|
||||
|
||||
const errorMessage = isInitialLoad
|
||||
? `Failed to load ${errorMessagePrefix}. Please try refreshing the page.`
|
||||
: `Failed to load more ${errorMessagePrefix}. Please try again.`;
|
||||
|
@ -107,7 +131,7 @@ export function usePagination<T>({
|
|||
}));
|
||||
}
|
||||
},
|
||||
[limit, model, order, fetchFunction, errorMessagePrefix],
|
||||
[limit, model, order, fetchFunction, errorMessagePrefix, client, router],
|
||||
);
|
||||
|
||||
/**
|
||||
|
@ -120,17 +144,28 @@ export function usePagination<T>({
|
|||
}
|
||||
}, [fetchData]);
|
||||
|
||||
// Auto-load initial data on mount
|
||||
// Auto-load initial data on mount when enabled
|
||||
useEffect(() => {
|
||||
if (!hasFetchedInitialData.current) {
|
||||
// If using auth, wait for session to load
|
||||
const isAuthReady = !useAuth || sessionStatus !== "loading";
|
||||
const shouldFetch = enabled && isAuthReady;
|
||||
|
||||
if (shouldFetch && !hasFetchedInitialData.current) {
|
||||
hasFetchedInitialData.current = true;
|
||||
fetchData();
|
||||
} else if (!shouldFetch) {
|
||||
// Reset the flag when disabled so it can fetch when re-enabled
|
||||
hasFetchedInitialData.current = false;
|
||||
}
|
||||
}, [fetchData]);
|
||||
}, [fetchData, enabled, useAuth, sessionStatus]);
|
||||
|
||||
// Override status if we're waiting for auth
|
||||
const effectiveStatus =
|
||||
useAuth && sessionStatus === "loading" ? "loading" : state.status;
|
||||
|
||||
return {
|
||||
data: state.data,
|
||||
status: state.status,
|
||||
status: effectiveStatus,
|
||||
hasMore: state.hasMore,
|
||||
error: state.error,
|
||||
loadMore,
|
Loading…
Add table
Add a link
Reference in a new issue