refactor: integrate global loading effect into onboarding page and streamline LLMConfigForm usage for improved user experience

This commit is contained in:
Anish Sarkar 2026-03-29 18:46:01 +05:30
parent 32ff5f085c
commit ba926bbcc9
4 changed files with 67 additions and 144 deletions

View file

@ -16,6 +16,7 @@ import { Logo } from "@/components/Logo";
import { LLMConfigForm, type LLMConfigFormData } from "@/components/shared/llm-config-form"; import { LLMConfigForm, type LLMConfigFormData } from "@/components/shared/llm-config-form";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner"; import { Spinner } from "@/components/ui/spinner";
import { useGlobalLoadingEffect } from "@/hooks/use-global-loading";
import { getBearerToken, redirectToLogin } from "@/lib/auth-utils"; import { getBearerToken, redirectToLogin } from "@/lib/auth-utils";
export default function OnboardPage() { export default function OnboardPage() {
@ -138,17 +139,11 @@ export default function OnboardPage() {
const isSubmitting = isCreating || isUpdatingPreferences; const isSubmitting = isCreating || isUpdatingPreferences;
if (globalConfigsLoading || preferencesLoading || isAutoConfiguring) { const isLoading = globalConfigsLoading || preferencesLoading || isAutoConfiguring;
return ( useGlobalLoadingEffect(isLoading);
<div className="h-screen flex items-center justify-center bg-background dark:bg-neutral-900">
<div className="text-center space-y-3"> if (isLoading) {
<Spinner size="lg" className="mx-auto text-muted-foreground" /> return null;
<p className="text-sm text-muted-foreground">
{isAutoConfiguring ? "Setting up your AI..." : "Loading..."}
</p>
</div>
</div>
);
} }
if (globalConfigs.length > 0 && !isAutoConfiguring) { if (globalConfigs.length > 0 && !isAutoConfiguring) {
@ -171,15 +166,13 @@ export default function OnboardPage() {
{/* Form card */} {/* Form card */}
<div className="rounded-xl border bg-background dark:bg-neutral-900 flex-1 min-h-0 overflow-y-auto px-6 py-6"> <div className="rounded-xl border bg-background dark:bg-neutral-900 flex-1 min-h-0 overflow-y-auto px-6 py-6">
<LLMConfigForm <LLMConfigForm
searchSpaceId={searchSpaceId} searchSpaceId={searchSpaceId}
onSubmit={handleSubmit} onSubmit={handleSubmit}
isSubmitting={isSubmitting} mode="create"
mode="create" showAdvanced={true}
showAdvanced={true} formId="onboard-config-form"
formId="onboard-config-form" initialData={{
hideActions
initialData={{
citations_enabled: true, citations_enabled: true,
use_default_system_instructions: true, use_default_system_instructions: true,
}} }}

View file

@ -498,7 +498,7 @@ export function ModelSelector({
}} }}
> >
<Plus className="size-4 text-primary" /> <Plus className="size-4 text-primary" />
<span className="text-sm font-medium">Add New Configuration</span> <span className="text-sm font-medium">Add LLM Model</span>
</Button> </Button>
</div> </div>
</CommandList> </CommandList>

View file

@ -3,9 +3,8 @@
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { useAtomValue } from "jotai"; import { useAtomValue } from "jotai";
import { Check, ChevronDown, ChevronsUpDown } from "lucide-react"; import { Check, ChevronDown, ChevronsUpDown } from "lucide-react";
import { AnimatePresence, motion } from "motion/react";
import { useEffect, useMemo, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm, type Resolver } from "react-hook-form";
import { z } from "zod"; import { z } from "zod";
import { import {
defaultSystemInstructionsAtom, defaultSystemInstructionsAtom,
@ -41,7 +40,6 @@ import {
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { Spinner } from "@/components/ui/spinner";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { LLM_PROVIDERS } from "@/contracts/enums/llm-providers"; import { LLM_PROVIDERS } from "@/contracts/enums/llm-providers";
@ -73,28 +71,18 @@ interface LLMConfigFormProps {
initialData?: Partial<LLMConfigFormData>; initialData?: Partial<LLMConfigFormData>;
searchSpaceId: number; searchSpaceId: number;
onSubmit: (data: LLMConfigFormData) => Promise<void>; onSubmit: (data: LLMConfigFormData) => Promise<void>;
onCancel?: () => void;
isSubmitting?: boolean;
mode?: "create" | "edit"; mode?: "create" | "edit";
submitLabel?: string;
showAdvanced?: boolean; showAdvanced?: boolean;
compact?: boolean;
formId?: string; formId?: string;
hideActions?: boolean;
} }
export function LLMConfigForm({ export function LLMConfigForm({
initialData, initialData,
searchSpaceId, searchSpaceId,
onSubmit, onSubmit,
onCancel,
isSubmitting = false,
mode = "create", mode = "create",
submitLabel,
showAdvanced = true, showAdvanced = true,
compact = false,
formId, formId,
hideActions = false,
}: LLMConfigFormProps) { }: LLMConfigFormProps) {
const { data: defaultInstructions, isSuccess: defaultInstructionsLoaded } = useAtomValue( const { data: defaultInstructions, isSuccess: defaultInstructionsLoaded } = useAtomValue(
defaultSystemInstructionsAtom defaultSystemInstructionsAtom
@ -105,8 +93,7 @@ export function LLMConfigForm({
const [systemInstructionsOpen, setSystemInstructionsOpen] = useState(false); const [systemInstructionsOpen, setSystemInstructionsOpen] = useState(false);
const form = useForm<FormValues>({ const form = useForm<FormValues>({
// eslint-disable-next-line @typescript-eslint/no-explicit-any resolver: zodResolver(formSchema) as Resolver<FormValues>,
resolver: zodResolver(formSchema) as any,
defaultValues: { defaultValues: {
name: initialData?.name ?? "", name: initialData?.name ?? "",
description: initialData?.description ?? "", description: initialData?.description ?? "",
@ -232,34 +219,26 @@ export function LLMConfigForm({
)} )}
/> />
{/* Custom Provider (conditional) */} {/* Custom Provider (conditional) */}
<AnimatePresence> {watchProvider === "CUSTOM" && (
{watchProvider === "CUSTOM" && ( <FormField
<motion.div control={form.control}
initial={{ opacity: 0, height: 0 }} name="custom_provider"
animate={{ opacity: 1, height: "auto" }} render={({ field }) => (
exit={{ opacity: 0, height: 0 }} <FormItem>
> <FormLabel className="text-xs sm:text-sm">Custom Provider Name</FormLabel>
<FormField <FormControl>
control={form.control} <Input
name="custom_provider" placeholder="my-custom-provider"
render={({ field }) => ( {...field}
<FormItem> value={field.value ?? ""}
<FormLabel className="text-xs sm:text-sm">Custom Provider Name</FormLabel> />
<FormControl> </FormControl>
<Input <FormMessage />
placeholder="my-custom-provider" </FormItem>
{...field}
value={field.value ?? ""}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</motion.div>
)} )}
</AnimatePresence> />
)}
{/* Model Name with Combobox */} {/* Model Name with Combobox */}
<FormField <FormField
@ -404,36 +383,29 @@ export function LLMConfigForm({
/> />
</div> </div>
{/* Ollama Quick Actions */} {/* Ollama Quick Actions */}
<AnimatePresence> {watchProvider === "OLLAMA" && (
{watchProvider === "OLLAMA" && ( <div className="flex flex-wrap gap-2">
<motion.div <Button
initial={{ opacity: 0, height: 0 }} type="button"
animate={{ opacity: 1, height: "auto" }} variant="outline"
exit={{ opacity: 0, height: 0 }} size="sm"
className="flex flex-wrap gap-2" className="h-7 text-xs"
> onClick={() => form.setValue("api_base", "http://localhost:11434")}
<Button >
type="button" localhost:11434
variant="outline" </Button>
size="sm" <Button
className="h-7 text-xs" type="button"
onClick={() => form.setValue("api_base", "http://localhost:11434")} variant="outline"
> size="sm"
localhost:11434 className="h-7 text-xs"
</Button> onClick={() => form.setValue("api_base", "http://host.docker.internal:11434")}
<Button >
type="button" Docker
variant="outline" </Button>
size="sm" </div>
className="h-7 text-xs" )}
onClick={() => form.setValue("api_base", "http://host.docker.internal:11434")}
>
Docker
</Button>
</motion.div>
)}
</AnimatePresence>
</div> </div>
{/* Advanced Parameters */} {/* Advanced Parameters */}
@ -554,44 +526,6 @@ export function LLMConfigForm({
/> />
</CollapsibleContent> </CollapsibleContent>
</Collapsible> </Collapsible>
{!hideActions && (
<div
className={cn(
"flex gap-3 pt-4",
compact ? "justify-end" : "justify-center sm:justify-end"
)}
>
{onCancel && (
<Button
type="button"
variant="secondary"
onClick={onCancel}
disabled={isSubmitting}
className="text-xs sm:text-sm h-9 sm:h-10"
>
Cancel
</Button>
)}
<Button
type="submit"
disabled={isSubmitting}
className="gap-2 min-w-[140px] sm:min-w-[160px] text-xs sm:text-sm h-9 sm:h-10"
>
{isSubmitting ? (
<>
<Spinner size="sm" />
{mode === "edit" ? "Updating..." : "Creating"}
</>
) : (
<>
{submitLabel ??
(mode === "edit" ? "Update Configuration" : "Create Configuration")}
</>
)}
</Button>
</div>
)}
</form> </form>
</Form> </Form>
); );

View file

@ -213,14 +213,12 @@ export function ModelConfigDialog({
)} )}
{mode === "create" ? ( {mode === "create" ? (
<LLMConfigForm <LLMConfigForm
searchSpaceId={searchSpaceId} searchSpaceId={searchSpaceId}
onSubmit={handleSubmit} onSubmit={handleSubmit}
isSubmitting={isSubmitting} mode="create"
mode="create" formId="model-config-form"
formId="model-config-form" />
hideActions
/>
) : isAutoMode && config ? ( ) : isAutoMode && config ? (
<div className="space-y-6"> <div className="space-y-6">
<div className="space-y-4"> <div className="space-y-4">
@ -362,11 +360,9 @@ export function ModelConfigDialog({
citations_enabled: config.citations_enabled, citations_enabled: config.citations_enabled,
search_space_id: searchSpaceId, search_space_id: searchSpaceId,
}} }}
onSubmit={handleSubmit} onSubmit={handleSubmit}
isSubmitting={isSubmitting} mode="edit"
mode="edit" formId="model-config-form"
formId="model-config-form"
hideActions
/> />
) : null} ) : null}
</div> </div>